Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 12 additions & 28 deletions packages/diff-view/src/parts/ApplyEditInput/ApplyEditInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,24 @@ import { getVisibleLinesState } from '../GetVisibleLinesState/GetVisibleLinesSta
import * as InputSource from '../InputSource/InputSource.ts'
import { loadSyntaxHighlighting } from '../LoadSyntaxHighlighting/LoadSyntaxHighlighting.ts'

const getBaseContentRight = (state: DiffViewState): string => {
if (state.inputValue && state.contentRight.startsWith(state.inputValue)) {
return state.contentRight.slice(state.inputValue.length)
}
return state.contentRight
}

export const applyEditInput = async (state: DiffViewState, inputValue: string): Promise<DiffViewState> => {
if (inputValue === state.inputValue || state.renderModeRight !== 'text' || state.errorRightMessage || getLineCount(inputValue) > state.maxInputLines) {
return state
}

const { totalLineCountLeft } = state
// compute base content without any previously-applied input buffer
const baseContent = getBaseContentRight(state)

// derive insertion index from cursor position (row/column)
// compute full content index (including any previously-applied inputValue prefix)
const fullLines = state.contentRight ? state.contentRight.split('\n') : ['']
const fullCursorRow = Math.max(0, Math.min(state.rightEditor.cursorRowIndex, fullLines.length - 1))
const fullCurrentLine = fullLines[fullCursorRow] ?? ''
const fullCursorCol = Math.max(0, Math.min(state.rightEditor.cursorColumnIndex, fullCurrentLine.length))
let fullIndex = 0
for (let i = 0; i < fullCursorRow; i++) {
fullIndex += (fullLines[i] ?? '').length + 1
}
fullIndex += fullCursorCol

const prefixLength = state.inputValue && state.contentRight.startsWith(state.inputValue) ? state.inputValue.length : 0
const index = Math.max(0, fullIndex - prefixLength)
// base content split into lines is available if needed

const before = baseContent.slice(0, index)
const after = baseContent.slice(index)
const { cursorColumnIndex, cursorRowIndex } = state.rightEditor
const precedingLines = state.contentRight.split('\n').slice(0, cursorRowIndex).join('\n')
const fullIndex = precedingLines.length + (cursorRowIndex === 0 ? 0 : 1) + cursorColumnIndex
const inputStart = Math.max(fullIndex - state.inputValue.length, 0)
const before = state.contentRight.slice(0, inputStart)
const after = state.contentRight.slice(fullIndex)
const contentRight = `${before}${inputValue}${after}`
const linesBeforeCursor = `${before}${inputValue}`.split('\n')
const rightEditor = {
cursorColumnIndex: (linesBeforeCursor.at(-1) || '').length,
cursorRowIndex: linesBeforeCursor.length - 1,
}
const totalLineCountRight = getLineCount(contentRight)
const canComputeInlineDiff = state.renderModeLeft === 'text' && !state.errorLeftMessage
const { inlineChanges, totalLineCount } = canComputeInlineDiff
Expand All @@ -65,6 +48,7 @@ export const applyEditInput = async (state: DiffViewState, inputValue: string):
inputValue,
languageIdLeft: syntaxHighlightingState?.languageIdLeft || state.languageIdLeft,
languageIdRight: syntaxHighlightingState?.languageIdRight || state.languageIdRight,
rightEditor,
scrollBarBackgroundImage: getScrollBarBackgroundImage(inlineChanges, totalLineCount),
tokenizedLinesLeft: syntaxHighlightingState?.tokenizedLinesLeft || state.tokenizedLinesLeft,
tokenizedLinesRight: syntaxHighlightingState?.tokenizedLinesRight || [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ export const getCursorPositionFromCoordinates = (state: DiffViewState, clientX:
const rawCursorColumnIndex = Math.floor((clientX - contentLeft - gutterWidth - CursorConstants.RowPaddingLeft) / charWidth)
const rawCursorRowIndex = state.minLineY + Math.floor((clientY - contentTop) / CursorConstants.LineHeight)
const maxCursorRowIndex = Math.max(state.totalLineCountRight - 1, 0)
const cursorRowIndex = Math.min(Math.max(rawCursorRowIndex, 0), maxCursorRowIndex)
const line = state.contentRight.split('\n')[cursorRowIndex] || ''
return {
cursorColumnIndex: Math.max(rawCursorColumnIndex, 0),
cursorRowIndex: Math.min(Math.max(rawCursorRowIndex, 0), maxCursorRowIndex),
cursorColumnIndex: Math.min(Math.max(rawCursorColumnIndex, 0), line.length),
cursorRowIndex,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export const handleClickRightSide = (state: DiffViewState, clientX: number, clie
...state,
focus: DiffEditorWhenExpression.FocusDiffEditorText,
focusVersion: state.focusVersion + 1,
inputSource: 0,
inputValue: '',
}
return setCursorPosition(focusedState, cursorPosition.cursorColumnIndex, cursorPosition.cursorRowIndex)
}
1 change: 0 additions & 1 deletion packages/diff-view/src/parts/InputName/InputName.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export const DiffEditorInput = 'DiffEditorInput'
export const SourceControlInput = 'SourceControlInput'
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ export const loadSyntaxHighlighting = async (
): Promise<SyntaxHighlightingState> => {
try {
const languages = await getLanguages(platform, assetDir)
const languageLeft = getSyntaxLanguage(uriLeft, languages)
const languageRight = getSyntaxLanguage(uriRight, languages)
const detectedLanguageLeft = getSyntaxLanguage(uriLeft, languages)
const detectedLanguageRight = getSyntaxLanguage(uriRight, languages)
const languageLeft = detectedLanguageLeft.languageId === 'unknown' ? detectedLanguageRight : detectedLanguageLeft
const languageRight = detectedLanguageRight
const uniqueLanguages = getUniqueLanguages(languageLeft, languageRight)
await Promise.all(uniqueLanguages.map(loadTokenizer))
const [tokenizedLinesLeft, tokenizedLinesRight] = await Promise.all([tokenizeCodeBlock(contentLeft, languageLeft), tokenizeCodeBlock(contentRight, languageRight)])
Expand Down
14 changes: 7 additions & 7 deletions packages/diff-view/src/parts/RenderCss/RenderCss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ export const renderCss = (oldState: DiffViewState, newState: DiffViewState): any
const rightCursorTop = (rightEditor.cursorRowIndex - minLineY) * CursorConstants.LineHeight
const css = `
:root {
--DiffBackground: #0b0d10;
--DiffForeground: rgba(255, 255, 255, 0.88);
--DiffGutterBackground: #0f1218;
--DiffGutterForeground: rgba(255, 255, 255, 0.58);
--DiffSeparatorColor: rgba(255, 255, 255, 0.12);
--DiffBackground: var(--MainBackground);
--DiffForeground: var(--EditorForeground);
--DiffGutterBackground: var(--SideBarBackground);
--DiffGutterForeground: var(--SideBarForeground);
--DiffSeparatorColor: var(--SideBarBorder);
--DiffMissingLineBackground: rgba(255, 255, 255, 0.035);
--DiffDeletionBackground: rgba(248, 81, 73, 0.2);
--DiffDeletionForeground: #ffb3ad;
Expand Down Expand Up @@ -312,8 +312,8 @@ ${getEmptyLineNumberCss(newState.visibleLinesLeft, newState.visibleLinesRight, i
z-index: 1;
}

.EditorCursor {
background: red;
.DiffEditor .EditorCursor {
background: var(--EditorCursorBackground);
height: var(--ItemHeight);
left: var(--CursorLeft);
position: absolute;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export const renderEventListeners = (): readonly DomEventListener[] => {
params: ['handleBeforeInput', 'event.inputType', 'event.data'],
preventDefault: true,
},
{
name: DomEventListenersFunctions.HandleInput,
params: ['handleInput', EventExpression.TargetValue],
},
{
name: DomEventListenersFunctions.HandleContextMenu,
params: ['handleContextMenu', EventExpression.Button, EventExpression.ClientX, EventExpression.ClientY],
Expand Down
2 changes: 1 addition & 1 deletion packages/diff-view/src/parts/RenderValue/RenderValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import * as InputName from '../InputName/InputName.ts'

export const renderValue = (oldState: DiffViewState, newState: DiffViewState): any => {
const { id, inputValue } = newState
return [ViewletCommand.SetValueByName, id, InputName.SourceControlInput, inputValue]
return [ViewletCommand.SetValueByName, id, InputName.DiffEditorInput, inputValue]
}
2 changes: 1 addition & 1 deletion packages/diff-view/test/ApplyRender.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ test('applyRender runs renderers in diff order', (): void => {

expect(result).toEqual([
[ViewletCommand.SetFocusContext, 2, 3],
[ViewletCommand.SetValueByName, 2, InputName.SourceControlInput, 'commit message'],
[ViewletCommand.SetValueByName, 2, InputName.DiffEditorInput, 'commit message'],
])
})
8 changes: 8 additions & 0 deletions packages/diff-view/test/EditCommands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ test('insertLineBreak inserts a newline into the edit buffer', async (): Promise
contentLeft: 'alpha',
contentRight: 'gammaBeta',
inputValue: 'gamma',
rightEditor: {
cursorColumnIndex: 5,
cursorRowIndex: 0,
},
totalLineCountLeft: 1,
totalLineCountRight: 1,
}
Expand Down Expand Up @@ -59,6 +63,10 @@ test('deleteLeft deletes the previous edit buffer character', async (): Promise<
contentLeft: 'alpha',
contentRight: 'gamma Beta',
inputValue: 'gamma ',
rightEditor: {
cursorColumnIndex: 6,
cursorRowIndex: 0,
},
totalLineCountLeft: 1,
totalLineCountRight: 1,
}
Expand Down
16 changes: 16 additions & 0 deletions packages/diff-view/test/GetCursorPositionFromCoordinates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getCursorPositionFromCoordinates } from '../src/parts/GetCursorPosition
test('getCursorPositionFromCoordinates computes cursor position in horizontal layout', (): void => {
const state = {
...createDefaultState(),
contentRight: 'zero\none\ntwo',
gutterWidthVariable: 18,
leftWidth: 100,
totalLineCountRight: 3,
Expand All @@ -23,6 +24,7 @@ test('getCursorPositionFromCoordinates computes cursor position in horizontal la
test('getCursorPositionFromCoordinates computes cursor position in vertical layout', (): void => {
const state = {
...createDefaultState(),
contentRight: 'zero\none\ntwo\nthree',
gutterWidthVariable: 18,
layout: 'vertical' as const,
leftWidth: 100,
Expand Down Expand Up @@ -84,3 +86,17 @@ test('getCursorPositionFromCoordinates clamps rows to the last right editor line
cursorRowIndex: 2,
})
})

test('getCursorPositionFromCoordinates clamps columns to the line length', (): void => {
const state = {
...createDefaultState(),
contentRight: 'abc',
}

const result = getCursorPositionFromCoordinates(state, 1000, 0)

expect(result).toEqual({
cursorColumnIndex: 3,
cursorRowIndex: 0,
})
})
2 changes: 2 additions & 0 deletions packages/diff-view/test/HandleClickAt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { handleClickAt } from '../src/parts/HandleClickAt/HandleClickAt.ts'
test('handleClickAt focuses the editable right side for horizontal layout clicks', (): void => {
const state = {
...createDefaultState(),
contentRight: 'zero\none\ntwo',
gutterWidthVariable: 18,
leftWidth: 40,
totalLineCountRight: 3,
Expand Down Expand Up @@ -38,6 +39,7 @@ test('handleClickAt ignores horizontal layout clicks in the left side', (): void
test('handleClickAt focuses the editable right side for vertical layout clicks', (): void => {
const state = {
...createDefaultState(),
contentRight: 'zero\none\ntwo\nthree',
gutterWidthVariable: 18,
layout: 'vertical' as const,
leftWidth: 40,
Expand Down
2 changes: 2 additions & 0 deletions packages/diff-view/test/HandleClickRightSide.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { handleClickRightSide } from '../src/parts/HandleClickRightSide/HandleCl
test('handleClickRightSide focuses the editable right side', (): void => {
const state = {
...createDefaultState(),
contentRight: 'zero\none',
totalLineCountRight: 2,
}

Expand All @@ -24,6 +25,7 @@ test('handleClickRightSide focuses the editable right side', (): void => {
test('handleClickRightSide increments focusVersion when already focused', (): void => {
const state = {
...createDefaultState(),
contentRight: 'zero\none\ntwo',
focus: DiffEditorWhenExpression.FocusDiffEditorText,
focusVersion: 4,
totalLineCountRight: 3,
Expand Down
8 changes: 8 additions & 0 deletions packages/diff-view/test/HandleInput.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ test('handleInput applies the hidden input value before the stable right content
contentLeft: 'alpha',
contentRight: 'gamma beta',
inputValue: 'gamma ',
rightEditor: {
cursorColumnIndex: 6,
cursorRowIndex: 0,
},
totalLineCountLeft: 1,
totalLineCountRight: 1,
}
Expand All @@ -91,6 +95,10 @@ test('handleInput supports shrinking the hidden input value', async (): Promise<
contentLeft: 'alpha',
contentRight: 'gamma Beta',
inputValue: 'gamma ',
rightEditor: {
cursorColumnIndex: 6,
cursorRowIndex: 0,
},
totalLineCountLeft: 1,
totalLineCountRight: 1,
}
Expand Down
4 changes: 4 additions & 0 deletions packages/diff-view/test/RenderEventListeners.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ test('renderEventListeners registers tracked sash pointer listeners', (): void =
name: DomEventListenerFunctions.HandleClickRightSide,
params: ['handleClickRightSide', EventExpression.ClientX, EventExpression.ClientY],
})
expect(result).toContainEqual({
name: DomEventListenerFunctions.HandleInput,
params: ['handleInput', EventExpression.TargetValue],
})
})
35 changes: 35 additions & 0 deletions packages/diff-view/test/TypingAtCursor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,41 @@ test('typing inserts characters at the cursor position', async (): Promise<void>
expect(diffWorkerRpc.invocations.length).toBeGreaterThanOrEqual(0)
expect(result.contentRight).toBe('hello World')
expect(result.inputValue).toBe(' ')
expect(result.rightEditor).toEqual({
cursorColumnIndex: 6,
cursorRowIndex: 0,
})
})

test('typing multiple input events replaces the previous input buffer', async (): Promise<void> => {
using diffWorkerRpc = DiffWorker.registerMockRpc({
'Diff.diffInline': async (): Promise<readonly unknown[]> => [],
})

const state = {
...createDefaultState(),
contentLeft: '',
contentRight: 'helloWorld',
inputValue: '',
rightEditor: {
cursorColumnIndex: 10,
cursorRowIndex: 0,
},
totalLineCountLeft: 1,
totalLineCountRight: 1,
}

const afterFirstInput = await applyEditInput(state, ' ')
const afterSecondInput = await applyEditInput(afterFirstInput, ' a')
const result = await applyEditInput(afterSecondInput, ' abc')

expect(diffWorkerRpc.invocations.length).toBeGreaterThanOrEqual(0)
expect(result.contentRight).toBe('helloWorld abc')
expect(result.inputValue).toBe(' abc')
expect(result.rightEditor).toEqual({
cursorColumnIndex: 14,
cursorRowIndex: 0,
})
})

test('insertLineBreak at cursor splits the line', async (): Promise<void> => {
Expand Down
4 changes: 2 additions & 2 deletions packages/e2e/src/diff.css-three-lines-added-light-theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import type { Test } from '@lvce-editor/test-with-playwright'

export const name = 'diff.css-three-lines-added-light-theme'

export const skip = 1

export const test: Test = async ({ Command, DiffView, expect, FileSystem, Locator, Workspace }) => {
const tmpDir = await FileSystem.getTmpDir()
await FileSystem.writeFile(
Expand All @@ -27,11 +25,13 @@ export const test: Test = async ({ Command, DiffView, expect, FileSystem, Locato
await DiffView.open(`${tmpDir}/before.css`, `${tmpDir}/after.css`)

const main = Locator('.Main')
const diffEditor = Locator('.DiffEditor')
const beforePane = Locator('.DiffEditorContentLeft .DiffEditorRows')
const afterPane = Locator('.DiffEditorContentRight .DiffEditorRows')
const insertedRows = Locator('.DiffEditorContentRight .EditorRow.Insertion')

await expect(main).toHaveCSS('background-color', 'rgb(248, 249, 250)')
await expect(diffEditor).toHaveCSS('background-color', 'rgb(248, 249, 250)')
await expect(beforePane).toContainText('.button')
await expect(afterPane).toContainText('.button')
await expect(afterPane).toContainText('color: red')
Expand Down
7 changes: 1 addition & 6 deletions packages/e2e/src/diff.syntax-highlighting-typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ import type { Test } from '@lvce-editor/test-with-playwright'

export const name = 'diff.syntax-highlighting-typescript'

export const skip = 1

export const test: Test = async ({ DiffView, expect, FileSystem, Locator, Workspace }) => {
const tmpDir = await FileSystem.getTmpDir()
await FileSystem.writeFile(`${tmpDir}/file-1.ts`, `const leftValue = 1`)
await FileSystem.writeFile(`${tmpDir}/file-2.ts`, `const rightValue = 2`)
await Workspace.setPath(tmpDir)

await DiffView.open(`${tmpDir}/file-1.ts`, `${tmpDir}/file-2.ts`)
await DiffView.open(`data://const leftValue = 1`, `${tmpDir}/file-2.ts`)

const beforePane = Locator('.DiffEditorContentLeft .DiffEditorRows')
const afterPane = Locator('.DiffEditorContentRight .DiffEditorRows')
Expand All @@ -25,6 +22,4 @@ export const test: Test = async ({ DiffView, expect, FileSystem, Locator, Worksp
await expect(expectedLocator3).toHaveCount(1)
await expect(beforePane).toContainText('const leftValue = 1')
await expect(afterPane).toContainText('const rightValue = 2')
await expect(beforePane).not.toContainText('rightValue')
await expect(afterPane).not.toContainText('leftValue')
}
5 changes: 4 additions & 1 deletion packages/e2e/src/diff.text-cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@ import type { Test } from '@lvce-editor/test-with-playwright'

export const name = 'diff.text-cursor'

export const test: Test = async ({ DiffView, expect, FileSystem, Locator, Workspace }) => {
export const test: Test = async ({ Command, DiffView, expect, FileSystem, Locator, Workspace }) => {
const tmpDir = await FileSystem.getTmpDir()
await FileSystem.writeFile(`${tmpDir}/before.txt`, 'alpha')
await FileSystem.writeFile(`${tmpDir}/after.txt`, 'beta')
await Workspace.setPath(tmpDir)
await Command.execute('ColorTheme.setColorTheme', 'slime')

await DiffView.open(`${tmpDir}/before.txt`, `${tmpDir}/after.txt`)

const beforeRows = Locator('.DiffEditorContentLeft .DiffEditorRows')
const afterRows = Locator('.DiffEditorContentRight .DiffEditorRows')
const beforeLineNumber = Locator('.DiffEditorContentLeft .DiffEditorLineNumber').first()
const sash = Locator('.SashVertical').first()
const cursor = Locator('.EditorCursorRight')

await expect(beforeRows).toHaveCSS('cursor', 'text')
await expect(afterRows).toHaveCSS('cursor', 'text')
await expect(beforeLineNumber).toHaveCSS('cursor', 'auto')
await expect(sash).toHaveCSS('cursor', 'col-resize')
await expect(cursor).toHaveCSS('background-color', 'rgb(168, 223, 90)')
}
Loading
Loading