From c0eebe6731fdbac57a9fc5225d3c981f2c93a72a Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sat, 7 Mar 2026 23:24:53 -0800 Subject: [PATCH 01/21] Make diff pane selectable and jump to raw edit --- .../LightDiffViewer/LightDiffViewer.tsx | 26 ++++ .../TabView/FileViewerPane/FileViewerPane.tsx | 13 +- .../DiffViewerContextMenu.tsx | 139 +++++++++++++++++ .../components/DiffViewerContextMenu/index.ts | 1 + .../FileViewerContent/FileViewerContent.tsx | 143 ++++++++++++++++-- .../utils/diff-location.test.ts | 87 +++++++++++ .../FileViewerContent/utils/diff-location.ts | 123 +++++++++++++++ .../desktop/src/renderer/stores/tabs/store.ts | 18 ++- 8 files changed, 534 insertions(+), 16 deletions(-) create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/index.ts create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/LightDiffViewer/LightDiffViewer.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/LightDiffViewer/LightDiffViewer.tsx index aee39018232..053aa8f22b6 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/LightDiffViewer/LightDiffViewer.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/LightDiffViewer/LightDiffViewer.tsx @@ -8,6 +8,20 @@ import { } from "renderer/screens/main/components/WorkspaceView/utils/code-theme"; import type { DiffViewMode, FileContents } from "shared/changes-types"; +interface DiffLinePointerEvent { + lineNumber: number; + annotationSide: "deletions" | "additions"; + lineType: + | "change-deletion" + | "change-addition" + | "context" + | "context-expanded"; + lineElement: HTMLElement; + numberElement: HTMLElement | undefined; + numberColumn: boolean; + event: PointerEvent; +} + interface LightDiffViewerProps { contents: FileContents; viewMode: DiffViewMode; @@ -15,6 +29,8 @@ interface LightDiffViewerProps { filePath: string; className?: string; style?: CSSProperties; + onDiffLineClick?: (event: DiffLinePointerEvent) => void; + onDiffLineEnter?: (event: DiffLinePointerEvent) => void; } export function LightDiffViewer({ @@ -24,6 +40,8 @@ export function LightDiffViewer({ filePath, className, style, + onDiffLineClick, + onDiffLineEnter, }: LightDiffViewerProps) { const { data: fontSettings } = electronTrpc.settings.getFontSettings.useQuery( undefined, @@ -61,6 +79,14 @@ export function LightDiffViewer({ themeType: "dark", overflow: "wrap", disableFileHeader: true, + unsafeCSS: ` + * { + user-select: text; + -webkit-user-select: text; + } + `, + onLineClick: onDiffLineClick, + onLineEnter: onDiffLineEnter, }} /> ); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx index d46a6061abf..6756aed7b31 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx @@ -173,7 +173,10 @@ export function FileViewerPane({ pinPane(paneId); }; - const switchToMode = (newMode: FileViewerMode) => { + const switchToMode = ( + newMode: FileViewerMode, + location?: { line?: number; column?: number }, + ) => { const panes = useTabsStore.getState().panes; const currentPane = panes[paneId]; if (currentPane?.fileViewer) { @@ -185,6 +188,9 @@ export function FileViewerPane({ fileViewer: { ...currentPane.fileViewer, viewMode: newMode, + initialLine: location?.line ?? currentPane.fileViewer.initialLine, + initialColumn: + location?.column ?? currentPane.fileViewer.initialColumn, }, }, }, @@ -192,6 +198,10 @@ export function FileViewerPane({ } }; + const handleSwitchToRawAtLocation = (line: number, column: number) => { + switchToMode("raw", { line, column }); + }; + const handleViewModeChange = (value: string) => { if (!value) return; const newMode = value as FileViewerMode; @@ -300,6 +310,7 @@ export function FileViewerPane({ onSaveRaw={handleSaveRaw} onEditorChange={handleEditorChange} setIsDirty={setIsDirty} + onSwitchToRawAtLocation={handleSwitchToRawAtLocation} // Context menu props onSplitHorizontal={() => splitPaneHorizontal(tabId, paneId, path)} onSplitVertical={() => splitPaneVertical(tabId, paneId, path)} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx new file mode 100644 index 00000000000..896a21f186e --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx @@ -0,0 +1,139 @@ +import { toast } from "@superset/ui/sonner"; +import { type MutableRefObject, type ReactNode, useCallback } from "react"; +import type { Tab } from "renderer/stores/tabs/types"; +import { + type CodeEditorAdapter, + EditorContextMenu, + type EditorSelectionLines, + useEditorActions, +} from "../../../../../components"; + +interface DiffViewerContextMenuProps { + children: ReactNode; + containerRef: MutableRefObject; + filePath: string; + getSelectionLines: () => EditorSelectionLines | null; + onSplitHorizontal: () => void; + onSplitVertical: () => void; + onSplitWithNewChat?: () => void; + onSplitWithNewBrowser?: () => void; + onClosePane: () => void; + currentTabId: string; + availableTabs: Tab[]; + onMoveToTab: (tabId: string) => void; + onMoveToNewTab: () => void; +} + +function isSelectionInsideContainer( + container: HTMLDivElement, + selection: Selection, +): boolean { + if (selection.rangeCount === 0) { + return false; + } + + for (let index = 0; index < selection.rangeCount; index += 1) { + const range = selection.getRangeAt(index); + if (!container.contains(range.commonAncestorContainer)) { + return false; + } + } + + return true; +} + +export function DiffViewerContextMenu({ + children, + containerRef, + filePath, + getSelectionLines, + onSplitHorizontal, + onSplitVertical, + onSplitWithNewChat, + onSplitWithNewBrowser, + onClosePane, + currentTabId, + availableTabs, + onMoveToTab, + onMoveToNewTab, +}: DiffViewerContextMenuProps) { + const getEditor = useCallback((): CodeEditorAdapter | null => { + const container = containerRef.current; + if (!container) { + return null; + } + + return { + focus() { + container.focus(); + }, + getValue() { + return container.innerText; + }, + setValue(_value: string) {}, + revealPosition(_line: number, _column?: number) {}, + getSelectionLines, + selectAll() { + const selection = window.getSelection(); + if (!selection) return; + const range = document.createRange(); + range.selectNodeContents(container); + selection.removeAllRanges(); + selection.addRange(range); + }, + cut() {}, + copy() { + const selection = window.getSelection(); + if (!selection || !isSelectionInsideContainer(container, selection)) { + return; + } + + const selectedText = selection.toString(); + if (!selectedText) { + return; + } + + void navigator.clipboard.writeText(selectedText).catch((error) => { + console.error( + "[DiffViewerContextMenu] Failed to copy selection:", + error, + ); + toast.error("Failed to copy selection", { + description: String(error), + }); + }); + }, + paste() {}, + openFind() {}, + dispose() {}, + }; + }, [containerRef, getSelectionLines]); + + const editorActions = useEditorActions({ + getEditor, + filePath, + editable: false, + }); + + return ( + + {children} + + ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/index.ts new file mode 100644 index 00000000000..10518fdbde1 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/index.ts @@ -0,0 +1 @@ +export { DiffViewerContextMenu } from "./DiffViewerContextMenu"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index 51ab641849d..8a9b06daa49 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -14,8 +14,13 @@ import type { DiffViewMode } from "shared/changes-types"; import { detectLanguage } from "shared/detect-language"; import { isImageFile } from "shared/file-types"; import type { FileViewerMode } from "shared/tabs-types"; +import { DiffViewerContextMenu } from "../DiffViewerContextMenu"; import { FileEditorContextMenu } from "../FileEditorContextMenu"; import { MarkdownSearch } from "../MarkdownSearch"; +import { + getColumnFromDiffSelection, + mapDiffLocationToRawPosition, +} from "./utils/diff-location"; interface RawFileData { ok: true; @@ -77,6 +82,7 @@ interface FileViewerContentProps { onSaveRaw: () => Promise; onEditorChange: (value: string | undefined) => void; setIsDirty: (dirty: boolean) => void; + onSwitchToRawAtLocation: (line: number, column: number) => void; onSplitHorizontal: () => void; onSplitVertical: () => void; onSplitWithNewChat?: () => void; @@ -120,6 +126,7 @@ export function FileViewerContent({ onSaveRaw, onEditorChange, setIsDirty, + onSwitchToRawAtLocation, onSplitHorizontal, onSplitVertical, onSplitWithNewChat, @@ -134,6 +141,16 @@ export function FileViewerContent({ }: FileViewerContentProps) { const isImage = isImageFile(filePath); const hasAppliedInitialLocationRef = useRef(false); + const diffContainerRef = useRef(null); + const lastDiffLocationRef = useRef<{ + lineNumber: number; + side: "deletions" | "additions"; + lineType: + | "change-deletion" + | "change-addition" + | "context" + | "context-expanded"; + } | null>(null); // biome-ignore lint/correctness/useExhaustiveDependencies: Reset on file change only useEffect(() => { @@ -145,6 +162,92 @@ export function FileViewerContent({ hasAppliedInitialLocationRef.current = false; }, [initialLine, initialColumn]); + // biome-ignore lint/correctness/useExhaustiveDependencies: Reset cached diff interaction when the file changes + useEffect(() => { + lastDiffLocationRef.current = null; + }, [filePath]); + + const getDiffSelectionLines = () => { + if (!diffData || !lastDiffLocationRef.current) { + return null; + } + + const position = mapDiffLocationToRawPosition({ + contents: diffData, + lineNumber: lastDiffLocationRef.current.lineNumber, + side: lastDiffLocationRef.current.side, + lineType: lastDiffLocationRef.current.lineType, + }); + + return { + startLine: position.lineNumber, + endLine: position.lineNumber, + }; + }; + + const handleDiffLineClick = ({ + lineNumber, + annotationSide, + lineType, + lineElement, + numberColumn, + event, + }: { + lineNumber: number; + annotationSide: "deletions" | "additions"; + lineType: + | "change-deletion" + | "change-addition" + | "context" + | "context-expanded"; + lineElement: HTMLElement; + numberColumn: boolean; + event: PointerEvent; + }) => { + lastDiffLocationRef.current = { + lineNumber, + side: annotationSide, + lineType, + }; + + if (event.detail !== 2 || !diffData) { + return; + } + + const position = mapDiffLocationToRawPosition({ + contents: diffData, + lineNumber, + side: annotationSide, + lineType, + column: getColumnFromDiffSelection({ + lineElement, + numberColumn, + }), + }); + + onSwitchToRawAtLocation(position.lineNumber, position.column); + }; + + const handleDiffLineEnter = ({ + lineNumber, + annotationSide, + lineType, + }: { + lineNumber: number; + annotationSide: "deletions" | "additions"; + lineType: + | "change-deletion" + | "change-addition" + | "context" + | "context-expanded"; + }) => { + lastDiffLocationRef.current = { + lineNumber, + side: annotationSide, + lineType, + }; + }; + useEffect(() => { if (viewMode !== "raw") return; if (isLoadingRaw) return; @@ -203,16 +306,36 @@ export function FileViewerContent({ } return ( -
- -
+ +
+ +
+
); } diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts new file mode 100644 index 00000000000..70e048a9ab7 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts @@ -0,0 +1,87 @@ +import { describe, expect, it } from "bun:test"; +import type { FileContents } from "shared/changes-types"; +import { mapDiffLocationToRawPosition } from "./diff-location"; + +describe("mapDiffLocationToRawPosition", () => { + it("keeps addition-side clicks on the same modified line", () => { + const contents: FileContents = { + original: "one\ntwo\nthree", + modified: "one\ntwo changed\nthree", + language: "text", + }; + + expect( + mapDiffLocationToRawPosition({ + contents, + lineNumber: 2, + side: "additions", + lineType: "change-addition", + column: 5, + }), + ).toEqual({ + lineNumber: 2, + column: 5, + }); + }); + + it("maps deletion-side clicks to the replacement start line", () => { + const contents: FileContents = { + original: "one\ntwo\nthree", + modified: "one\ntwo changed\nthree", + language: "text", + }; + + expect( + mapDiffLocationToRawPosition({ + contents, + lineNumber: 2, + side: "deletions", + lineType: "change-deletion", + }), + ).toEqual({ + lineNumber: 2, + column: 1, + }); + }); + + it("maps pure deletions to the insertion point in the modified file", () => { + const contents: FileContents = { + original: "one\ntwo\nthree\nfour", + modified: "one\nfour", + language: "text", + }; + + expect( + mapDiffLocationToRawPosition({ + contents, + lineNumber: 2, + side: "deletions", + lineType: "change-deletion", + }), + ).toEqual({ + lineNumber: 2, + column: 1, + }); + }); + + it("clamps columns to the target line length", () => { + const contents: FileContents = { + original: "alpha", + modified: "beta", + language: "text", + }; + + expect( + mapDiffLocationToRawPosition({ + contents, + lineNumber: 1, + side: "additions", + lineType: "change-addition", + column: 99, + }), + ).toEqual({ + lineNumber: 1, + column: 5, + }); + }); +}); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts new file mode 100644 index 00000000000..12c5152def5 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts @@ -0,0 +1,123 @@ +import { parseDiffFromFile } from "@pierre/diffs"; +import type { AnnotationSide, LineTypes } from "@pierre/diffs/react"; +import type { FileContents } from "shared/changes-types"; + +interface MapDiffLocationToRawPositionOptions { + contents: FileContents; + lineNumber: number; + side: AnnotationSide; + lineType: LineTypes; + column?: number; +} + +interface DiffClickColumnOptions { + lineElement: HTMLElement; + numberColumn?: boolean; +} + +export interface RawEditorPosition { + lineNumber: number; + column: number; +} + +function clampLineNumber(lineNumber: number, modifiedLines: string[]): number { + if (modifiedLines.length === 0) return 1; + return Math.max(1, Math.min(lineNumber, modifiedLines.length)); +} + +function clampColumn( + lineNumber: number, + column: number | undefined, + modifiedLines: string[], +): number { + const safeLineNumber = clampLineNumber(lineNumber, modifiedLines); + const lineContent = modifiedLines[safeLineNumber - 1] ?? ""; + const requestedColumn = column ?? 1; + + return Math.max(1, Math.min(requestedColumn, lineContent.length + 1)); +} + +function mapDeletedLineToRawLine( + contents: FileContents, + lineNumber: number, +): number { + const modifiedLines = contents.modified.split("\n"); + const diff = parseDiffFromFile( + { name: "before", contents: contents.original }, + { name: "after", contents: contents.modified }, + ); + + for (const hunk of diff.hunks) { + let currentOldLine = hunk.deletionStart; + let currentNewLine = hunk.additionStart; + + for (const chunk of hunk.hunkContent) { + if (chunk.type === "context") { + currentOldLine += chunk.lines.length; + currentNewLine += chunk.lines.length; + continue; + } + + const insertionLine = clampLineNumber(currentNewLine, modifiedLines); + + for (let index = 0; index < chunk.deletions.length; index += 1) { + if (currentOldLine === lineNumber) { + return insertionLine; + } + currentOldLine += 1; + } + + currentNewLine += chunk.additions.length; + } + } + + return clampLineNumber(lineNumber, modifiedLines); +} + +export function mapDiffLocationToRawPosition({ + contents, + lineNumber, + side, + lineType, + column, +}: MapDiffLocationToRawPositionOptions): RawEditorPosition { + const modifiedLines = contents.modified.split("\n"); + + const rawLineNumber = + lineType === "context" || + lineType === "context-expanded" || + side === "additions" + ? clampLineNumber(lineNumber, modifiedLines) + : mapDeletedLineToRawLine(contents, lineNumber); + + return { + lineNumber: rawLineNumber, + column: clampColumn(rawLineNumber, column, modifiedLines), + }; +} + +export function getColumnFromDiffSelection({ + lineElement, + numberColumn = false, +}: DiffClickColumnOptions): number { + if (numberColumn) { + return 1; + } + + const selection = window.getSelection(); + if (!selection || selection.rangeCount === 0) { + return 1; + } + + const range = selection.getRangeAt(0); + const anchorNode = range.startContainer; + if (!lineElement.contains(anchorNode)) { + return 1; + } + + const measureRange = document.createRange(); + measureRange.selectNodeContents(lineElement); + measureRange.setEnd(anchorNode, range.startOffset); + + return Math.max(1, measureRange.toString().length + 1); +} diff --git a/apps/desktop/src/renderer/stores/tabs/store.ts b/apps/desktop/src/renderer/stores/tabs/store.ts index 003a92b83ed..11b560fc495 100644 --- a/apps/desktop/src/renderer/stores/tabs/store.ts +++ b/apps/desktop/src/renderer/stores/tabs/store.ts @@ -710,10 +710,14 @@ export const useTabsStore = create()( existingFileViewer.commitHash === options.commitHash; if (isSameFile) { - if ( - options.viewMode && - existingFileViewer.viewMode !== options.viewMode - ) { + const nextViewMode = + options.viewMode ?? existingFileViewer.viewMode; + const shouldUpdateViewerState = + nextViewMode !== existingFileViewer.viewMode || + options.line !== undefined || + options.column !== undefined; + + if (shouldUpdateViewerState) { set({ panes: { ...state.panes, @@ -721,7 +725,11 @@ export const useTabsStore = create()( ...paneToReuse, fileViewer: { ...existingFileViewer, - viewMode: options.viewMode, + viewMode: nextViewMode, + initialLine: + options.line ?? existingFileViewer.initialLine, + initialColumn: + options.column ?? existingFileViewer.initialColumn, }, }, }, From 04845ee9fb491be41e5d2d8a371c23224bad98fa Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 10:46:38 -0700 Subject: [PATCH 02/21] Preserve diff selection after mouse release --- .../FileViewerContent/FileViewerContent.tsx | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index 8a9b06daa49..cc9f0430096 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -63,6 +63,28 @@ interface DiffData { language: string; } +function hasActiveSelectionWithinElement( + element: HTMLDivElement | null, +): boolean { + if (!element) { + return false; + } + + const selection = window.getSelection(); + if (!selection || selection.isCollapsed || selection.rangeCount === 0) { + return false; + } + + for (let index = 0; index < selection.rangeCount; index += 1) { + const range = selection.getRangeAt(index); + if (element.contains(range.commonAncestorContainer)) { + return true; + } + } + + return false; +} + interface FileViewerContentProps { viewMode: FileViewerMode; filePath: string; @@ -320,9 +342,15 @@ export function FileViewerContent({ onMoveToTab={onMoveToTab} onMoveToNewTab={onMoveToNewTab} > + {/* biome-ignore lint/a11y/useKeyWithClickEvents lint/a11y/noStaticElementInteractions: Diff wrapper intercepts mouse clicks to preserve text selection */}
{ + if (hasActiveSelectionWithinElement(diffContainerRef.current)) { + event.stopPropagation(); + } + }} > Date: Sun, 8 Mar 2026 10:52:26 -0700 Subject: [PATCH 03/21] Handle diff double click without line click hooks --- .../LightDiffViewer/LightDiffViewer.tsx | 27 ++---- .../FileViewerContent/FileViewerContent.tsx | 86 +++++++++---------- .../FileViewerContent/utils/diff-location.ts | 61 +++++++++++++ 3 files changed, 112 insertions(+), 62 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/LightDiffViewer/LightDiffViewer.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/LightDiffViewer/LightDiffViewer.tsx index 053aa8f22b6..a70243ae63b 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/LightDiffViewer/LightDiffViewer.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/LightDiffViewer/LightDiffViewer.tsx @@ -8,20 +8,6 @@ import { } from "renderer/screens/main/components/WorkspaceView/utils/code-theme"; import type { DiffViewMode, FileContents } from "shared/changes-types"; -interface DiffLinePointerEvent { - lineNumber: number; - annotationSide: "deletions" | "additions"; - lineType: - | "change-deletion" - | "change-addition" - | "context" - | "context-expanded"; - lineElement: HTMLElement; - numberElement: HTMLElement | undefined; - numberColumn: boolean; - event: PointerEvent; -} - interface LightDiffViewerProps { contents: FileContents; viewMode: DiffViewMode; @@ -29,8 +15,15 @@ interface LightDiffViewerProps { filePath: string; className?: string; style?: CSSProperties; - onDiffLineClick?: (event: DiffLinePointerEvent) => void; - onDiffLineEnter?: (event: DiffLinePointerEvent) => void; + onDiffLineEnter?: (event: { + lineNumber: number; + annotationSide: "deletions" | "additions"; + lineType: + | "change-deletion" + | "change-addition" + | "context" + | "context-expanded"; + }) => void; } export function LightDiffViewer({ @@ -40,7 +33,6 @@ export function LightDiffViewer({ filePath, className, style, - onDiffLineClick, onDiffLineEnter, }: LightDiffViewerProps) { const { data: fontSettings } = electronTrpc.settings.getFontSettings.useQuery( @@ -85,7 +77,6 @@ export function LightDiffViewer({ -webkit-user-select: text; } `, - onLineClick: onDiffLineClick, onLineEnter: onDiffLineEnter, }} /> diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index cc9f0430096..d5023e78bf3 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -19,6 +19,7 @@ import { FileEditorContextMenu } from "../FileEditorContextMenu"; import { MarkdownSearch } from "../MarkdownSearch"; import { getColumnFromDiffSelection, + getDiffLocationFromTarget, mapDiffLocationToRawPosition, } from "./utils/diff-location"; @@ -207,49 +208,6 @@ export function FileViewerContent({ }; }; - const handleDiffLineClick = ({ - lineNumber, - annotationSide, - lineType, - lineElement, - numberColumn, - event, - }: { - lineNumber: number; - annotationSide: "deletions" | "additions"; - lineType: - | "change-deletion" - | "change-addition" - | "context" - | "context-expanded"; - lineElement: HTMLElement; - numberColumn: boolean; - event: PointerEvent; - }) => { - lastDiffLocationRef.current = { - lineNumber, - side: annotationSide, - lineType, - }; - - if (event.detail !== 2 || !diffData) { - return; - } - - const position = mapDiffLocationToRawPosition({ - contents: diffData, - lineNumber, - side: annotationSide, - lineType, - column: getColumnFromDiffSelection({ - lineElement, - numberColumn, - }), - }); - - onSwitchToRawAtLocation(position.lineNumber, position.column); - }; - const handleDiffLineEnter = ({ lineNumber, annotationSide, @@ -346,6 +304,47 @@ export function FileViewerContent({
{ + const location = getDiffLocationFromTarget(event.target); + if (!location) { + return; + } + + lastDiffLocationRef.current = { + lineNumber: location.lineNumber, + side: location.side, + lineType: location.lineType, + }; + }} + onDoubleClick={(event) => { + if (!diffData) { + return; + } + + const location = getDiffLocationFromTarget(event.target); + if (!location) { + return; + } + + lastDiffLocationRef.current = { + lineNumber: location.lineNumber, + side: location.side, + lineType: location.lineType, + }; + + const position = mapDiffLocationToRawPosition({ + contents: diffData, + lineNumber: location.lineNumber, + side: location.side, + lineType: location.lineType, + column: getColumnFromDiffSelection({ + lineElement: location.lineElement, + numberColumn: location.numberColumn, + }), + }); + + onSwitchToRawAtLocation(position.lineNumber, position.column); + }} onClick={(event) => { if (hasActiveSelectionWithinElement(diffContainerRef.current)) { event.stopPropagation(); @@ -359,7 +358,6 @@ export function FileViewerContent({ hideUnchangedRegions={hideUnchangedRegions} filePath={filePath} className="min-h-full" - onDiffLineClick={handleDiffLineClick} onDiffLineEnter={handleDiffLineEnter} />
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts index 12c5152def5..eef9b7881aa 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts @@ -15,11 +15,28 @@ interface DiffClickColumnOptions { numberColumn?: boolean; } +export interface DiffDomLocation { + lineElement: HTMLElement; + lineNumber: number; + side: AnnotationSide; + lineType: LineTypes; + numberColumn: boolean; +} + export interface RawEditorPosition { lineNumber: number; column: number; } +function isSupportedLineType(lineType: string): lineType is LineTypes { + return ( + lineType === "context" || + lineType === "context-expanded" || + lineType === "change-deletion" || + lineType === "change-addition" + ); +} + function clampLineNumber(lineNumber: number, modifiedLines: string[]): number { if (modifiedLines.length === 0) return 1; return Math.max(1, Math.min(lineNumber, modifiedLines.length)); @@ -121,3 +138,47 @@ export function getColumnFromDiffSelection({ return Math.max(1, measureRange.toString().length + 1); } + +export function getDiffLocationFromTarget( + target: EventTarget | null, +): DiffDomLocation | null { + if (!(target instanceof Node)) { + return null; + } + + const targetElement = + target instanceof HTMLElement ? target : target.parentElement; + const lineElement = targetElement?.closest("[data-line]"); + if (!(lineElement instanceof HTMLElement)) { + return null; + } + + const rawLineNumber = Number.parseInt(lineElement.dataset.line ?? "", 10); + const lineType = lineElement.dataset.lineType; + if ( + !Number.isFinite(rawLineNumber) || + !lineType || + !isSupportedLineType(lineType) + ) { + return null; + } + + const numberColumn = !!targetElement?.closest("[data-column-number]"); + const parentCode = lineElement.closest("[data-code]"); + const side: AnnotationSide = + lineType === "change-deletion" + ? "deletions" + : lineType === "change-addition" + ? "additions" + : parentCode instanceof HTMLElement && "deletions" in parentCode.dataset + ? "deletions" + : "additions"; + + return { + lineElement, + lineNumber: rawLineNumber, + side, + lineType, + numberColumn, + }; +} From 5daa4ca4ce46023c5e03b8be15c877ef18f43de8 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 10:55:08 -0700 Subject: [PATCH 04/21] Add diff selection debug logging --- .../FileViewerContent/FileViewerContent.tsx | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index d5023e78bf3..bb041e05649 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -64,6 +64,44 @@ interface DiffData { language: string; } +function getSelectionDebugState(element: HTMLDivElement | null) { + const selection = window.getSelection(); + const text = selection?.toString() ?? ""; + const range = + selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null; + + return { + hasSelection: + !!selection && !selection.isCollapsed && selection.rangeCount > 0, + isCollapsed: selection?.isCollapsed ?? true, + rangeCount: selection?.rangeCount ?? 0, + textLength: text.length, + textPreview: text.slice(0, 80), + insideDiffContainer: + !!element && !!range && element.contains(range.commonAncestorContainer), + commonAncestor: + range?.commonAncestorContainer instanceof HTMLElement + ? { + tagName: range.commonAncestorContainer.tagName, + className: range.commonAncestorContainer.className, + dataset: { ...range.commonAncestorContainer.dataset }, + } + : range?.commonAncestorContainer.nodeName, + }; +} + +function logDiffSelectionDebug( + element: HTMLDivElement | null, + label: string, + extra?: Record, +) { + console.log("[DiffSelectionDebug]", { + label, + ...getSelectionDebugState(element), + ...extra, + }); +} + function hasActiveSelectionWithinElement( element: HTMLDivElement | null, ): boolean { @@ -190,6 +228,21 @@ export function FileViewerContent({ lastDiffLocationRef.current = null; }, [filePath]); + useEffect(() => { + if (viewMode !== "diff") { + return; + } + + const handleSelectionChange = () => { + logDiffSelectionDebug(diffContainerRef.current, "selectionchange"); + }; + + document.addEventListener("selectionchange", handleSelectionChange); + return () => { + document.removeEventListener("selectionchange", handleSelectionChange); + }; + }, [viewMode]); + const getDiffSelectionLines = () => { if (!diffData || !lastDiffLocationRef.current) { return null; @@ -304,7 +357,54 @@ export function FileViewerContent({
{ + logDiffSelectionDebug(diffContainerRef.current, "mousedown", { + target: + event.target instanceof HTMLElement + ? { + tagName: event.target.tagName, + className: event.target.className, + dataset: { ...event.target.dataset }, + } + : undefined, + }); + }} + onMouseUpCapture={(event) => { + logDiffSelectionDebug(diffContainerRef.current, "mouseup", { + target: + event.target instanceof HTMLElement + ? { + tagName: event.target.tagName, + className: event.target.className, + dataset: { ...event.target.dataset }, + } + : undefined, + }); + }} + onClickCapture={(event) => { + logDiffSelectionDebug(diffContainerRef.current, "click-capture", { + detail: event.detail, + target: + event.target instanceof HTMLElement + ? { + tagName: event.target.tagName, + className: event.target.className, + dataset: { ...event.target.dataset }, + } + : undefined, + }); + }} onContextMenuCapture={(event) => { + logDiffSelectionDebug(diffContainerRef.current, "contextmenu", { + target: + event.target instanceof HTMLElement + ? { + tagName: event.target.tagName, + className: event.target.className, + dataset: { ...event.target.dataset }, + } + : undefined, + }); const location = getDiffLocationFromTarget(event.target); if (!location) { return; @@ -317,6 +417,16 @@ export function FileViewerContent({ }; }} onDoubleClick={(event) => { + logDiffSelectionDebug(diffContainerRef.current, "double-click", { + target: + event.target instanceof HTMLElement + ? { + tagName: event.target.tagName, + className: event.target.className, + dataset: { ...event.target.dataset }, + } + : undefined, + }); if (!diffData) { return; } @@ -346,7 +456,13 @@ export function FileViewerContent({ onSwitchToRawAtLocation(position.lineNumber, position.column); }} onClick={(event) => { + logDiffSelectionDebug(diffContainerRef.current, "click-bubble", { + detail: event.detail, + }); if (hasActiveSelectionWithinElement(diffContainerRef.current)) { + console.log("[DiffSelectionDebug]", { + label: "stop-click-propagation", + }); event.stopPropagation(); } }} From ac91b351e28b21fb01be7386257f7e1f17beff03 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 10:56:51 -0700 Subject: [PATCH 05/21] Detect diff selections by selected text --- .../FileViewerContent/FileViewerContent.tsx | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index bb041e05649..895a9edab2f 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -69,16 +69,17 @@ function getSelectionDebugState(element: HTMLDivElement | null) { const text = selection?.toString() ?? ""; const range = selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null; + const insideDiffContainer = + !!element && !!range && element.contains(range.commonAncestorContainer); + const hasTextSelection = text.length > 0; return { - hasSelection: - !!selection && !selection.isCollapsed && selection.rangeCount > 0, + hasSelection: hasTextSelection && insideDiffContainer, isCollapsed: selection?.isCollapsed ?? true, rangeCount: selection?.rangeCount ?? 0, textLength: text.length, textPreview: text.slice(0, 80), - insideDiffContainer: - !!element && !!range && element.contains(range.commonAncestorContainer), + insideDiffContainer, commonAncestor: range?.commonAncestorContainer instanceof HTMLElement ? { @@ -110,7 +111,12 @@ function hasActiveSelectionWithinElement( } const selection = window.getSelection(); - if (!selection || selection.isCollapsed || selection.rangeCount === 0) { + if (!selection || selection.rangeCount === 0) { + return false; + } + + const text = selection.toString(); + if (text.length === 0) { return false; } @@ -393,6 +399,12 @@ export function FileViewerContent({ } : undefined, }); + if (hasActiveSelectionWithinElement(diffContainerRef.current)) { + console.log("[DiffSelectionDebug]", { + label: "stop-click-capture-propagation", + }); + event.stopPropagation(); + } }} onContextMenuCapture={(event) => { logDiffSelectionDebug(diffContainerRef.current, "contextmenu", { @@ -459,12 +471,6 @@ export function FileViewerContent({ logDiffSelectionDebug(diffContainerRef.current, "click-bubble", { detail: event.detail, }); - if (hasActiveSelectionWithinElement(diffContainerRef.current)) { - console.log("[DiffSelectionDebug]", { - label: "stop-click-propagation", - }); - event.stopPropagation(); - } }} > Date: Sun, 8 Mar 2026 10:59:00 -0700 Subject: [PATCH 06/21] Remove diff selection debug instrumentation --- .../FileViewerContent/FileViewerContent.tsx | 119 +----------------- 1 file changed, 1 insertion(+), 118 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index 895a9edab2f..40e394d3bfb 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -64,45 +64,6 @@ interface DiffData { language: string; } -function getSelectionDebugState(element: HTMLDivElement | null) { - const selection = window.getSelection(); - const text = selection?.toString() ?? ""; - const range = - selection && selection.rangeCount > 0 ? selection.getRangeAt(0) : null; - const insideDiffContainer = - !!element && !!range && element.contains(range.commonAncestorContainer); - const hasTextSelection = text.length > 0; - - return { - hasSelection: hasTextSelection && insideDiffContainer, - isCollapsed: selection?.isCollapsed ?? true, - rangeCount: selection?.rangeCount ?? 0, - textLength: text.length, - textPreview: text.slice(0, 80), - insideDiffContainer, - commonAncestor: - range?.commonAncestorContainer instanceof HTMLElement - ? { - tagName: range.commonAncestorContainer.tagName, - className: range.commonAncestorContainer.className, - dataset: { ...range.commonAncestorContainer.dataset }, - } - : range?.commonAncestorContainer.nodeName, - }; -} - -function logDiffSelectionDebug( - element: HTMLDivElement | null, - label: string, - extra?: Record, -) { - console.log("[DiffSelectionDebug]", { - label, - ...getSelectionDebugState(element), - ...extra, - }); -} - function hasActiveSelectionWithinElement( element: HTMLDivElement | null, ): boolean { @@ -234,21 +195,6 @@ export function FileViewerContent({ lastDiffLocationRef.current = null; }, [filePath]); - useEffect(() => { - if (viewMode !== "diff") { - return; - } - - const handleSelectionChange = () => { - logDiffSelectionDebug(diffContainerRef.current, "selectionchange"); - }; - - document.addEventListener("selectionchange", handleSelectionChange); - return () => { - document.removeEventListener("selectionchange", handleSelectionChange); - }; - }, [viewMode]); - const getDiffSelectionLines = () => { if (!diffData || !lastDiffLocationRef.current) { return null; @@ -359,64 +305,16 @@ export function FileViewerContent({ onMoveToTab={onMoveToTab} onMoveToNewTab={onMoveToNewTab} > - {/* biome-ignore lint/a11y/useKeyWithClickEvents lint/a11y/noStaticElementInteractions: Diff wrapper intercepts mouse clicks to preserve text selection */} + {/* biome-ignore lint/a11y/noStaticElementInteractions: diff wrapper intercepts click capture to preserve browser text selection */}
{ - logDiffSelectionDebug(diffContainerRef.current, "mousedown", { - target: - event.target instanceof HTMLElement - ? { - tagName: event.target.tagName, - className: event.target.className, - dataset: { ...event.target.dataset }, - } - : undefined, - }); - }} - onMouseUpCapture={(event) => { - logDiffSelectionDebug(diffContainerRef.current, "mouseup", { - target: - event.target instanceof HTMLElement - ? { - tagName: event.target.tagName, - className: event.target.className, - dataset: { ...event.target.dataset }, - } - : undefined, - }); - }} onClickCapture={(event) => { - logDiffSelectionDebug(diffContainerRef.current, "click-capture", { - detail: event.detail, - target: - event.target instanceof HTMLElement - ? { - tagName: event.target.tagName, - className: event.target.className, - dataset: { ...event.target.dataset }, - } - : undefined, - }); if (hasActiveSelectionWithinElement(diffContainerRef.current)) { - console.log("[DiffSelectionDebug]", { - label: "stop-click-capture-propagation", - }); event.stopPropagation(); } }} onContextMenuCapture={(event) => { - logDiffSelectionDebug(diffContainerRef.current, "contextmenu", { - target: - event.target instanceof HTMLElement - ? { - tagName: event.target.tagName, - className: event.target.className, - dataset: { ...event.target.dataset }, - } - : undefined, - }); const location = getDiffLocationFromTarget(event.target); if (!location) { return; @@ -429,16 +327,6 @@ export function FileViewerContent({ }; }} onDoubleClick={(event) => { - logDiffSelectionDebug(diffContainerRef.current, "double-click", { - target: - event.target instanceof HTMLElement - ? { - tagName: event.target.tagName, - className: event.target.className, - dataset: { ...event.target.dataset }, - } - : undefined, - }); if (!diffData) { return; } @@ -467,11 +355,6 @@ export function FileViewerContent({ onSwitchToRawAtLocation(position.lineNumber, position.column); }} - onClick={(event) => { - logDiffSelectionDebug(diffContainerRef.current, "click-bubble", { - detail: event.detail, - }); - }} > Date: Sun, 8 Mar 2026 11:09:00 -0700 Subject: [PATCH 07/21] Fix diff location edge cases --- .../FileViewerContent/FileViewerContent.tsx | 16 +++++++++++++-- .../utils/diff-location.test.ts | 20 +++++++++++++++++++ .../FileViewerContent/utils/diff-location.ts | 17 +++++++++------- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index 40e394d3bfb..8a7926dbd5a 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -190,10 +190,22 @@ export function FileViewerContent({ hasAppliedInitialLocationRef.current = false; }, [initialLine, initialColumn]); - // biome-ignore lint/correctness/useExhaustiveDependencies: Reset cached diff interaction when the file changes + useEffect(() => { + if (viewMode !== "raw") { + hasAppliedInitialLocationRef.current = false; + } + }, [viewMode]); + + // biome-ignore lint/correctness/useExhaustiveDependencies: Reset cached diff interaction when the rendered diff changes useEffect(() => { lastDiffLocationRef.current = null; - }, [filePath]); + }, [ + filePath, + diffData?.original, + diffData?.modified, + diffViewMode, + hideUnchangedRegions, + ]); const getDiffSelectionLines = () => { if (!diffData || !lastDiffLocationRef.current) { diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts index 70e048a9ab7..49fb674a65f 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts @@ -64,6 +64,26 @@ describe("mapDiffLocationToRawPosition", () => { }); }); + it("maps deletion-side context lines using old-to-new line translation", () => { + const contents: FileContents = { + original: "one\ntwo\nthree\nfour", + modified: "zero\none\ntwo\nthree\nfour", + language: "text", + }; + + expect( + mapDiffLocationToRawPosition({ + contents, + lineNumber: 4, + side: "deletions", + lineType: "context", + }), + ).toEqual({ + lineNumber: 5, + column: 1, + }); + }); + it("clamps columns to the target line length", () => { const contents: FileContents = { original: "alpha", diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts index eef9b7881aa..e22c0512e3a 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts @@ -54,7 +54,7 @@ function clampColumn( return Math.max(1, Math.min(requestedColumn, lineContent.length + 1)); } -function mapDeletedLineToRawLine( +function mapOldSideLineToRawLine( contents: FileContents, lineNumber: number, ): number { @@ -70,8 +70,14 @@ function mapDeletedLineToRawLine( for (const chunk of hunk.hunkContent) { if (chunk.type === "context") { - currentOldLine += chunk.lines.length; - currentNewLine += chunk.lines.length; + for (let index = 0; index < chunk.lines.length; index += 1) { + if (currentOldLine === lineNumber) { + return clampLineNumber(currentNewLine, modifiedLines); + } + + currentOldLine += 1; + currentNewLine += 1; + } continue; } @@ -95,17 +101,14 @@ export function mapDiffLocationToRawPosition({ contents, lineNumber, side, - lineType, column, }: MapDiffLocationToRawPositionOptions): RawEditorPosition { const modifiedLines = contents.modified.split("\n"); const rawLineNumber = - lineType === "context" || - lineType === "context-expanded" || side === "additions" ? clampLineNumber(lineNumber, modifiedLines) - : mapDeletedLineToRawLine(contents, lineNumber); + : mapOldSideLineToRawLine(contents, lineNumber); return { lineNumber: rawLineNumber, From 837e8ceeea40bf795942a0ec1c77bad9633e48c6 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 13:26:54 -0700 Subject: [PATCH 08/21] Fix old-side diff fallback mapping --- .../utils/diff-location.test.ts | 20 +++++++++++++++++++ .../FileViewerContent/utils/diff-location.ts | 9 ++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts index 49fb674a65f..98e928865ec 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts @@ -84,6 +84,26 @@ describe("mapDiffLocationToRawPosition", () => { }); }); + it("maps old-side lines below the final hunk using the cumulative delta", () => { + const contents: FileContents = { + original: "one\ntwo\nthree\nfour\nfive", + modified: "zero\none\ntwo\nthree\nfour\nfive", + language: "text", + }; + + expect( + mapDiffLocationToRawPosition({ + contents, + lineNumber: 5, + side: "deletions", + lineType: "context", + }), + ).toEqual({ + lineNumber: 6, + column: 1, + }); + }); + it("clamps columns to the target line length", () => { const contents: FileContents = { original: "alpha", diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts index e22c0512e3a..1e28829ecf2 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts @@ -63,8 +63,13 @@ function mapOldSideLineToRawLine( { name: "before", contents: contents.original }, { name: "after", contents: contents.modified }, ); + let lineDelta = 0; for (const hunk of diff.hunks) { + if (lineNumber < hunk.deletionStart) { + return clampLineNumber(lineNumber + lineDelta, modifiedLines); + } + let currentOldLine = hunk.deletionStart; let currentNewLine = hunk.additionStart; @@ -92,9 +97,11 @@ function mapOldSideLineToRawLine( currentNewLine += chunk.additions.length; } + + lineDelta = currentNewLine - currentOldLine; } - return clampLineNumber(lineNumber, modifiedLines); + return clampLineNumber(lineNumber + lineDelta, modifiedLines); } export function mapDiffLocationToRawPosition({ From 38a7de1e8c000fa25ccca8ea37688988ced3a43e Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 13:35:11 -0700 Subject: [PATCH 09/21] Add diff context menu edit action --- .../DiffViewerContextMenu.tsx | 10 +++ .../FileViewerContent/FileViewerContent.tsx | 87 ++++++++++++------- .../FileViewerContent/utils/diff-location.ts | 58 ++++++++++++- .../EditorContextMenu/EditorContextMenu.tsx | 9 ++ 4 files changed, 132 insertions(+), 32 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx index 896a21f186e..e1dadd20541 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx @@ -1,5 +1,7 @@ +import { ContextMenuItem } from "@superset/ui/context-menu"; import { toast } from "@superset/ui/sonner"; import { type MutableRefObject, type ReactNode, useCallback } from "react"; +import { LuSquarePen } from "react-icons/lu"; import type { Tab } from "renderer/stores/tabs/types"; import { type CodeEditorAdapter, @@ -22,6 +24,7 @@ interface DiffViewerContextMenuProps { availableTabs: Tab[]; onMoveToTab: (tabId: string) => void; onMoveToNewTab: () => void; + onEditAtLocation: () => void; } function isSelectionInsideContainer( @@ -56,6 +59,7 @@ export function DiffViewerContextMenu({ availableTabs, onMoveToTab, onMoveToNewTab, + onEditAtLocation, }: DiffViewerContextMenuProps) { const getEditor = useCallback((): CodeEditorAdapter | null => { const container = containerRef.current; @@ -121,6 +125,12 @@ export function DiffViewerContextMenu({ ...editorActions, onFind: undefined, }} + extraItems={ + + + Edit Here + + } paneActions={{ onSplitHorizontal, onSplitVertical, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index 8a7926dbd5a..8647c25f2f8 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -18,6 +18,8 @@ import { DiffViewerContextMenu } from "../DiffViewerContextMenu"; import { FileEditorContextMenu } from "../FileEditorContextMenu"; import { MarkdownSearch } from "../MarkdownSearch"; import { + type DiffDomLocation, + getColumnFromDiffPoint, getColumnFromDiffSelection, getDiffLocationFromTarget, mapDiffLocationToRawPosition, @@ -170,15 +172,12 @@ export function FileViewerContent({ const isImage = isImageFile(filePath); const hasAppliedInitialLocationRef = useRef(false); const diffContainerRef = useRef(null); - const lastDiffLocationRef = useRef<{ - lineNumber: number; - side: "deletions" | "additions"; - lineType: - | "change-deletion" - | "change-addition" - | "context" - | "context-expanded"; - } | null>(null); + const lastDiffLocationRef = useRef< + | (DiffDomLocation & { + column?: number; + }) + | null + >(null); // biome-ignore lint/correctness/useExhaustiveDependencies: Reset on file change only useEffect(() => { @@ -225,6 +224,33 @@ export function FileViewerContent({ }; }; + const openRawFromDiffLocation = ( + location: DiffDomLocation & { + column?: number; + }, + ) => { + if (!diffData) { + return; + } + + lastDiffLocationRef.current = location; + + const position = mapDiffLocationToRawPosition({ + contents: diffData, + lineNumber: location.lineNumber, + side: location.side, + lineType: location.lineType, + column: + location.column ?? + getColumnFromDiffSelection({ + lineElement: location.lineElement, + numberColumn: location.numberColumn, + }), + }); + + onSwitchToRawAtLocation(position.lineNumber, position.column); + }; + const handleDiffLineEnter = ({ lineNumber, annotationSide, @@ -239,9 +265,11 @@ export function FileViewerContent({ | "context-expanded"; }) => { lastDiffLocationRef.current = { + lineElement: diffContainerRef.current ?? document.createElement("div"), lineNumber, side: annotationSide, lineType, + numberColumn: false, }; }; @@ -316,6 +344,14 @@ export function FileViewerContent({ availableTabs={availableTabs} onMoveToTab={onMoveToTab} onMoveToNewTab={onMoveToNewTab} + onEditAtLocation={() => { + const location = lastDiffLocationRef.current; + if (!location) { + return; + } + + openRawFromDiffLocation(location); + }} > {/* biome-ignore lint/a11y/noStaticElementInteractions: diff wrapper intercepts click capture to preserve browser text selection */}
{ - if (!diffData) { - return; - } - const location = getDiffLocationFromTarget(event.target); if (!location) { return; } - lastDiffLocationRef.current = { - lineNumber: location.lineNumber, - side: location.side, - lineType: location.lineType, - }; - - const position = mapDiffLocationToRawPosition({ - contents: diffData, - lineNumber: location.lineNumber, - side: location.side, - lineType: location.lineType, - column: getColumnFromDiffSelection({ + openRawFromDiffLocation({ + ...location, + column: getColumnFromDiffPoint({ lineElement: location.lineElement, numberColumn: location.numberColumn, + clientX: event.clientX, + clientY: event.clientY, }), }); - - onSwitchToRawAtLocation(position.lineNumber, position.column); }} > { offsetNode: Node; offset: number } | null; + caretRangeFromPoint?: (x: number, y: number) => Range | null; + }; + const caretPosition = documentWithCaretApi.caretPositionFromPoint?.( + clientX, + clientY, + ); + if (caretPosition && codeElement.contains(caretPosition.offsetNode)) { + const measureRange = document.createRange(); + measureRange.selectNodeContents(codeElement); + measureRange.setEnd(caretPosition.offsetNode, caretPosition.offset); + return Math.max(1, measureRange.toString().length + 1); + } + + const caretRange = documentWithCaretApi.caretRangeFromPoint?.( + clientX, + clientY, + ); + if (caretRange && codeElement.contains(caretRange.startContainer)) { + const measureRange = document.createRange(); + measureRange.selectNodeContents(codeElement); + measureRange.setEnd(caretRange.startContainer, caretRange.startOffset); + return Math.max(1, measureRange.toString().length + 1); + } + + return 1; +} + export function getDiffLocationFromTarget( target: EventTarget | null, ): DiffDomLocation | null { diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/EditorContextMenu/EditorContextMenu.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/EditorContextMenu/EditorContextMenu.tsx index 6a2429b0432..035545cb2e5 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/EditorContextMenu/EditorContextMenu.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/EditorContextMenu/EditorContextMenu.tsx @@ -38,12 +38,14 @@ interface EditorContextMenuProps { children: ReactNode; editorActions: EditorActions; paneActions: PaneActions; + extraItems?: ReactNode; } export function EditorContextMenu({ children, editorActions, paneActions, + extraItems, }: EditorContextMenuProps) { const { data: platform } = electronTrpc.window.getPlatform.useQuery(); const isMac = platform === "darwin"; @@ -114,6 +116,13 @@ export function EditorContextMenu({ )} + {extraItems && ( + <> + + {extraItems} + + )} + From 5ff35efe74c40ef1e86218a7851a7d9079e9c269 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 13:44:02 -0700 Subject: [PATCH 10/21] Highlight raw diff section on edit jump --- .../LightDiffViewer/LightDiffViewer.tsx | 11 -- .../TabView/FileViewerPane/FileViewerPane.tsx | 33 ++++- .../FileViewerContent/FileViewerContent.tsx | 79 ++++++------ .../utils/diff-location.test.ts | 43 ++++++- .../FileViewerContent/utils/diff-location.ts | 119 ++++++++++++++---- .../CodeEditorAdapter/CodeEditorAdapter.ts | 11 +- .../components/CodeEditor/CodeEditor.tsx | 75 ++++++++++- .../desktop/src/renderer/stores/tabs/store.ts | 8 ++ .../desktop/src/renderer/stores/tabs/types.ts | 4 + .../desktop/src/renderer/stores/tabs/utils.ts | 6 + apps/desktop/src/shared/tabs-types.ts | 3 + 11 files changed, 310 insertions(+), 82 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/LightDiffViewer/LightDiffViewer.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/LightDiffViewer/LightDiffViewer.tsx index a70243ae63b..3b64ab44c2a 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/LightDiffViewer/LightDiffViewer.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/LightDiffViewer/LightDiffViewer.tsx @@ -15,15 +15,6 @@ interface LightDiffViewerProps { filePath: string; className?: string; style?: CSSProperties; - onDiffLineEnter?: (event: { - lineNumber: number; - annotationSide: "deletions" | "additions"; - lineType: - | "change-deletion" - | "change-addition" - | "context" - | "context-expanded"; - }) => void; } export function LightDiffViewer({ @@ -33,7 +24,6 @@ export function LightDiffViewer({ filePath, className, style, - onDiffLineEnter, }: LightDiffViewerProps) { const { data: fontSettings } = electronTrpc.settings.getFontSettings.useQuery( undefined, @@ -77,7 +67,6 @@ export function LightDiffViewer({ -webkit-user-select: text; } `, - onLineEnter: onDiffLineEnter, }} /> ); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx index 6756aed7b31..3c051e4af30 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx @@ -85,6 +85,8 @@ export function FileViewerPane({ const oldPath = fileViewer?.oldPath; const initialLine = fileViewer?.initialLine; const initialColumn = fileViewer?.initialColumn; + const initialSelectionStartLine = fileViewer?.initialSelectionStartLine; + const initialSelectionEndLine = fileViewer?.initialSelectionEndLine; const pinPane = useTabsStore((s) => s.pinPane); @@ -175,7 +177,12 @@ export function FileViewerPane({ const switchToMode = ( newMode: FileViewerMode, - location?: { line?: number; column?: number }, + location?: { + line?: number; + column?: number; + selectionStartLine?: number; + selectionEndLine?: number; + }, ) => { const panes = useTabsStore.getState().panes; const currentPane = panes[paneId]; @@ -191,6 +198,12 @@ export function FileViewerPane({ initialLine: location?.line ?? currentPane.fileViewer.initialLine, initialColumn: location?.column ?? currentPane.fileViewer.initialColumn, + initialSelectionStartLine: + location?.selectionStartLine ?? + currentPane.fileViewer.initialSelectionStartLine, + initialSelectionEndLine: + location?.selectionEndLine ?? + currentPane.fileViewer.initialSelectionEndLine, }, }, }, @@ -198,8 +211,20 @@ export function FileViewerPane({ } }; - const handleSwitchToRawAtLocation = (line: number, column: number) => { - switchToMode("raw", { line, column }); + const handleSwitchToRawAtLocation = ( + line: number, + column: number, + selection?: { + startLine: number; + endLine: number; + }, + ) => { + switchToMode("raw", { + line, + column, + selectionStartLine: selection?.startLine, + selectionEndLine: selection?.endLine, + }); }; const handleViewModeChange = (value: string) => { @@ -305,6 +330,8 @@ export function FileViewerPane({ draftContentRef={draftContentRef} initialLine={initialLine} initialColumn={initialColumn} + initialSelectionStartLine={initialSelectionStartLine} + initialSelectionEndLine={initialSelectionEndLine} diffViewMode={diffViewMode} hideUnchangedRegions={hideUnchangedRegions} onSaveRaw={handleSaveRaw} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index 8647c25f2f8..ab54f6f7fcd 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -20,8 +20,8 @@ import { MarkdownSearch } from "../MarkdownSearch"; import { type DiffDomLocation, getColumnFromDiffPoint, - getColumnFromDiffSelection, getDiffLocationFromTarget, + getRawSectionForDiffLocation, mapDiffLocationToRawPosition, } from "./utils/diff-location"; @@ -107,12 +107,21 @@ interface FileViewerContentProps { draftContentRef: MutableRefObject; initialLine?: number; initialColumn?: number; + initialSelectionStartLine?: number; + initialSelectionEndLine?: number; diffViewMode: DiffViewMode; hideUnchangedRegions: boolean; onSaveRaw: () => Promise; onEditorChange: (value: string | undefined) => void; setIsDirty: (dirty: boolean) => void; - onSwitchToRawAtLocation: (line: number, column: number) => void; + onSwitchToRawAtLocation: ( + line: number, + column: number, + selection?: { + startLine: number; + endLine: number; + }, + ) => void; onSplitHorizontal: () => void; onSplitVertical: () => void; onSplitWithNewChat?: () => void; @@ -151,6 +160,8 @@ export function FileViewerContent({ draftContentRef, initialLine, initialColumn, + initialSelectionStartLine, + initialSelectionEndLine, diffViewMode, hideUnchangedRegions, onSaveRaw, @@ -187,7 +198,12 @@ export function FileViewerContent({ // biome-ignore lint/correctness/useExhaustiveDependencies: Reset when requested cursor target changes useEffect(() => { hasAppliedInitialLocationRef.current = false; - }, [initialLine, initialColumn]); + }, [ + initialLine, + initialColumn, + initialSelectionStartLine, + initialSelectionEndLine, + ]); useEffect(() => { if (viewMode !== "raw") { @@ -226,7 +242,7 @@ export function FileViewerContent({ const openRawFromDiffLocation = ( location: DiffDomLocation & { - column?: number; + column: number; }, ) => { if (!diffData) { @@ -240,37 +256,19 @@ export function FileViewerContent({ lineNumber: location.lineNumber, side: location.side, lineType: location.lineType, - column: - location.column ?? - getColumnFromDiffSelection({ - lineElement: location.lineElement, - numberColumn: location.numberColumn, - }), + column: location.column, + }); + const rawSection = getRawSectionForDiffLocation({ + contents: diffData, + lineNumber: location.lineNumber, + side: location.side, }); - onSwitchToRawAtLocation(position.lineNumber, position.column); - }; - - const handleDiffLineEnter = ({ - lineNumber, - annotationSide, - lineType, - }: { - lineNumber: number; - annotationSide: "deletions" | "additions"; - lineType: - | "change-deletion" - | "change-addition" - | "context" - | "context-expanded"; - }) => { - lastDiffLocationRef.current = { - lineElement: diffContainerRef.current ?? document.createElement("div"), - lineNumber, - side: annotationSide, - lineType, - numberColumn: false, - }; + onSwitchToRawAtLocation( + position.lineNumber, + position.column, + rawSection ?? undefined, + ); }; useEffect(() => { @@ -302,13 +300,18 @@ export function FileViewerContent({ return; } - editorRef.current.revealPosition(initialLine, initialColumn ?? 1); + editorRef.current.revealPosition(initialLine, initialColumn ?? 1, { + startLine: initialSelectionStartLine ?? initialLine, + endLine: initialSelectionEndLine ?? initialLine, + }); hasAppliedInitialLocationRef.current = true; }, [ viewMode, editorRef, initialLine, initialColumn, + initialSelectionStartLine, + initialSelectionEndLine, isLoadingRaw, rawFileData, ]); @@ -346,11 +349,14 @@ export function FileViewerContent({ onMoveToNewTab={onMoveToNewTab} onEditAtLocation={() => { const location = lastDiffLocationRef.current; - if (!location) { + if (!location || location.column === undefined) { return; } - openRawFromDiffLocation(location); + openRawFromDiffLocation({ + ...location, + column: location.column, + }); }} > {/* biome-ignore lint/a11y/noStaticElementInteractions: diff wrapper intercepts click capture to preserve browser text selection */} @@ -402,7 +408,6 @@ export function FileViewerContent({ hideUnchangedRegions={hideUnchangedRegions} filePath={filePath} className="min-h-full" - onDiffLineEnter={handleDiffLineEnter} />
diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts index 98e928865ec..918f15f7770 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from "bun:test"; import type { FileContents } from "shared/changes-types"; -import { mapDiffLocationToRawPosition } from "./diff-location"; +import { + getRawSectionForDiffLocation, + mapDiffLocationToRawPosition, +} from "./diff-location"; describe("mapDiffLocationToRawPosition", () => { it("keeps addition-side clicks on the same modified line", () => { @@ -124,4 +127,42 @@ describe("mapDiffLocationToRawPosition", () => { column: 5, }); }); + + it("returns the modified hunk range for addition-side clicks", () => { + const contents: FileContents = { + original: "one\ntwo\nthree\nfour", + modified: "one\ntwo changed\nthree\nfour\nfive", + language: "text", + }; + + expect( + getRawSectionForDiffLocation({ + contents, + lineNumber: 2, + side: "additions", + }), + ).toEqual({ + startLine: 1, + endLine: 5, + }); + }); + + it("returns the modified hunk range for deletion-side clicks", () => { + const contents: FileContents = { + original: "one\ntwo\nthree\nfour", + modified: "zero\none\nthree\nfour", + language: "text", + }; + + expect( + getRawSectionForDiffLocation({ + contents, + lineNumber: 2, + side: "deletions", + }), + ).toEqual({ + startLine: 1, + endLine: 4, + }); + }); }); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts index 30e7b50011d..514dae8c68e 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts @@ -33,6 +33,11 @@ export interface RawEditorPosition { column: number; } +export interface RawEditorRange { + startLine: number; + endLine: number; +} + function isSupportedLineType(lineType: string): lineType is LineTypes { return ( lineType === "context" || @@ -114,6 +119,93 @@ function mapOldSideLineToRawLine( return clampLineNumber(lineNumber + lineDelta, modifiedLines); } +export function getRawSectionForDiffLocation({ + contents, + lineNumber, + side, +}: Pick< + MapDiffLocationToRawPositionOptions, + "contents" | "lineNumber" | "side" +>): RawEditorRange | null { + const modifiedLines = contents.modified.split("\n"); + const diff = parseDiffFromFile( + { name: "before", contents: contents.original }, + { name: "after", contents: contents.modified }, + ); + + for (const hunk of diff.hunks) { + let currentOldLine = hunk.deletionStart; + let currentNewLine = hunk.additionStart; + let containsLocation = false; + + for (const chunk of hunk.hunkContent) { + if (chunk.type === "context") { + const contextLineCount = chunk.lines.length; + + if ( + side === "additions" && + lineNumber >= currentNewLine && + lineNumber < currentNewLine + contextLineCount + ) { + containsLocation = true; + break; + } + + if ( + side === "deletions" && + lineNumber >= currentOldLine && + lineNumber < currentOldLine + contextLineCount + ) { + containsLocation = true; + break; + } + + currentOldLine += contextLineCount; + currentNewLine += contextLineCount; + continue; + } + + if ( + side === "deletions" && + lineNumber >= currentOldLine && + lineNumber < currentOldLine + chunk.deletions.length + ) { + containsLocation = true; + break; + } + + if ( + side === "additions" && + lineNumber >= currentNewLine && + lineNumber < currentNewLine + chunk.additions.length + ) { + containsLocation = true; + break; + } + + currentOldLine += chunk.deletions.length; + currentNewLine += chunk.additions.length; + } + + if (!containsLocation) { + continue; + } + + const rawStartLine = clampLineNumber(hunk.additionStart, modifiedLines); + const rawEndLine = clampLineNumber( + Math.max(hunk.additionStart, hunk.additionStart + hunk.additionCount - 1), + modifiedLines, + ); + + return { + startLine: rawStartLine, + endLine: rawEndLine, + }; + } + + return null; +} + export function mapDiffLocationToRawPosition({ contents, lineNumber, @@ -133,33 +225,6 @@ export function mapDiffLocationToRawPosition({ }; } -export function getColumnFromDiffSelection({ - lineElement, - numberColumn = false, -}: DiffClickColumnOptions): number { - if (numberColumn) { - return 1; - } - - const selection = window.getSelection(); - if (!selection || selection.rangeCount === 0) { - return 1; - } - - const codeElement = getDiffCodeElement(lineElement); - const range = selection.getRangeAt(0); - const anchorNode = range.startContainer; - if (!codeElement.contains(anchorNode)) { - return 1; - } - - const measureRange = document.createRange(); - measureRange.selectNodeContents(codeElement); - measureRange.setEnd(anchorNode, range.startOffset); - - return Math.max(1, measureRange.toString().length + 1); -} - export function getColumnFromDiffPoint({ lineElement, clientX, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/CodeEditorAdapter/CodeEditorAdapter.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/CodeEditorAdapter/CodeEditorAdapter.ts index 17aea0f719c..c5db3935e2a 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/CodeEditorAdapter/CodeEditorAdapter.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/CodeEditorAdapter/CodeEditorAdapter.ts @@ -3,11 +3,20 @@ export interface EditorSelectionLines { endLine: number; } +export interface EditorHighlightRange { + startLine: number; + endLine: number; +} + export interface CodeEditorAdapter { focus(): void; getValue(): string; setValue(value: string): void; - revealPosition(line: number, column?: number): void; + revealPosition( + line: number, + column?: number, + highlightRange?: EditorHighlightRange, + ): void; getSelectionLines(): EditorSelectionLines | null; selectAll(): void; cut(): void; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx index 2be71afce24..46f7dfe1040 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx @@ -11,8 +11,16 @@ import { openSearchPanel, searchKeymap, } from "@codemirror/search"; -import { Compartment, EditorSelection, EditorState } from "@codemirror/state"; import { + Compartment, + EditorSelection, + EditorState, + StateEffect, + StateField, +} from "@codemirror/state"; +import { + Decoration, + type DecorationSet, drawSelection, dropCursor, EditorView, @@ -41,6 +49,38 @@ interface CodeEditorProps { onSave?: () => void; } +const setHighlightRangeEffect = StateEffect.define<{ + from: number; + to: number; +} | null>(); + +const highlightRangeField = StateField.define({ + create: () => Decoration.none, + update(decorations, transaction) { + decorations = decorations.map(transaction.changes); + + for (const effect of transaction.effects) { + if (!effect.is(setHighlightRangeEffect)) { + continue; + } + + if (!effect.value) { + decorations = Decoration.none; + continue; + } + + decorations = Decoration.set([ + Decoration.mark({ + class: "cm-jump-target-section", + }).range(effect.value.from, effect.value.to), + ]); + } + + return decorations; + }, + provide: (field) => EditorView.decorations.from(field), +}); + function createCodeMirrorAdapter(view: EditorView): CodeEditorAdapter { let disposed = false; @@ -60,14 +100,38 @@ function createCodeMirrorAdapter(view: EditorView): CodeEditorAdapter { }, }); }, - revealPosition(line, column = 1) { + revealPosition(line, column = 1, highlightRange) { const safeLine = Math.max(1, Math.min(line, view.state.doc.lines)); const lineInfo = view.state.doc.line(safeLine); const offset = Math.min(column - 1, lineInfo.length); const anchor = lineInfo.from + Math.max(0, offset); + const effects = []; + + if (highlightRange) { + const startLine = Math.max( + 1, + Math.min(highlightRange.startLine, view.state.doc.lines), + ); + const endLine = Math.max( + startLine, + Math.min(highlightRange.endLine, view.state.doc.lines), + ); + const startLineInfo = view.state.doc.line(startLine); + const endLineInfo = view.state.doc.line(endLine); + + effects.push( + setHighlightRangeEffect.of({ + from: startLineInfo.from, + to: endLineInfo.to, + }), + ); + } else { + effects.push(setHighlightRangeEffect.of(null)); + } view.dispatch({ selection: EditorSelection.cursor(anchor), + effects, scrollIntoView: true, }); view.focus(); @@ -210,6 +274,7 @@ export function CodeEditor({ const state = EditorState.create({ doc: value, extensions: [ + highlightRangeField, lineNumbers(), highlightActiveLineGutter(), highlightSpecialChars(), @@ -230,6 +295,12 @@ export function CodeEditor({ "data-testid": "code-editor", spellcheck: "false", }), + EditorView.theme({ + ".cm-jump-target-section": { + backgroundColor: + "color-mix(in srgb, var(--accent) 18%, transparent)", + }, + }), keymap.of([ indentWithTab, ...defaultKeymap, diff --git a/apps/desktop/src/renderer/stores/tabs/store.ts b/apps/desktop/src/renderer/stores/tabs/store.ts index 11b560fc495..48fbd4d74d9 100644 --- a/apps/desktop/src/renderer/stores/tabs/store.ts +++ b/apps/desktop/src/renderer/stores/tabs/store.ts @@ -730,6 +730,12 @@ export const useTabsStore = create()( options.line ?? existingFileViewer.initialLine, initialColumn: options.column ?? existingFileViewer.initialColumn, + initialSelectionStartLine: + options.selectionStartLine ?? + existingFileViewer.initialSelectionStartLine, + initialSelectionEndLine: + options.selectionEndLine ?? + existingFileViewer.initialSelectionEndLine, }, }, }, @@ -776,6 +782,8 @@ export const useTabsStore = create()( oldPath: options.oldPath, initialLine: options.line, initialColumn: options.column, + initialSelectionStartLine: options.selectionStartLine, + initialSelectionEndLine: options.selectionEndLine, }, }, }, diff --git a/apps/desktop/src/renderer/stores/tabs/types.ts b/apps/desktop/src/renderer/stores/tabs/types.ts index 065b653fbc3..1522fc105c9 100644 --- a/apps/desktop/src/renderer/stores/tabs/types.ts +++ b/apps/desktop/src/renderer/stores/tabs/types.ts @@ -78,6 +78,10 @@ export interface AddFileViewerPaneOptions { line?: number; /** Column to scroll to (raw mode only) */ column?: number; + /** Start of a raw highlight range to reveal */ + selectionStartLine?: number; + /** End of a raw highlight range to reveal */ + selectionEndLine?: number; /** If true, opens pinned (permanent). If false/undefined, opens in preview mode (can be replaced) */ isPinned?: boolean; /** If true, opens in a new tab instead of splitting the current tab */ diff --git a/apps/desktop/src/renderer/stores/tabs/utils.ts b/apps/desktop/src/renderer/stores/tabs/utils.ts index 3417fe42ea9..7d1d917a747 100644 --- a/apps/desktop/src/renderer/stores/tabs/utils.ts +++ b/apps/desktop/src/renderer/stores/tabs/utils.ts @@ -184,6 +184,10 @@ export interface CreateFileViewerPaneOptions { line?: number; /** Column to scroll to (raw mode only) */ column?: number; + /** Start of a raw highlight range to reveal */ + selectionStartLine?: number; + /** End of a raw highlight range to reveal */ + selectionEndLine?: number; } /** @@ -212,6 +216,8 @@ export const createFileViewerPane = ( oldPath: options.oldPath, initialLine: options.line, initialColumn: options.column, + initialSelectionStartLine: options.selectionStartLine, + initialSelectionEndLine: options.selectionEndLine, }; // Use filename for display name diff --git a/apps/desktop/src/shared/tabs-types.ts b/apps/desktop/src/shared/tabs-types.ts index c82e6a46654..70186d6d3b3 100644 --- a/apps/desktop/src/shared/tabs-types.ts +++ b/apps/desktop/src/shared/tabs-types.ts @@ -119,6 +119,9 @@ export interface FileViewerState { initialLine?: number; /** Initial column to scroll to (raw mode only, transient - applied once) */ initialColumn?: number; + /** Initial raw highlight range to reveal (transient - applied once) */ + initialSelectionStartLine?: number; + initialSelectionEndLine?: number; } /** From 1122a11a010abccc24e2501c243e3f2718d9e97c Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 13:50:09 -0700 Subject: [PATCH 11/21] Fix diff context menu location lookup --- .../FileViewerContent/FileViewerContent.tsx | 6 +++--- .../FileViewerContent/utils/diff-location.ts | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index ab54f6f7fcd..4dfa3b6d16a 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -20,7 +20,7 @@ import { MarkdownSearch } from "../MarkdownSearch"; import { type DiffDomLocation, getColumnFromDiffPoint, - getDiffLocationFromTarget, + getDiffLocationFromEvent, getRawSectionForDiffLocation, mapDiffLocationToRawPosition, } from "./utils/diff-location"; @@ -369,7 +369,7 @@ export function FileViewerContent({ } }} onContextMenuCapture={(event) => { - const location = getDiffLocationFromTarget(event.target); + const location = getDiffLocationFromEvent(event.nativeEvent); if (!location) { return; } @@ -385,7 +385,7 @@ export function FileViewerContent({ }; }} onDoubleClick={(event) => { - const location = getDiffLocationFromTarget(event.target); + const location = getDiffLocationFromEvent(event.nativeEvent); if (!location) { return; } diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts index 514dae8c68e..7bd497887a8 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts @@ -311,3 +311,18 @@ export function getDiffLocationFromTarget( numberColumn, }; } + +export function getDiffLocationFromEvent( + event: Pick, +): DiffDomLocation | null { + const composedPath = event.composedPath(); + + for (const pathEntry of composedPath) { + const location = getDiffLocationFromTarget(pathEntry ?? null); + if (location) { + return location; + } + } + + return getDiffLocationFromTarget(event.target); +} From 6d47fdc86bd468bbcad220afb15712175fe92280 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 13:54:28 -0700 Subject: [PATCH 12/21] Improve jump target highlight contrast --- .../WorkspaceView/components/CodeEditor/CodeEditor.tsx | 6 ------ .../WorkspaceView/components/CodeEditor/constants.ts | 2 ++ .../components/CodeEditor/createCodeMirrorTheme.ts | 4 ++++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx index 46f7dfe1040..d39a0643adf 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx @@ -295,12 +295,6 @@ export function CodeEditor({ "data-testid": "code-editor", spellcheck: "false", }), - EditorView.theme({ - ".cm-jump-target-section": { - backgroundColor: - "color-mix(in srgb, var(--accent) 18%, transparent)", - }, - }), keymap.of([ indentWithTab, ...defaultKeymap, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts index e8bdd960b80..1d0bcc1e746 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts @@ -7,6 +7,8 @@ export const MIDNIGHT_CODE_COLORS = { border: "#21252b", muted: "#636d83", selection: "#4b5668", + jumpTargetSection: "#61afef24", + jumpTargetSectionEdge: "#61afef66", search: "#e5c07b33", searchActive: "#e5c07b55", panel: "#21252b", diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts index 3f626371214..b474102f459 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts @@ -53,6 +53,10 @@ export function createCodeMirrorTheme( { backgroundColor: MIDNIGHT_CODE_COLORS.selection, }, + ".cm-jump-target-section": { + backgroundColor: MIDNIGHT_CODE_COLORS.jumpTargetSection, + boxShadow: `inset 2px 0 0 ${MIDNIGHT_CODE_COLORS.jumpTargetSectionEdge}`, + }, ".cm-selectionMatch": { backgroundColor: MIDNIGHT_CODE_COLORS.search, }, From c8f092459d90d2c17d5927f019fdd744cc944d0b Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 14:00:25 -0700 Subject: [PATCH 13/21] Use line decorations for jump highlights --- .../components/CodeEditor/CodeEditor.tsx | 31 ++++++++++++++++--- .../CodeEditor/createCodeMirrorTheme.ts | 2 +- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx index d39a0643adf..37030cf728e 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx @@ -54,6 +54,27 @@ const setHighlightRangeEffect = StateEffect.define<{ to: number; } | null>(); +function createJumpTargetDecorations( + state: EditorState, + from: number, + to: number, +): DecorationSet { + const startLine = state.doc.lineAt(from).number; + const endLine = state.doc.lineAt(to).number; + const decorations = []; + + for (let lineNumber = startLine; lineNumber <= endLine; lineNumber += 1) { + const line = state.doc.line(lineNumber); + decorations.push( + Decoration.line({ + class: "cm-jump-target-section", + }).range(line.from), + ); + } + + return Decoration.set(decorations); +} + const highlightRangeField = StateField.define({ create: () => Decoration.none, update(decorations, transaction) { @@ -69,11 +90,11 @@ const highlightRangeField = StateField.define({ continue; } - decorations = Decoration.set([ - Decoration.mark({ - class: "cm-jump-target-section", - }).range(effect.value.from, effect.value.to), - ]); + decorations = createJumpTargetDecorations( + transaction.state, + effect.value.from, + effect.value.to, + ); } return decorations; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts index b474102f459..c819ef92199 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts @@ -53,7 +53,7 @@ export function createCodeMirrorTheme( { backgroundColor: MIDNIGHT_CODE_COLORS.selection, }, - ".cm-jump-target-section": { + ".cm-line.cm-jump-target-section": { backgroundColor: MIDNIGHT_CODE_COLORS.jumpTargetSection, boxShadow: `inset 2px 0 0 ${MIDNIGHT_CODE_COLORS.jumpTargetSectionEdge}`, }, From 4299c934ee0f2a6db1dd56eb5adaac19fe40d416 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 14:05:30 -0700 Subject: [PATCH 14/21] Fallback single-line jump highlight to active line --- .../components/CodeEditor/CodeEditor.tsx | 30 +++++++++++++++++++ .../CodeEditor/createCodeMirrorTheme.ts | 7 +++++ 2 files changed, 37 insertions(+) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx index 37030cf728e..23da162178f 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx @@ -53,6 +53,7 @@ const setHighlightRangeEffect = StateEffect.define<{ from: number; to: number; } | null>(); +const emptyEditorAttrs: Record = {}; function createJumpTargetDecorations( state: EditorState, @@ -75,6 +76,34 @@ function createJumpTargetDecorations( return Decoration.set(decorations); } +const singleLineJumpHighlightField = StateField.define({ + create: () => false, + update(value, transaction) { + for (const effect of transaction.effects) { + if (!effect.is(setHighlightRangeEffect)) { + continue; + } + + if (!effect.value) { + return false; + } + + return ( + transaction.state.doc.lineAt(effect.value.from).number === + transaction.state.doc.lineAt(effect.value.to).number + ); + } + + return value; + }, + provide: (field) => + EditorView.editorAttributes.from(field, (isSingleLineJumpTarget) => + isSingleLineJumpTarget + ? { class: "cm-single-line-jump-target" } + : emptyEditorAttrs, + ), +}); + const highlightRangeField = StateField.define({ create: () => Decoration.none, update(decorations, transaction) { @@ -295,6 +324,7 @@ export function CodeEditor({ const state = EditorState.create({ doc: value, extensions: [ + singleLineJumpHighlightField, highlightRangeField, lineNumbers(), highlightActiveLineGutter(), diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts index c819ef92199..e7a34315ec3 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts @@ -49,6 +49,13 @@ export function createCodeMirrorTheme( ".cm-activeLineGutter": { backgroundColor: MIDNIGHT_CODE_COLORS.surface, }, + "&.cm-single-line-jump-target .cm-activeLine": { + backgroundColor: MIDNIGHT_CODE_COLORS.jumpTargetSection, + boxShadow: `inset 2px 0 0 ${MIDNIGHT_CODE_COLORS.jumpTargetSectionEdge}`, + }, + "&.cm-single-line-jump-target .cm-activeLineGutter": { + backgroundColor: MIDNIGHT_CODE_COLORS.jumpTargetSection, + }, "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": { backgroundColor: MIDNIGHT_CODE_COLORS.selection, From e53d340aafd2b26c245799f52191e6d3728c68d7 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 14:07:37 -0700 Subject: [PATCH 15/21] Improve active line selection contrast --- .../WorkspaceView/components/CodeEditor/constants.ts | 4 +++- .../components/CodeEditor/createCodeMirrorTheme.ts | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts index 1d0bcc1e746..6416862ebad 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts @@ -6,7 +6,9 @@ export const MIDNIGHT_CODE_COLORS = { background: "#282c34", border: "#21252b", muted: "#636d83", - selection: "#4b5668", + activeLine: "#313846", + selection: "#6b84bd", + selectionOnActiveLine: "#7d97d3", jumpTargetSection: "#61afef24", jumpTargetSectionEdge: "#61afef66", search: "#e5c07b33", diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts index e7a34315ec3..20550f79949 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts @@ -44,10 +44,10 @@ export function createCodeMirrorTheme( borderRight: `1px solid ${MIDNIGHT_CODE_COLORS.border}`, }, ".cm-activeLine": { - backgroundColor: MIDNIGHT_CODE_COLORS.surface, + backgroundColor: MIDNIGHT_CODE_COLORS.activeLine, }, ".cm-activeLineGutter": { - backgroundColor: MIDNIGHT_CODE_COLORS.surface, + backgroundColor: MIDNIGHT_CODE_COLORS.activeLine, }, "&.cm-single-line-jump-target .cm-activeLine": { backgroundColor: MIDNIGHT_CODE_COLORS.jumpTargetSection, @@ -60,6 +60,9 @@ export function createCodeMirrorTheme( { backgroundColor: MIDNIGHT_CODE_COLORS.selection, }, + ".cm-activeLine ::selection": { + backgroundColor: MIDNIGHT_CODE_COLORS.selectionOnActiveLine, + }, ".cm-line.cm-jump-target-section": { backgroundColor: MIDNIGHT_CODE_COLORS.jumpTargetSection, boxShadow: `inset 2px 0 0 ${MIDNIGHT_CODE_COLORS.jumpTargetSectionEdge}`, From 50c62a189d06183e96193ff153bbaadbd214070a Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 14:10:26 -0700 Subject: [PATCH 16/21] Make active line background transparent --- .../components/WorkspaceView/components/CodeEditor/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts index 6416862ebad..56558983ba6 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts @@ -6,7 +6,7 @@ export const MIDNIGHT_CODE_COLORS = { background: "#282c34", border: "#21252b", muted: "#636d83", - activeLine: "#313846", + activeLine: "#ffffff0a", selection: "#6b84bd", selectionOnActiveLine: "#7d97d3", jumpTargetSection: "#61afef24", From 0916e6bedd418d2b765687318d1e1484d9bfc9a1 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 14:12:02 -0700 Subject: [PATCH 17/21] Remove extra editor highlight styling tweaks --- .../components/CodeEditor/CodeEditor.tsx | 30 ------------------- .../components/CodeEditor/constants.ts | 3 +- .../CodeEditor/createCodeMirrorTheme.ts | 10 ------- 3 files changed, 1 insertion(+), 42 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx index 23da162178f..37030cf728e 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx @@ -53,7 +53,6 @@ const setHighlightRangeEffect = StateEffect.define<{ from: number; to: number; } | null>(); -const emptyEditorAttrs: Record = {}; function createJumpTargetDecorations( state: EditorState, @@ -76,34 +75,6 @@ function createJumpTargetDecorations( return Decoration.set(decorations); } -const singleLineJumpHighlightField = StateField.define({ - create: () => false, - update(value, transaction) { - for (const effect of transaction.effects) { - if (!effect.is(setHighlightRangeEffect)) { - continue; - } - - if (!effect.value) { - return false; - } - - return ( - transaction.state.doc.lineAt(effect.value.from).number === - transaction.state.doc.lineAt(effect.value.to).number - ); - } - - return value; - }, - provide: (field) => - EditorView.editorAttributes.from(field, (isSingleLineJumpTarget) => - isSingleLineJumpTarget - ? { class: "cm-single-line-jump-target" } - : emptyEditorAttrs, - ), -}); - const highlightRangeField = StateField.define({ create: () => Decoration.none, update(decorations, transaction) { @@ -324,7 +295,6 @@ export function CodeEditor({ const state = EditorState.create({ doc: value, extensions: [ - singleLineJumpHighlightField, highlightRangeField, lineNumbers(), highlightActiveLineGutter(), diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts index 56558983ba6..a17d5aae5fb 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts @@ -7,8 +7,7 @@ export const MIDNIGHT_CODE_COLORS = { border: "#21252b", muted: "#636d83", activeLine: "#ffffff0a", - selection: "#6b84bd", - selectionOnActiveLine: "#7d97d3", + selection: "#4b5668", jumpTargetSection: "#61afef24", jumpTargetSectionEdge: "#61afef66", search: "#e5c07b33", diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts index 20550f79949..8ae439fda02 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts @@ -49,20 +49,10 @@ export function createCodeMirrorTheme( ".cm-activeLineGutter": { backgroundColor: MIDNIGHT_CODE_COLORS.activeLine, }, - "&.cm-single-line-jump-target .cm-activeLine": { - backgroundColor: MIDNIGHT_CODE_COLORS.jumpTargetSection, - boxShadow: `inset 2px 0 0 ${MIDNIGHT_CODE_COLORS.jumpTargetSectionEdge}`, - }, - "&.cm-single-line-jump-target .cm-activeLineGutter": { - backgroundColor: MIDNIGHT_CODE_COLORS.jumpTargetSection, - }, "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": { backgroundColor: MIDNIGHT_CODE_COLORS.selection, }, - ".cm-activeLine ::selection": { - backgroundColor: MIDNIGHT_CODE_COLORS.selectionOnActiveLine, - }, ".cm-line.cm-jump-target-section": { backgroundColor: MIDNIGHT_CODE_COLORS.jumpTargetSection, boxShadow: `inset 2px 0 0 ${MIDNIGHT_CODE_COLORS.jumpTargetSectionEdge}`, From 3904d5627944965e5b79da5a38fe0f6537f0d74d Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 14:14:59 -0700 Subject: [PATCH 18/21] Add diff edit-here debug logging --- .../TabView/FileViewerPane/FileViewerPane.tsx | 6 ++ .../FileViewerContent/FileViewerContent.tsx | 58 +++++++++++++++++-- .../components/CodeEditor/CodeEditor.tsx | 13 +++++ 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx index 3c051e4af30..3040550639b 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx @@ -219,6 +219,12 @@ export function FileViewerPane({ endLine: number; }, ) => { + console.debug("[DiffEditHereDebug]", { + label: "switch-to-raw", + line, + column, + selection, + }); switchToMode("raw", { line, column, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index 4dfa3b6d16a..452149e7b59 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -263,6 +263,18 @@ export function FileViewerContent({ lineNumber: location.lineNumber, side: location.side, }); + console.debug("[DiffEditHereDebug]", { + label: "map-diff-location", + diffLocation: { + lineNumber: location.lineNumber, + side: location.side, + lineType: location.lineType, + column: location.column, + numberColumn: location.numberColumn, + }, + rawPosition: position, + rawSection, + }); onSwitchToRawAtLocation( position.lineNumber, @@ -300,6 +312,14 @@ export function FileViewerContent({ return; } + console.debug("[DiffEditHereDebug]", { + label: "apply-raw-location", + initialLine, + initialColumn: initialColumn ?? 1, + initialSelectionStartLine: initialSelectionStartLine ?? initialLine, + initialSelectionEndLine: initialSelectionEndLine ?? initialLine, + }); + editorRef.current.revealPosition(initialLine, initialColumn ?? 1, { startLine: initialSelectionStartLine ?? initialLine, endLine: initialSelectionEndLine ?? initialLine, @@ -349,6 +369,10 @@ export function FileViewerContent({ onMoveToNewTab={onMoveToNewTab} onEditAtLocation={() => { const location = lastDiffLocationRef.current; + console.debug("[DiffEditHereDebug]", { + label: "edit-here-select", + location, + }); if (!location || location.column === undefined) { return; } @@ -371,18 +395,40 @@ export function FileViewerContent({ onContextMenuCapture={(event) => { const location = getDiffLocationFromEvent(event.nativeEvent); if (!location) { + console.debug("[DiffEditHereDebug]", { + label: "context-menu-capture-miss", + target: + event.target instanceof HTMLElement + ? { + tagName: event.target.tagName, + className: event.target.className, + } + : String(event.target), + }); return; } + const column = getColumnFromDiffPoint({ + lineElement: location.lineElement, + numberColumn: location.numberColumn, + clientX: event.clientX, + clientY: event.clientY, + }); + lastDiffLocationRef.current = { ...location, - column: getColumnFromDiffPoint({ - lineElement: location.lineElement, - numberColumn: location.numberColumn, - clientX: event.clientX, - clientY: event.clientY, - }), + column, }; + console.debug("[DiffEditHereDebug]", { + label: "context-menu-capture", + location: { + lineNumber: location.lineNumber, + side: location.side, + lineType: location.lineType, + numberColumn: location.numberColumn, + column, + }, + }); }} onDoubleClick={(event) => { const location = getDiffLocationFromEvent(event.nativeEvent); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx index 37030cf728e..57aca362357 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx @@ -155,6 +155,19 @@ function createCodeMirrorAdapter(view: EditorView): CodeEditorAdapter { effects, scrollIntoView: true, }); + console.debug("[DiffEditHereDebug]", { + label: "editor-reveal-position", + requestedLine: line, + requestedColumn: column, + safeLine, + highlightRange, + anchor, + lineLength: lineInfo.length, + selection: { + from: view.state.selection.main.from, + to: view.state.selection.main.to, + }, + }); view.focus(); }, getSelectionLines() { From 4e6ecbce1df8f829bbf1b3f5a7072831b496b851 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 14:18:34 -0700 Subject: [PATCH 19/21] Improve diff edit-here column and highlight --- .../FileViewerContent/utils/diff-location.ts | 65 ++++++++++++++++++- .../components/CodeEditor/CodeEditor.tsx | 18 +++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts index 7bd497887a8..fb476793fd3 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts @@ -69,6 +69,69 @@ function getDiffCodeElement(lineElement: HTMLElement): HTMLElement { return codeElement instanceof HTMLElement ? codeElement : lineElement; } +let measurementCanvas: HTMLCanvasElement | null = null; + +function getMeasurementContext(): CanvasRenderingContext2D | null { + if (!measurementCanvas) { + measurementCanvas = document.createElement("canvas"); + } + + return measurementCanvas.getContext("2d"); +} + +function measureColumnFromRenderedText( + codeElement: HTMLElement, + clientX: number, +): number { + const lineText = codeElement.textContent ?? ""; + if (lineText.length === 0) { + return 1; + } + + const rect = codeElement.getBoundingClientRect(); + if (clientX <= rect.left) { + return 1; + } + + const style = window.getComputedStyle(codeElement); + const context = getMeasurementContext(); + if (!context) { + return 1; + } + + context.font = [ + style.fontStyle, + style.fontVariant, + style.fontWeight, + style.fontSize, + style.fontFamily, + ] + .filter(Boolean) + .join(" "); + + const tabSize = Number.parseInt(style.tabSize || "4", 10); + const safeTabSize = Number.isFinite(tabSize) && tabSize > 0 ? tabSize : 4; + const targetX = clientX - rect.left; + let previousWidth = 0; + let renderedText = ""; + const characters = Array.from(lineText); + + for (let index = 0; index < characters.length; index += 1) { + renderedText += + characters[index] === "\t" ? " ".repeat(safeTabSize) : characters[index]; + const nextWidth = context.measureText(renderedText).width; + const midpoint = previousWidth + (nextWidth - previousWidth) / 2; + + if (targetX <= midpoint) { + return index + 1; + } + + previousWidth = nextWidth; + } + + return characters.length + 1; +} + function mapOldSideLineToRawLine( contents: FileContents, lineNumber: number, @@ -265,7 +328,7 @@ export function getColumnFromDiffPoint({ return Math.max(1, measureRange.toString().length + 1); } - return 1; + return measureColumnFromRenderedText(codeElement, clientX); } export function getDiffLocationFromTarget( diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx index 57aca362357..40399e9efd9 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx @@ -70,6 +70,11 @@ function createJumpTargetDecorations( class: "cm-jump-target-section", }).range(line.from), ); + decorations.push( + Decoration.mark({ + class: "cm-jump-target-section-text", + }).range(line.from, line.to), + ); } return Decoration.set(decorations); @@ -329,6 +334,19 @@ export function CodeEditor({ "data-testid": "code-editor", spellcheck: "false", }), + EditorView.theme({ + ".cm-jump-target-section": { + backgroundColor: + "color-mix(in srgb, var(--accent) 24%, transparent)", + boxShadow: + "inset 3px 0 0 color-mix(in srgb, var(--accent) 75%, white)", + }, + ".cm-jump-target-section-text": { + backgroundColor: + "color-mix(in srgb, var(--accent) 14%, transparent)", + borderRadius: "2px", + }, + }), keymap.of([ indentWithTab, ...defaultKeymap, From 9f2889baf67f1670202c80b86a364d5337ef7554 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 14:21:10 -0700 Subject: [PATCH 20/21] Simplify diff edit interaction --- .../TabView/FileViewerPane/FileViewerPane.tsx | 6 -- .../DiffViewerContextMenu.tsx | 2 +- .../FileViewerContent/FileViewerContent.tsx | 61 ------------------- .../EditorContextMenu/EditorContextMenu.tsx | 18 +++--- .../components/CodeEditor/CodeEditor.tsx | 13 ---- 5 files changed, 10 insertions(+), 90 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx index 3040550639b..3c051e4af30 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx @@ -219,12 +219,6 @@ export function FileViewerPane({ endLine: number; }, ) => { - console.debug("[DiffEditHereDebug]", { - label: "switch-to-raw", - line, - column, - selection, - }); switchToMode("raw", { line, column, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx index e1dadd20541..51579ebcf5d 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/DiffViewerContextMenu/DiffViewerContextMenu.tsx @@ -125,7 +125,7 @@ export function DiffViewerContextMenu({ ...editorActions, onFind: undefined, }} - extraItems={ + leadingItems={ Edit Here diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index 452149e7b59..21bda05c2ad 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -263,18 +263,6 @@ export function FileViewerContent({ lineNumber: location.lineNumber, side: location.side, }); - console.debug("[DiffEditHereDebug]", { - label: "map-diff-location", - diffLocation: { - lineNumber: location.lineNumber, - side: location.side, - lineType: location.lineType, - column: location.column, - numberColumn: location.numberColumn, - }, - rawPosition: position, - rawSection, - }); onSwitchToRawAtLocation( position.lineNumber, @@ -312,14 +300,6 @@ export function FileViewerContent({ return; } - console.debug("[DiffEditHereDebug]", { - label: "apply-raw-location", - initialLine, - initialColumn: initialColumn ?? 1, - initialSelectionStartLine: initialSelectionStartLine ?? initialLine, - initialSelectionEndLine: initialSelectionEndLine ?? initialLine, - }); - editorRef.current.revealPosition(initialLine, initialColumn ?? 1, { startLine: initialSelectionStartLine ?? initialLine, endLine: initialSelectionEndLine ?? initialLine, @@ -369,10 +349,6 @@ export function FileViewerContent({ onMoveToNewTab={onMoveToNewTab} onEditAtLocation={() => { const location = lastDiffLocationRef.current; - console.debug("[DiffEditHereDebug]", { - label: "edit-here-select", - location, - }); if (!location || location.column === undefined) { return; } @@ -383,7 +359,6 @@ export function FileViewerContent({ }); }} > - {/* biome-ignore lint/a11y/noStaticElementInteractions: diff wrapper intercepts click capture to preserve browser text selection */}
{ const location = getDiffLocationFromEvent(event.nativeEvent); if (!location) { - console.debug("[DiffEditHereDebug]", { - label: "context-menu-capture-miss", - target: - event.target instanceof HTMLElement - ? { - tagName: event.target.tagName, - className: event.target.className, - } - : String(event.target), - }); return; } @@ -419,32 +384,6 @@ export function FileViewerContent({ ...location, column, }; - console.debug("[DiffEditHereDebug]", { - label: "context-menu-capture", - location: { - lineNumber: location.lineNumber, - side: location.side, - lineType: location.lineType, - numberColumn: location.numberColumn, - column, - }, - }); - }} - onDoubleClick={(event) => { - const location = getDiffLocationFromEvent(event.nativeEvent); - if (!location) { - return; - } - - openRawFromDiffLocation({ - ...location, - column: getColumnFromDiffPoint({ - lineElement: location.lineElement, - numberColumn: location.numberColumn, - clientX: event.clientX, - clientY: event.clientY, - }), - }); }} > {children} + {leadingItems && ( + <> + {leadingItems} + + + )} + {/* Clipboard Actions */} {showCutPaste && ( @@ -116,13 +123,6 @@ export function EditorContextMenu({ )} - {extraItems && ( - <> - - {extraItems} - - )} - diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx index 40399e9efd9..fbfda164b5e 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx @@ -160,19 +160,6 @@ function createCodeMirrorAdapter(view: EditorView): CodeEditorAdapter { effects, scrollIntoView: true, }); - console.debug("[DiffEditHereDebug]", { - label: "editor-reveal-position", - requestedLine: line, - requestedColumn: column, - safeLine, - highlightRange, - anchor, - lineLength: lineInfo.length, - selection: { - from: view.state.selection.main.from, - to: view.state.selection.main.to, - }, - }); view.focus(); }, getSelectionLines() { From f82b18ac2c2f3691b17d269190155a87da6d1aee Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Sun, 8 Mar 2026 14:26:25 -0700 Subject: [PATCH 21/21] Remove raw jump highlighting --- .../TabView/FileViewerPane/FileViewerPane.tsx | 28 +---- .../FileViewerContent/FileViewerContent.tsx | 39 +------ .../utils/diff-location.test.ts | 43 +------ .../FileViewerContent/utils/diff-location.ts | 92 --------------- .../CodeEditorAdapter/CodeEditorAdapter.ts | 11 +- .../components/CodeEditor/CodeEditor.tsx | 108 +----------------- .../components/CodeEditor/constants.ts | 2 - .../CodeEditor/createCodeMirrorTheme.ts | 4 - .../desktop/src/renderer/stores/tabs/store.ts | 8 -- .../desktop/src/renderer/stores/tabs/types.ts | 4 - .../desktop/src/renderer/stores/tabs/utils.ts | 6 - apps/desktop/src/shared/tabs-types.ts | 3 - 12 files changed, 10 insertions(+), 338 deletions(-) diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx index 3c051e4af30..4901ed2c98b 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsx @@ -85,8 +85,6 @@ export function FileViewerPane({ const oldPath = fileViewer?.oldPath; const initialLine = fileViewer?.initialLine; const initialColumn = fileViewer?.initialColumn; - const initialSelectionStartLine = fileViewer?.initialSelectionStartLine; - const initialSelectionEndLine = fileViewer?.initialSelectionEndLine; const pinPane = useTabsStore((s) => s.pinPane); @@ -180,8 +178,6 @@ export function FileViewerPane({ location?: { line?: number; column?: number; - selectionStartLine?: number; - selectionEndLine?: number; }, ) => { const panes = useTabsStore.getState().panes; @@ -198,12 +194,6 @@ export function FileViewerPane({ initialLine: location?.line ?? currentPane.fileViewer.initialLine, initialColumn: location?.column ?? currentPane.fileViewer.initialColumn, - initialSelectionStartLine: - location?.selectionStartLine ?? - currentPane.fileViewer.initialSelectionStartLine, - initialSelectionEndLine: - location?.selectionEndLine ?? - currentPane.fileViewer.initialSelectionEndLine, }, }, }, @@ -211,20 +201,8 @@ export function FileViewerPane({ } }; - const handleSwitchToRawAtLocation = ( - line: number, - column: number, - selection?: { - startLine: number; - endLine: number; - }, - ) => { - switchToMode("raw", { - line, - column, - selectionStartLine: selection?.startLine, - selectionEndLine: selection?.endLine, - }); + const handleSwitchToRawAtLocation = (line: number, column: number) => { + switchToMode("raw", { line, column }); }; const handleViewModeChange = (value: string) => { @@ -330,8 +308,6 @@ export function FileViewerPane({ draftContentRef={draftContentRef} initialLine={initialLine} initialColumn={initialColumn} - initialSelectionStartLine={initialSelectionStartLine} - initialSelectionEndLine={initialSelectionEndLine} diffViewMode={diffViewMode} hideUnchangedRegions={hideUnchangedRegions} onSaveRaw={handleSaveRaw} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index 21bda05c2ad..e2c5c15e66d 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -21,7 +21,6 @@ import { type DiffDomLocation, getColumnFromDiffPoint, getDiffLocationFromEvent, - getRawSectionForDiffLocation, mapDiffLocationToRawPosition, } from "./utils/diff-location"; @@ -107,21 +106,12 @@ interface FileViewerContentProps { draftContentRef: MutableRefObject; initialLine?: number; initialColumn?: number; - initialSelectionStartLine?: number; - initialSelectionEndLine?: number; diffViewMode: DiffViewMode; hideUnchangedRegions: boolean; onSaveRaw: () => Promise; onEditorChange: (value: string | undefined) => void; setIsDirty: (dirty: boolean) => void; - onSwitchToRawAtLocation: ( - line: number, - column: number, - selection?: { - startLine: number; - endLine: number; - }, - ) => void; + onSwitchToRawAtLocation: (line: number, column: number) => void; onSplitHorizontal: () => void; onSplitVertical: () => void; onSplitWithNewChat?: () => void; @@ -160,8 +150,6 @@ export function FileViewerContent({ draftContentRef, initialLine, initialColumn, - initialSelectionStartLine, - initialSelectionEndLine, diffViewMode, hideUnchangedRegions, onSaveRaw, @@ -198,12 +186,7 @@ export function FileViewerContent({ // biome-ignore lint/correctness/useExhaustiveDependencies: Reset when requested cursor target changes useEffect(() => { hasAppliedInitialLocationRef.current = false; - }, [ - initialLine, - initialColumn, - initialSelectionStartLine, - initialSelectionEndLine, - ]); + }, [initialLine, initialColumn]); useEffect(() => { if (viewMode !== "raw") { @@ -258,17 +241,8 @@ export function FileViewerContent({ lineType: location.lineType, column: location.column, }); - const rawSection = getRawSectionForDiffLocation({ - contents: diffData, - lineNumber: location.lineNumber, - side: location.side, - }); - onSwitchToRawAtLocation( - position.lineNumber, - position.column, - rawSection ?? undefined, - ); + onSwitchToRawAtLocation(position.lineNumber, position.column); }; useEffect(() => { @@ -300,18 +274,13 @@ export function FileViewerContent({ return; } - editorRef.current.revealPosition(initialLine, initialColumn ?? 1, { - startLine: initialSelectionStartLine ?? initialLine, - endLine: initialSelectionEndLine ?? initialLine, - }); + editorRef.current.revealPosition(initialLine, initialColumn ?? 1); hasAppliedInitialLocationRef.current = true; }, [ viewMode, editorRef, initialLine, initialColumn, - initialSelectionStartLine, - initialSelectionEndLine, isLoadingRaw, rawFileData, ]); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts index 918f15f7770..98e928865ec 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.test.ts @@ -1,9 +1,6 @@ import { describe, expect, it } from "bun:test"; import type { FileContents } from "shared/changes-types"; -import { - getRawSectionForDiffLocation, - mapDiffLocationToRawPosition, -} from "./diff-location"; +import { mapDiffLocationToRawPosition } from "./diff-location"; describe("mapDiffLocationToRawPosition", () => { it("keeps addition-side clicks on the same modified line", () => { @@ -127,42 +124,4 @@ describe("mapDiffLocationToRawPosition", () => { column: 5, }); }); - - it("returns the modified hunk range for addition-side clicks", () => { - const contents: FileContents = { - original: "one\ntwo\nthree\nfour", - modified: "one\ntwo changed\nthree\nfour\nfive", - language: "text", - }; - - expect( - getRawSectionForDiffLocation({ - contents, - lineNumber: 2, - side: "additions", - }), - ).toEqual({ - startLine: 1, - endLine: 5, - }); - }); - - it("returns the modified hunk range for deletion-side clicks", () => { - const contents: FileContents = { - original: "one\ntwo\nthree\nfour", - modified: "zero\none\nthree\nfour", - language: "text", - }; - - expect( - getRawSectionForDiffLocation({ - contents, - lineNumber: 2, - side: "deletions", - }), - ).toEqual({ - startLine: 1, - endLine: 4, - }); - }); }); diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts index fb476793fd3..d3d6c35ecc1 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/utils/diff-location.ts @@ -33,11 +33,6 @@ export interface RawEditorPosition { column: number; } -export interface RawEditorRange { - startLine: number; - endLine: number; -} - function isSupportedLineType(lineType: string): lineType is LineTypes { return ( lineType === "context" || @@ -182,93 +177,6 @@ function mapOldSideLineToRawLine( return clampLineNumber(lineNumber + lineDelta, modifiedLines); } -export function getRawSectionForDiffLocation({ - contents, - lineNumber, - side, -}: Pick< - MapDiffLocationToRawPositionOptions, - "contents" | "lineNumber" | "side" ->): RawEditorRange | null { - const modifiedLines = contents.modified.split("\n"); - const diff = parseDiffFromFile( - { name: "before", contents: contents.original }, - { name: "after", contents: contents.modified }, - ); - - for (const hunk of diff.hunks) { - let currentOldLine = hunk.deletionStart; - let currentNewLine = hunk.additionStart; - let containsLocation = false; - - for (const chunk of hunk.hunkContent) { - if (chunk.type === "context") { - const contextLineCount = chunk.lines.length; - - if ( - side === "additions" && - lineNumber >= currentNewLine && - lineNumber < currentNewLine + contextLineCount - ) { - containsLocation = true; - break; - } - - if ( - side === "deletions" && - lineNumber >= currentOldLine && - lineNumber < currentOldLine + contextLineCount - ) { - containsLocation = true; - break; - } - - currentOldLine += contextLineCount; - currentNewLine += contextLineCount; - continue; - } - - if ( - side === "deletions" && - lineNumber >= currentOldLine && - lineNumber < currentOldLine + chunk.deletions.length - ) { - containsLocation = true; - break; - } - - if ( - side === "additions" && - lineNumber >= currentNewLine && - lineNumber < currentNewLine + chunk.additions.length - ) { - containsLocation = true; - break; - } - - currentOldLine += chunk.deletions.length; - currentNewLine += chunk.additions.length; - } - - if (!containsLocation) { - continue; - } - - const rawStartLine = clampLineNumber(hunk.additionStart, modifiedLines); - const rawEndLine = clampLineNumber( - Math.max(hunk.additionStart, hunk.additionStart + hunk.additionCount - 1), - modifiedLines, - ); - - return { - startLine: rawStartLine, - endLine: rawEndLine, - }; - } - - return null; -} - export function mapDiffLocationToRawPosition({ contents, lineNumber, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/CodeEditorAdapter/CodeEditorAdapter.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/CodeEditorAdapter/CodeEditorAdapter.ts index c5db3935e2a..17aea0f719c 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/CodeEditorAdapter/CodeEditorAdapter.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/components/CodeEditorAdapter/CodeEditorAdapter.ts @@ -3,20 +3,11 @@ export interface EditorSelectionLines { endLine: number; } -export interface EditorHighlightRange { - startLine: number; - endLine: number; -} - export interface CodeEditorAdapter { focus(): void; getValue(): string; setValue(value: string): void; - revealPosition( - line: number, - column?: number, - highlightRange?: EditorHighlightRange, - ): void; + revealPosition(line: number, column?: number): void; getSelectionLines(): EditorSelectionLines | null; selectAll(): void; cut(): void; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx index fbfda164b5e..2be71afce24 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx @@ -11,16 +11,8 @@ import { openSearchPanel, searchKeymap, } from "@codemirror/search"; +import { Compartment, EditorSelection, EditorState } from "@codemirror/state"; import { - Compartment, - EditorSelection, - EditorState, - StateEffect, - StateField, -} from "@codemirror/state"; -import { - Decoration, - type DecorationSet, drawSelection, dropCursor, EditorView, @@ -49,64 +41,6 @@ interface CodeEditorProps { onSave?: () => void; } -const setHighlightRangeEffect = StateEffect.define<{ - from: number; - to: number; -} | null>(); - -function createJumpTargetDecorations( - state: EditorState, - from: number, - to: number, -): DecorationSet { - const startLine = state.doc.lineAt(from).number; - const endLine = state.doc.lineAt(to).number; - const decorations = []; - - for (let lineNumber = startLine; lineNumber <= endLine; lineNumber += 1) { - const line = state.doc.line(lineNumber); - decorations.push( - Decoration.line({ - class: "cm-jump-target-section", - }).range(line.from), - ); - decorations.push( - Decoration.mark({ - class: "cm-jump-target-section-text", - }).range(line.from, line.to), - ); - } - - return Decoration.set(decorations); -} - -const highlightRangeField = StateField.define({ - create: () => Decoration.none, - update(decorations, transaction) { - decorations = decorations.map(transaction.changes); - - for (const effect of transaction.effects) { - if (!effect.is(setHighlightRangeEffect)) { - continue; - } - - if (!effect.value) { - decorations = Decoration.none; - continue; - } - - decorations = createJumpTargetDecorations( - transaction.state, - effect.value.from, - effect.value.to, - ); - } - - return decorations; - }, - provide: (field) => EditorView.decorations.from(field), -}); - function createCodeMirrorAdapter(view: EditorView): CodeEditorAdapter { let disposed = false; @@ -126,38 +60,14 @@ function createCodeMirrorAdapter(view: EditorView): CodeEditorAdapter { }, }); }, - revealPosition(line, column = 1, highlightRange) { + revealPosition(line, column = 1) { const safeLine = Math.max(1, Math.min(line, view.state.doc.lines)); const lineInfo = view.state.doc.line(safeLine); const offset = Math.min(column - 1, lineInfo.length); const anchor = lineInfo.from + Math.max(0, offset); - const effects = []; - - if (highlightRange) { - const startLine = Math.max( - 1, - Math.min(highlightRange.startLine, view.state.doc.lines), - ); - const endLine = Math.max( - startLine, - Math.min(highlightRange.endLine, view.state.doc.lines), - ); - const startLineInfo = view.state.doc.line(startLine); - const endLineInfo = view.state.doc.line(endLine); - - effects.push( - setHighlightRangeEffect.of({ - from: startLineInfo.from, - to: endLineInfo.to, - }), - ); - } else { - effects.push(setHighlightRangeEffect.of(null)); - } view.dispatch({ selection: EditorSelection.cursor(anchor), - effects, scrollIntoView: true, }); view.focus(); @@ -300,7 +210,6 @@ export function CodeEditor({ const state = EditorState.create({ doc: value, extensions: [ - highlightRangeField, lineNumbers(), highlightActiveLineGutter(), highlightSpecialChars(), @@ -321,19 +230,6 @@ export function CodeEditor({ "data-testid": "code-editor", spellcheck: "false", }), - EditorView.theme({ - ".cm-jump-target-section": { - backgroundColor: - "color-mix(in srgb, var(--accent) 24%, transparent)", - boxShadow: - "inset 3px 0 0 color-mix(in srgb, var(--accent) 75%, white)", - }, - ".cm-jump-target-section-text": { - backgroundColor: - "color-mix(in srgb, var(--accent) 14%, transparent)", - borderRadius: "2px", - }, - }), keymap.of([ indentWithTab, ...defaultKeymap, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts index a17d5aae5fb..6fb832cb0c8 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/constants.ts @@ -8,8 +8,6 @@ export const MIDNIGHT_CODE_COLORS = { muted: "#636d83", activeLine: "#ffffff0a", selection: "#4b5668", - jumpTargetSection: "#61afef24", - jumpTargetSectionEdge: "#61afef66", search: "#e5c07b33", searchActive: "#e5c07b55", panel: "#21252b", diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts index 8ae439fda02..94e2b25a900 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts @@ -53,10 +53,6 @@ export function createCodeMirrorTheme( { backgroundColor: MIDNIGHT_CODE_COLORS.selection, }, - ".cm-line.cm-jump-target-section": { - backgroundColor: MIDNIGHT_CODE_COLORS.jumpTargetSection, - boxShadow: `inset 2px 0 0 ${MIDNIGHT_CODE_COLORS.jumpTargetSectionEdge}`, - }, ".cm-selectionMatch": { backgroundColor: MIDNIGHT_CODE_COLORS.search, }, diff --git a/apps/desktop/src/renderer/stores/tabs/store.ts b/apps/desktop/src/renderer/stores/tabs/store.ts index 48fbd4d74d9..11b560fc495 100644 --- a/apps/desktop/src/renderer/stores/tabs/store.ts +++ b/apps/desktop/src/renderer/stores/tabs/store.ts @@ -730,12 +730,6 @@ export const useTabsStore = create()( options.line ?? existingFileViewer.initialLine, initialColumn: options.column ?? existingFileViewer.initialColumn, - initialSelectionStartLine: - options.selectionStartLine ?? - existingFileViewer.initialSelectionStartLine, - initialSelectionEndLine: - options.selectionEndLine ?? - existingFileViewer.initialSelectionEndLine, }, }, }, @@ -782,8 +776,6 @@ export const useTabsStore = create()( oldPath: options.oldPath, initialLine: options.line, initialColumn: options.column, - initialSelectionStartLine: options.selectionStartLine, - initialSelectionEndLine: options.selectionEndLine, }, }, }, diff --git a/apps/desktop/src/renderer/stores/tabs/types.ts b/apps/desktop/src/renderer/stores/tabs/types.ts index 1522fc105c9..065b653fbc3 100644 --- a/apps/desktop/src/renderer/stores/tabs/types.ts +++ b/apps/desktop/src/renderer/stores/tabs/types.ts @@ -78,10 +78,6 @@ export interface AddFileViewerPaneOptions { line?: number; /** Column to scroll to (raw mode only) */ column?: number; - /** Start of a raw highlight range to reveal */ - selectionStartLine?: number; - /** End of a raw highlight range to reveal */ - selectionEndLine?: number; /** If true, opens pinned (permanent). If false/undefined, opens in preview mode (can be replaced) */ isPinned?: boolean; /** If true, opens in a new tab instead of splitting the current tab */ diff --git a/apps/desktop/src/renderer/stores/tabs/utils.ts b/apps/desktop/src/renderer/stores/tabs/utils.ts index 7d1d917a747..3417fe42ea9 100644 --- a/apps/desktop/src/renderer/stores/tabs/utils.ts +++ b/apps/desktop/src/renderer/stores/tabs/utils.ts @@ -184,10 +184,6 @@ export interface CreateFileViewerPaneOptions { line?: number; /** Column to scroll to (raw mode only) */ column?: number; - /** Start of a raw highlight range to reveal */ - selectionStartLine?: number; - /** End of a raw highlight range to reveal */ - selectionEndLine?: number; } /** @@ -216,8 +212,6 @@ export const createFileViewerPane = ( oldPath: options.oldPath, initialLine: options.line, initialColumn: options.column, - initialSelectionStartLine: options.selectionStartLine, - initialSelectionEndLine: options.selectionEndLine, }; // Use filename for display name diff --git a/apps/desktop/src/shared/tabs-types.ts b/apps/desktop/src/shared/tabs-types.ts index 70186d6d3b3..c82e6a46654 100644 --- a/apps/desktop/src/shared/tabs-types.ts +++ b/apps/desktop/src/shared/tabs-types.ts @@ -119,9 +119,6 @@ export interface FileViewerState { initialLine?: number; /** Initial column to scroll to (raw mode only, transient - applied once) */ initialColumn?: number; - /** Initial raw highlight range to reveal (transient - applied once) */ - initialSelectionStartLine?: number; - initialSelectionEndLine?: number; } /**