Check for zeditor if zed binary is not found (#3680)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
This commit is contained in:
Cole Miller 2025-07-20 15:35:21 -04:00 committed by GitHub
parent 8f85ac7de0
commit 7a9821607b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 178 additions and 46 deletions

View File

@ -52,56 +52,99 @@ describe('editor utils', () => {
describe('checkHasEditorType', () => { describe('checkHasEditorType', () => {
const testCases: Array<{ const testCases: Array<{
editor: EditorType; editor: EditorType;
command: string; commands: string[];
win32Command: string; win32Commands: string[];
}> = [ }> = [
{ editor: 'vscode', command: 'code', win32Command: 'code.cmd' }, { editor: 'vscode', commands: ['code'], win32Commands: ['code.cmd'] },
{ editor: 'vscodium', command: 'codium', win32Command: 'codium.cmd' }, {
{ editor: 'windsurf', command: 'windsurf', win32Command: 'windsurf' }, editor: 'vscodium',
{ editor: 'cursor', command: 'cursor', win32Command: 'cursor' }, commands: ['codium'],
{ editor: 'vim', command: 'vim', win32Command: 'vim' }, win32Commands: ['codium.cmd'],
{ editor: 'neovim', command: 'nvim', win32Command: 'nvim' }, },
{ editor: 'zed', command: 'zed', win32Command: 'zed' }, {
editor: 'windsurf',
commands: ['windsurf'],
win32Commands: ['windsurf'],
},
{ editor: 'cursor', commands: ['cursor'], win32Commands: ['cursor'] },
{ editor: 'vim', commands: ['vim'], win32Commands: ['vim'] },
{ editor: 'neovim', commands: ['nvim'], win32Commands: ['nvim'] },
{ editor: 'zed', commands: ['zed', 'zeditor'], win32Commands: ['zed'] },
]; ];
for (const { editor, command, win32Command } of testCases) { for (const { editor, commands, win32Commands } of testCases) {
describe(`${editor}`, () => { describe(`${editor}`, () => {
it(`should return true if "${command}" command exists on non-windows`, () => { // Non-windows tests
it(`should return true if first command "${commands[0]}" exists on non-windows`, () => {
Object.defineProperty(process, 'platform', { value: 'linux' }); Object.defineProperty(process, 'platform', { value: 'linux' });
(execSync as Mock).mockReturnValue( (execSync as Mock).mockReturnValue(
Buffer.from(`/usr/bin/${command}`), Buffer.from(`/usr/bin/${commands[0]}`),
); );
expect(checkHasEditorType(editor)).toBe(true); expect(checkHasEditorType(editor)).toBe(true);
expect(execSync).toHaveBeenCalledWith(`command -v ${command}`, { expect(execSync).toHaveBeenCalledWith(`command -v ${commands[0]}`, {
stdio: 'ignore', stdio: 'ignore',
}); });
}); });
it(`should return false if "${command}" command does not exist on non-windows`, () => { if (commands.length > 1) {
it(`should return true if first command doesn't exist but second command "${commands[1]}" exists on non-windows`, () => {
Object.defineProperty(process, 'platform', { value: 'linux' });
(execSync as Mock)
.mockImplementationOnce(() => {
throw new Error(); // first command not found
})
.mockReturnValueOnce(Buffer.from(`/usr/bin/${commands[1]}`)); // second command found
expect(checkHasEditorType(editor)).toBe(true);
expect(execSync).toHaveBeenCalledTimes(2);
});
}
it(`should return false if none of the commands exist on non-windows`, () => {
Object.defineProperty(process, 'platform', { value: 'linux' }); Object.defineProperty(process, 'platform', { value: 'linux' });
(execSync as Mock).mockImplementation(() => { (execSync as Mock).mockImplementation(() => {
throw new Error(); throw new Error(); // all commands not found
}); });
expect(checkHasEditorType(editor)).toBe(false); expect(checkHasEditorType(editor)).toBe(false);
expect(execSync).toHaveBeenCalledTimes(commands.length);
}); });
it(`should return true if "${win32Command}" command exists on windows`, () => { // Windows tests
it(`should return true if first command "${win32Commands[0]}" exists on windows`, () => {
Object.defineProperty(process, 'platform', { value: 'win32' }); Object.defineProperty(process, 'platform', { value: 'win32' });
(execSync as Mock).mockReturnValue( (execSync as Mock).mockReturnValue(
Buffer.from(`C:\\Program Files\\...\\${win32Command}`), Buffer.from(`C:\\Program Files\\...\\${win32Commands[0]}`),
); );
expect(checkHasEditorType(editor)).toBe(true); expect(checkHasEditorType(editor)).toBe(true);
expect(execSync).toHaveBeenCalledWith(`where.exe ${win32Command}`, { expect(execSync).toHaveBeenCalledWith(
`where.exe ${win32Commands[0]}`,
{
stdio: 'ignore', stdio: 'ignore',
}); },
);
}); });
it(`should return false if "${win32Command}" command does not exist on windows`, () => { if (win32Commands.length > 1) {
it(`should return true if first command doesn't exist but second command "${win32Commands[1]}" exists on windows`, () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
(execSync as Mock)
.mockImplementationOnce(() => {
throw new Error(); // first command not found
})
.mockReturnValueOnce(
Buffer.from(`C:\\Program Files\\...\\${win32Commands[1]}`),
); // second command found
expect(checkHasEditorType(editor)).toBe(true);
expect(execSync).toHaveBeenCalledTimes(2);
});
}
it(`should return false if none of the commands exist on windows`, () => {
Object.defineProperty(process, 'platform', { value: 'win32' }); Object.defineProperty(process, 'platform', { value: 'win32' });
(execSync as Mock).mockImplementation(() => { (execSync as Mock).mockImplementation(() => {
throw new Error(); throw new Error(); // all commands not found
}); });
expect(checkHasEditorType(editor)).toBe(false); expect(checkHasEditorType(editor)).toBe(false);
expect(execSync).toHaveBeenCalledTimes(win32Commands.length);
}); });
}); });
} }
@ -110,31 +153,109 @@ describe('editor utils', () => {
describe('getDiffCommand', () => { describe('getDiffCommand', () => {
const guiEditors: Array<{ const guiEditors: Array<{
editor: EditorType; editor: EditorType;
command: string; commands: string[];
win32Command: string; win32Commands: string[];
}> = [ }> = [
{ editor: 'vscode', command: 'code', win32Command: 'code.cmd' }, { editor: 'vscode', commands: ['code'], win32Commands: ['code.cmd'] },
{ editor: 'vscodium', command: 'codium', win32Command: 'codium.cmd' }, {
{ editor: 'windsurf', command: 'windsurf', win32Command: 'windsurf' }, editor: 'vscodium',
{ editor: 'cursor', command: 'cursor', win32Command: 'cursor' }, commands: ['codium'],
{ editor: 'zed', command: 'zed', win32Command: 'zed' }, win32Commands: ['codium.cmd'],
},
{
editor: 'windsurf',
commands: ['windsurf'],
win32Commands: ['windsurf'],
},
{ editor: 'cursor', commands: ['cursor'], win32Commands: ['cursor'] },
{ editor: 'zed', commands: ['zed', 'zeditor'], win32Commands: ['zed'] },
]; ];
for (const { editor, command, win32Command } of guiEditors) { for (const { editor, commands, win32Commands } of guiEditors) {
it(`should return the correct command for ${editor} on non-windows`, () => { // Non-windows tests
it(`should use first command "${commands[0]}" when it exists on non-windows`, () => {
Object.defineProperty(process, 'platform', { value: 'linux' }); Object.defineProperty(process, 'platform', { value: 'linux' });
(execSync as Mock).mockReturnValue(
Buffer.from(`/usr/bin/${commands[0]}`),
);
const diffCommand = getDiffCommand('old.txt', 'new.txt', editor); const diffCommand = getDiffCommand('old.txt', 'new.txt', editor);
expect(diffCommand).toEqual({ expect(diffCommand).toEqual({
command, command: commands[0],
args: ['--wait', '--diff', 'old.txt', 'new.txt'], args: ['--wait', '--diff', 'old.txt', 'new.txt'],
}); });
}); });
it(`should return the correct command for ${editor} on windows`, () => { if (commands.length > 1) {
Object.defineProperty(process, 'platform', { value: 'win32' }); it(`should use second command "${commands[1]}" when first doesn't exist on non-windows`, () => {
Object.defineProperty(process, 'platform', { value: 'linux' });
(execSync as Mock)
.mockImplementationOnce(() => {
throw new Error(); // first command not found
})
.mockReturnValueOnce(Buffer.from(`/usr/bin/${commands[1]}`)); // second command found
const diffCommand = getDiffCommand('old.txt', 'new.txt', editor); const diffCommand = getDiffCommand('old.txt', 'new.txt', editor);
expect(diffCommand).toEqual({ expect(diffCommand).toEqual({
command: win32Command, command: commands[1],
args: ['--wait', '--diff', 'old.txt', 'new.txt'],
});
});
}
it(`should fallback to last command "${commands[commands.length - 1]}" when none exist on non-windows`, () => {
Object.defineProperty(process, 'platform', { value: 'linux' });
(execSync as Mock).mockImplementation(() => {
throw new Error(); // all commands not found
});
const diffCommand = getDiffCommand('old.txt', 'new.txt', editor);
expect(diffCommand).toEqual({
command: commands[commands.length - 1],
args: ['--wait', '--diff', 'old.txt', 'new.txt'],
});
});
// Windows tests
it(`should use first command "${win32Commands[0]}" when it exists on windows`, () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
(execSync as Mock).mockReturnValue(
Buffer.from(`C:\\Program Files\\...\\${win32Commands[0]}`),
);
const diffCommand = getDiffCommand('old.txt', 'new.txt', editor);
expect(diffCommand).toEqual({
command: win32Commands[0],
args: ['--wait', '--diff', 'old.txt', 'new.txt'],
});
});
if (win32Commands.length > 1) {
it(`should use second command "${win32Commands[1]}" when first doesn't exist on windows`, () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
(execSync as Mock)
.mockImplementationOnce(() => {
throw new Error(); // first command not found
})
.mockReturnValueOnce(
Buffer.from(`C:\\Program Files\\...\\${win32Commands[1]}`),
); // second command found
const diffCommand = getDiffCommand('old.txt', 'new.txt', editor);
expect(diffCommand).toEqual({
command: win32Commands[1],
args: ['--wait', '--diff', 'old.txt', 'new.txt'],
});
});
}
it(`should fallback to last command "${win32Commands[win32Commands.length - 1]}" when none exist on windows`, () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
(execSync as Mock).mockImplementation(() => {
throw new Error(); // all commands not found
});
const diffCommand = getDiffCommand('old.txt', 'new.txt', editor);
expect(diffCommand).toEqual({
command: win32Commands[win32Commands.length - 1],
args: ['--wait', '--diff', 'old.txt', 'new.txt'], args: ['--wait', '--diff', 'old.txt', 'new.txt'],
}); });
}); });

View File

@ -44,21 +44,28 @@ function commandExists(cmd: string): boolean {
} }
} }
const editorCommands: Record<EditorType, { win32: string; default: string }> = { /**
vscode: { win32: 'code.cmd', default: 'code' }, * Editor command configurations for different platforms.
vscodium: { win32: 'codium.cmd', default: 'codium' }, * Each editor can have multiple possible command names, listed in order of preference.
windsurf: { win32: 'windsurf', default: 'windsurf' }, */
cursor: { win32: 'cursor', default: 'cursor' }, const editorCommands: Record<
vim: { win32: 'vim', default: 'vim' }, EditorType,
neovim: { win32: 'nvim', default: 'nvim' }, { win32: string[]; default: string[] }
zed: { win32: 'zed', default: 'zed' }, > = {
vscode: { win32: ['code.cmd'], default: ['code'] },
vscodium: { win32: ['codium.cmd'], default: ['codium'] },
windsurf: { win32: ['windsurf'], default: ['windsurf'] },
cursor: { win32: ['cursor'], default: ['cursor'] },
vim: { win32: ['vim'], default: ['vim'] },
neovim: { win32: ['nvim'], default: ['nvim'] },
zed: { win32: ['zed'], default: ['zed', 'zeditor'] },
}; };
export function checkHasEditorType(editor: EditorType): boolean { export function checkHasEditorType(editor: EditorType): boolean {
const commandConfig = editorCommands[editor]; const commandConfig = editorCommands[editor];
const command = const commands =
process.platform === 'win32' ? commandConfig.win32 : commandConfig.default; process.platform === 'win32' ? commandConfig.win32 : commandConfig.default;
return commandExists(command); return commands.some((cmd) => commandExists(cmd));
} }
export function allowEditorTypeInSandbox(editor: EditorType): boolean { export function allowEditorTypeInSandbox(editor: EditorType): boolean {
@ -92,8 +99,12 @@ export function getDiffCommand(
return null; return null;
} }
const commandConfig = editorCommands[editor]; const commandConfig = editorCommands[editor];
const command = const commands =
process.platform === 'win32' ? commandConfig.win32 : commandConfig.default; process.platform === 'win32' ? commandConfig.win32 : commandConfig.default;
const command =
commands.slice(0, -1).find((cmd) => commandExists(cmd)) ||
commands[commands.length - 1];
switch (editor) { switch (editor) {
case 'vscode': case 'vscode':
case 'vscodium': case 'vscodium':