Skip to content
Open
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
15 changes: 11 additions & 4 deletions src/panels/MainPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>('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;
Expand Down
57 changes: 56 additions & 1 deletion src/panels/__tests__/MainPanel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const H = vi.hoisted(() => {
messageHandler: null as null | ((m: unknown) => unknown),
panel: null as null | { webview: { postMessage: ReturnType<typeof vi.fn> } },
repos: [] as Array<{ path: string; name: string; type: string }>,
configValues: {} as Record<string, unknown>,
};
});

Expand Down Expand Up @@ -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() {} }),
Expand Down Expand Up @@ -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');
});
Expand Down Expand Up @@ -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<typeof vi.fn>).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']));
Expand Down