From 4bc651f8c1b6b449303bdb48f17fd6f97c832120 Mon Sep 17 00:00:00 2001 From: Le Vivilet Date: Sun, 19 Jul 2026 16:11:31 +0000 Subject: [PATCH] Fix syntax highlighting in inline diffs --- .../GetDiffEditorVirtualDom.ts | 13 +++- .../GetInlineDiffEditorVirtualDom.ts | 7 ++- .../GetInlineDiffRowDom.ts | 29 +++++++-- .../test/GetDiffEditorVirtualDom.test.ts | 43 ++++++++++--- .../test/GetInlineDiffRowDom.test.ts | 63 +++++++++++++++++++ 5 files changed, 140 insertions(+), 15 deletions(-) create mode 100644 packages/diff-view/test/GetInlineDiffRowDom.test.ts diff --git a/packages/diff-view/src/parts/GetDiffEditorVirtualDom/GetDiffEditorVirtualDom.ts b/packages/diff-view/src/parts/GetDiffEditorVirtualDom/GetDiffEditorVirtualDom.ts index 0bc55c88..9e7c13a4 100644 --- a/packages/diff-view/src/parts/GetDiffEditorVirtualDom/GetDiffEditorVirtualDom.ts +++ b/packages/diff-view/src/parts/GetDiffEditorVirtualDom/GetDiffEditorVirtualDom.ts @@ -113,7 +113,18 @@ export const getDiffEditorVirtualDom = (state: DiffViewState): readonly VirtualD } = state const canRenderInline = diffMode === 'inline' && renderModeLeft === 'text' && renderModeRight === 'text' && !errorLeftMessage && !errorRightMessage if (canRenderInline) { - return getInlineDiffEditorVirtualDom(contentLeft, contentRight, lineNumbers, minLineY, maxLineY, searchVisible, searchQuery, showWhitespace) + return getInlineDiffEditorVirtualDom( + contentLeft, + contentRight, + lineNumbers, + minLineY, + maxLineY, + searchVisible, + searchQuery, + showWhitespace, + tokenizedLinesLeft, + tokenizedLinesRight, + ) } const showLineNumbers = lineNumbers && renderModeLeft === 'text' && renderModeRight === 'text' diff --git a/packages/diff-view/src/parts/GetInlineDiffEditorVirtualDom/GetInlineDiffEditorVirtualDom.ts b/packages/diff-view/src/parts/GetInlineDiffEditorVirtualDom/GetInlineDiffEditorVirtualDom.ts index 898b9198..8b382a04 100644 --- a/packages/diff-view/src/parts/GetInlineDiffEditorVirtualDom/GetInlineDiffEditorVirtualDom.ts +++ b/packages/diff-view/src/parts/GetInlineDiffEditorVirtualDom/GetInlineDiffEditorVirtualDom.ts @@ -1,5 +1,6 @@ import type { VirtualDomNode } from '@lvce-editor/virtual-dom-worker' import { VirtualDomElements } from '@lvce-editor/virtual-dom-worker' +import type { TokenizedLine } from '../TokenizedLine/TokenizedLine.ts' import * as ClassNames from '../ClassNames/ClassNames.ts' import { getDiffEditorButtonsDom } from '../GetDiffEditorButtonsDom/GetDiffEditorButtonsDom.ts' import { getDiffSearchHeaderDom } from '../GetDiffSearchHeaderDom/GetDiffSearchHeaderDom.ts' @@ -18,6 +19,8 @@ export const getInlineDiffEditorVirtualDom = ( searchVisible: boolean, searchQuery: string, showWhitespace: boolean, + tokenizedLinesLeft: readonly TokenizedLine[], + tokenizedLinesRight: readonly TokenizedLine[], ): readonly VirtualDomNode[] => { const rows = getInlineDiffRows(contentLeft, contentRight) const visibleRows = rows.slice(minLineY, maxLineY) @@ -55,7 +58,9 @@ export const getInlineDiffEditorVirtualDom = ( className: ClassNames.DiffEditorRows, type: VirtualDomElements.Div, }, - ...visibleRows.flatMap(getInlineDiffRowDom), + ...visibleRows.flatMap((row) => + getInlineDiffRowDom(row, row.lineNumberRight === null ? tokenizedLinesLeft[row.lineNumberLeft! - 1] : tokenizedLinesRight[row.lineNumberRight - 1]), + ), ...buttonsDom, ...scrollBarDom, ] diff --git a/packages/diff-view/src/parts/GetInlineDiffRowDom/GetInlineDiffRowDom.ts b/packages/diff-view/src/parts/GetInlineDiffRowDom/GetInlineDiffRowDom.ts index 3d83f814..c5261e9f 100644 --- a/packages/diff-view/src/parts/GetInlineDiffRowDom/GetInlineDiffRowDom.ts +++ b/packages/diff-view/src/parts/GetInlineDiffRowDom/GetInlineDiffRowDom.ts @@ -1,16 +1,35 @@ import type { VirtualDomNode } from '@lvce-editor/virtual-dom-worker' import { text, VirtualDomElements } from '@lvce-editor/virtual-dom-worker' import type { InlineDiffRow } from '../GetInlineDiffRows/GetInlineDiffRows.ts' +import type { TokenizedLine } from '../TokenizedLine/TokenizedLine.ts' import { getInlineDiffRowClassName } from '../GetInlineDiffRowClassName/GetInlineDiffRowClassName.ts' -import { getInlineDiffRowText } from '../GetInlineDiffRowText/GetInlineDiffRowText.ts' +import { getTokens } from '../GetVisibleLines/GetTokens/GetTokens.ts' +import { getTokenDom } from '../GetVisibleLinesDom/GetTokenDom/GetTokenDom.ts' -export const getInlineDiffRowDom = (row: InlineDiffRow): readonly VirtualDomNode[] => { +const getPrefix = (row: InlineDiffRow): string => { + if (row.lineNumberLeft === null) { + if (row.lineNumberRight === null) { + return '' + } + return '+ ' + } + if (row.lineNumberRight === null) { + return '- ' + } + return ' ' +} + +export const getInlineDiffRowDom = (row: InlineDiffRow, tokenizedLine?: TokenizedLine): readonly VirtualDomNode[] => { + const className = getInlineDiffRowClassName(row) + const tokens = getTokens(tokenizedLine ? [tokenizedLine] : [], 0, row.text) + const prefix = getPrefix(row) return [ { - childCount: 1, - className: getInlineDiffRowClassName(row), + childCount: tokens.length + 1, + className, type: VirtualDomElements.Div, }, - text(getInlineDiffRowText(row)), + text(prefix), + ...tokens.flatMap(getTokenDom), ] } diff --git a/packages/diff-view/test/GetDiffEditorVirtualDom.test.ts b/packages/diff-view/test/GetDiffEditorVirtualDom.test.ts index f0ad66e6..26c2222c 100644 --- a/packages/diff-view/test/GetDiffEditorVirtualDom.test.ts +++ b/packages/diff-view/test/GetDiffEditorVirtualDom.test.ts @@ -695,29 +695,33 @@ test('getDiffEditorVirtualDom renders inline mode as a single combined diff pane type: VirtualDomElements.Div, }, { - childCount: 1, + childCount: 2, className: ClassNames.EditorRow, type: VirtualDomElements.Div, }, - text(' same'), + text(' '), + text('same'), { - childCount: 1, + childCount: 2, className: ClassNames.EditorRowDeletion, type: VirtualDomElements.Div, }, - text('- before'), + text('- '), + text('before'), { - childCount: 1, + childCount: 2, className: ClassNames.EditorRowInsertion, type: VirtualDomElements.Div, }, - text('+ after'), + text('+ '), + text('after'), { - childCount: 1, + childCount: 2, className: ClassNames.EditorRow, type: VirtualDomElements.Div, }, - text(' shared'), + text(' '), + text('shared'), { childCount: 2, className: ClassNames.DiffEditorButtons, @@ -744,6 +748,29 @@ test('getDiffEditorVirtualDom renders inline mode as a single combined diff pane ]) }) +test('getDiffEditorVirtualDom renders syntax highlighting in inline mode', (): void => { + const result = getDiffEditorVirtualDom({ + ...createDefaultState(), + contentLeft: 'name: Before', + contentRight: 'name: After', + diffMode: 'inline', + maxLineY: 2, + tokenizedLinesLeft: [['name', 'Token YamlPropertyName', ': ', 'Token Punctuation', 'Before', 'Token YamlPropertyValueString']], + tokenizedLinesRight: [['name', 'Token YamlPropertyName', ': ', 'Token Punctuation', 'After', 'Token YamlPropertyValueString']], + totalLineCount: 2, + uriLeft: '/tmp/before.yml', + uriRight: '/tmp/after.yml', + }) + + expect(result).toContainEqual({ + childCount: 1, + className: 'Token YamlPropertyName', + type: VirtualDomElements.Span, + }) + expect(result).toContainEqual(text('Before')) + expect(result).toContainEqual(text('After')) +}) + test('getDiffEditorVirtualDom renders a horizontal sash for vertical layout', (): void => { const result = getDiffEditorVirtualDom({ ...createDefaultState(), diff --git a/packages/diff-view/test/GetInlineDiffRowDom.test.ts b/packages/diff-view/test/GetInlineDiffRowDom.test.ts new file mode 100644 index 00000000..fc0ac66a --- /dev/null +++ b/packages/diff-view/test/GetInlineDiffRowDom.test.ts @@ -0,0 +1,63 @@ +import { expect, test } from '@jest/globals' +import { text, VirtualDomElements } from '@lvce-editor/virtual-dom-worker' +import * as ClassNames from '../src/parts/ClassNames/ClassNames.ts' +import { getInlineDiffRowDom } from '../src/parts/GetInlineDiffRowDom/GetInlineDiffRowDom.ts' +import { InlineDiffRowType } from '../src/parts/InlineDiffRowType/InlineDiffRowType.ts' + +test('getInlineDiffRowDom renders right-side syntax tokens for context rows', (): void => { + const row = { lineNumberLeft: 1, lineNumberRight: 1, text: 'name: Test', type: InlineDiffRowType.Context } + const result = getInlineDiffRowDom(row, ['name', 'Token YamlPropertyName', ': ', 'Token Punctuation', 'Test', 'Token YamlPropertyValueString']) + + expect(result).toEqual([ + { + childCount: 4, + className: ClassNames.EditorRow, + type: VirtualDomElements.Div, + }, + text(' '), + { + childCount: 1, + className: 'Token YamlPropertyName', + type: VirtualDomElements.Span, + }, + text('name'), + { + childCount: 1, + className: 'Token Punctuation', + type: VirtualDomElements.Span, + }, + text(': '), + { + childCount: 1, + className: 'Token YamlPropertyValueString', + type: VirtualDomElements.Span, + }, + text('Test'), + ]) +}) + +test('getInlineDiffRowDom renders left-side syntax tokens for deletions', (): void => { + const row = { lineNumberLeft: 1, lineNumberRight: null, text: 'enabled: true', type: InlineDiffRowType.Deletion } + const result = getInlineDiffRowDom(row, ['enabled', 'Token YamlPropertyName', ': ', 'Token Punctuation', 'true', 'Token LanguageConstant']) + + expect(result[1]).toEqual(text('- ')) + expect(result).toContainEqual({ + childCount: 1, + className: 'Token LanguageConstant', + type: VirtualDomElements.Span, + }) +}) + +test('getInlineDiffRowDom renders plain text when tokenization is unavailable', (): void => { + const row = { lineNumberLeft: null, lineNumberRight: 2, text: 'added', type: InlineDiffRowType.Insertion } + + expect(getInlineDiffRowDom(row)).toEqual([ + { + childCount: 2, + className: ClassNames.EditorRowInsertion, + type: VirtualDomElements.Div, + }, + text('+ '), + text('added'), + ]) +})