From 8aa6e4c8605f658934bbce13e76e88fa1f31b913 Mon Sep 17 00:00:00 2001 From: Patrick Cheung Date: Fri, 25 Sep 2026 17:34:01 +0800 Subject: [PATCH] feat(conflicts): respect git.mergeEditor when opening conflicted files (#67) --- src/panels/MainPanel.ts | 15 +++++-- src/panels/__tests__/MainPanel.test.ts | 57 +++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/panels/MainPanel.ts b/src/panels/MainPanel.ts index 5869905d..5ad555f2 100644 --- a/src/panels/MainPanel.ts +++ b/src/panels/MainPanel.ts @@ -1565,10 +1565,17 @@ export class MainPanel { case 'openConflictFile': { const fullPath = this.resolveRepoRelativePath(message.payload.file, 'openConflictFile'); const fileUri = vscode.Uri.file(fullPath); - // Try to open in VS Code's 3-way merge editor, fallback to normal editor - try { - await vscode.commands.executeCommand('git.openMergeEditor', fileUri); - } catch { + // Follow the native git.mergeEditor preference (#67): only try the + // 3-way merge editor when it is enabled (the VS Code default). + const useMergeEditor = vscode.workspace.getConfiguration('git').get('mergeEditor', true); + if (useMergeEditor) { + // Try to open in VS Code's 3-way merge editor, fallback to normal editor + try { + await vscode.commands.executeCommand('git.openMergeEditor', fileUri); + } catch { + await vscode.window.showTextDocument(fileUri); + } + } else { await vscode.window.showTextDocument(fileUri); } break; diff --git a/src/panels/__tests__/MainPanel.test.ts b/src/panels/__tests__/MainPanel.test.ts index 76051ee3..6822388a 100644 --- a/src/panels/__tests__/MainPanel.test.ts +++ b/src/panels/__tests__/MainPanel.test.ts @@ -37,6 +37,7 @@ const H = vi.hoisted(() => { messageHandler: null as null | ((m: unknown) => unknown), panel: null as null | { webview: { postMessage: ReturnType } }, repos: [] as Array<{ path: string; name: string; type: string }>, + configValues: {} as Record, }; }); @@ -68,9 +69,15 @@ vi.mock('vscode', () => { showWarningMessage: vi.fn(), showErrorMessage: vi.fn(async () => undefined), showSaveDialog: vi.fn(async () => undefined), + showTextDocument: vi.fn(async () => undefined), }, workspace: { - getConfiguration: () => ({ get: (_k: string, d?: unknown) => d }), + getConfiguration: (section?: string) => ({ + get: (k: string, d?: unknown) => { + const key = section ? `${section}.${k}` : k; + return key in H.configValues ? H.configValues[key] : d; + }, + }), getWorkspaceFolder: () => ({ uri: { fsPath: '/repo' } }), workspaceFolders: [{ uri: { fsPath: '/repo' } }], onDidChangeConfiguration: () => ({ dispose() {} }), @@ -128,6 +135,7 @@ beforeEach(() => { H.git.fileExistsAtRef.mockResolvedValue(true); H.git.getEmptyTreeRef.mockResolvedValue('4b825dc642cb6eb9a060e54bf8d69288fbee4904'); H.repos = [{ path: '/repo', name: 'repo', type: 'root' }]; + H.configValues = {}; (MainPanel as unknown as { currentPanel: unknown }).currentPanel = undefined; MainPanel.createOrShow(extUri, '/repo'); }); @@ -289,6 +297,53 @@ describe('MainPanel message routing', () => { }); }); +describe('MainPanel openConflictFile honours git.mergeEditor', () => { + const openConflict = () => dispatch({ type: 'openConflictFile', payload: { file: 'src/conflict.ts' } }); + + it('opens a normal text editor (no merge editor) when git.mergeEditor is false', async () => { + const vscode = await import('vscode'); + H.configValues['git.mergeEditor'] = false; + + await openConflict(); + + expect(vscode.window.showTextDocument).toHaveBeenCalledWith( + expect.objectContaining({ fsPath: expect.stringContaining('conflict.ts') }), + ); + expect(vscode.commands.executeCommand).not.toHaveBeenCalledWith('git.openMergeEditor', expect.anything()); + }); + + it('uses the merge editor when git.mergeEditor is true', async () => { + const vscode = await import('vscode'); + H.configValues['git.mergeEditor'] = true; + + await openConflict(); + + expect(vscode.commands.executeCommand).toHaveBeenCalledWith( + 'git.openMergeEditor', + expect.objectContaining({ fsPath: expect.stringContaining('conflict.ts') }), + ); + expect(vscode.window.showTextDocument).not.toHaveBeenCalled(); + }); + + it('defaults to the merge editor when git.mergeEditor is unset', async () => { + const vscode = await import('vscode'); + + await openConflict(); + + expect(vscode.commands.executeCommand).toHaveBeenCalledWith('git.openMergeEditor', expect.anything()); + expect(vscode.window.showTextDocument).not.toHaveBeenCalled(); + }); + + it('falls back to a normal editor when the merge editor command fails', async () => { + const vscode = await import('vscode'); + (vscode.commands.executeCommand as ReturnType).mockRejectedValueOnce(new Error('merge editor unavailable')); + + await openConflict(); + + expect(vscode.window.showTextDocument).toHaveBeenCalled(); + }); +}); + describe('MainPanel error handling', () => { it('posts notGitRepo when git reports "not a git repository"', async () => { H.git.log.mockRejectedValue(new GitError('fatal: not a git repository', 128, ['log']));