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

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