Open chat file links at the referenced line (#2309)

* fix(app): open chat file links at referenced lines

Pass chat file locations through the editable CodeMirror path so opening a reference selects and scrolls to its requested line. Cover the real assistant-link flow with a browser regression.

* fix(app): repeat chat file navigation

Track file navigation separately from stable tab identity so reopening the same path and line recenters both editable and read-only panes. Extend the browser regression to cover moving away and clicking the same link again.

* test(app): stabilize repeated file link navigation
This commit is contained in:
Mohamed Boudra
2026-07-21 21:34:20 +02:00
committed by GitHub
parent 7d10791bad
commit cb6d2f1459
8 changed files with 131 additions and 3 deletions
@@ -1,11 +1,14 @@
import { Text, View } from "react-native";
import { StyleSheet } from "react-native-unistyles";
import type { HighlightStyle } from "@getpaseo/highlight";
import type { WorkspaceFileLocation } from "@/workspace/file-open";
import type { FileEditorModel } from "./model";
export function FileEditorView(_props: {
model: FileEditorModel;
filename: string;
location: WorkspaceFileLocation;
navigationRevision: number;
vimEnabled: boolean;
theme: {
background: string;
@@ -3,12 +3,15 @@ import { Annotation, Compartment, EditorState, Transaction } from "@codemirror/s
import { EditorView } from "@codemirror/view";
import { getLanguageForFile } from "@getpaseo/highlight";
import { getCM, vim } from "@replit/codemirror-vim";
import type { WorkspaceFileLocation } from "@/workspace/file-open";
import type { FileEditorModel } from "./model";
import { editorBaseExtensions, editorTheme, type EditorVisualTheme } from "./extensions.web";
interface FileEditorViewProps {
model: FileEditorModel;
filename: string;
location: WorkspaceFileLocation;
navigationRevision: number;
vimEnabled: boolean;
theme: EditorVisualTheme;
onCursorChange(position: { line: number; column: number }): void;
@@ -22,6 +25,8 @@ const vimCompartment = new Compartment();
export function FileEditorView({
model,
filename,
location,
navigationRevision,
vimEnabled,
theme,
onCursorChange,
@@ -81,6 +86,19 @@ export function FileEditorView({
});
}, [snapshot.content]);
useEffect(() => {
const view = viewRef.current;
if (!view || !location.lineStart) return;
const lineStart = Math.min(location.lineStart, view.state.doc.lines);
const lineEnd = Math.min(location.lineEnd ?? lineStart, view.state.doc.lines);
const from = view.state.doc.line(lineStart).from;
const to = view.state.doc.line(Math.max(lineStart, lineEnd)).to;
view.dispatch({
selection: { anchor: from, head: lineEnd > lineStart ? to : from },
effects: EditorView.scrollIntoView(from, { y: "center" }),
});
}, [location.lineEnd, location.lineStart, navigationRevision]);
useEffect(() => {
viewRef.current?.dispatch({
effects: languageCompartment.reconfigure(getLanguageForFile(filename)?.extension ?? []),
+15 -1
View File
@@ -57,6 +57,7 @@ interface FilePreviewBodyProps {
isLoading: boolean;
isMobile: boolean;
location: WorkspaceFileLocation;
navigationRevision: number;
imagePreviewUri: string | null;
}
@@ -207,6 +208,7 @@ function FilePreviewBody({
isLoading,
isMobile,
location,
navigationRevision,
imagePreviewUri,
}: FilePreviewBodyProps) {
const theme = UnistylesRuntime.getTheme();
@@ -257,7 +259,7 @@ function FilePreviewBody({
});
}, 0);
return () => clearTimeout(timeout);
}, [lineHeight, lineSelection]);
}, [lineHeight, lineSelection, navigationRevision]);
if (isLoading && !preview) {
return (
@@ -380,10 +382,12 @@ export function FilePane({
serverId,
workspaceRoot,
location,
navigationRevision,
}: {
serverId: string;
workspaceRoot: string;
location: WorkspaceFileLocation;
navigationRevision: number;
}) {
const { t } = useTranslation();
const isMobile = useIsCompactFormFactor();
@@ -476,6 +480,7 @@ export function FilePane({
isLoading={query.isFetching}
isMobile={isMobile}
location={location}
navigationRevision={navigationRevision}
imagePreviewUri={imagePreviewUri}
/>
);
@@ -518,6 +523,7 @@ function FilePanePresentation({
isLoading,
isMobile,
location,
navigationRevision,
imagePreviewUri,
}: {
serverId: string;
@@ -535,6 +541,7 @@ function FilePanePresentation({
isLoading: boolean;
isMobile: boolean;
location: WorkspaceFileLocation;
navigationRevision: number;
imagePreviewUri: string | null;
}) {
if (!client && readTarget) {
@@ -562,6 +569,7 @@ function FilePanePresentation({
isLoading={isLoading}
isMobile={isMobile}
location={location}
navigationRevision={navigationRevision}
/>
);
}
@@ -587,6 +595,7 @@ function FilePanePresentation({
isLoading={isLoading}
isMobile={isMobile}
location={location}
navigationRevision={navigationRevision}
imagePreviewUri={imagePreviewUri}
/>
</View>
@@ -605,6 +614,7 @@ function EditableFilePane({
isLoading,
isMobile,
location,
navigationRevision,
}: {
client: DaemonClient;
cwd: string;
@@ -617,6 +627,7 @@ function EditableFilePane({
isLoading: boolean;
isMobile: boolean;
location: WorkspaceFileLocation;
navigationRevision: number;
}) {
const { settings } = useAppSettings();
const { t } = useTranslation();
@@ -742,6 +753,8 @@ function EditableFilePane({
<FileEditorView
model={model}
filename={filename}
location={location}
navigationRevision={navigationRevision}
vimEnabled={settings.vimKeybindings}
theme={visualTheme}
onCursorChange={setCursor}
@@ -753,6 +766,7 @@ function EditableFilePane({
isLoading={isLoading}
isMobile={isMobile}
location={location}
navigationRevision={navigationRevision}
imagePreviewUri={null}
/>
)}