|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { execSync, spawn } from 'child_process'; |
|
|
|
|
|
export type EditorType = 'vscode' | 'windsurf' | 'cursor' | 'vim' | 'zed'; |
|
|
|
|
|
function isValidEditorType(editor: string): editor is EditorType { |
|
|
return ['vscode', 'windsurf', 'cursor', 'vim', 'zed'].includes(editor); |
|
|
} |
|
|
|
|
|
interface DiffCommand { |
|
|
command: string; |
|
|
args: string[]; |
|
|
} |
|
|
|
|
|
function commandExists(cmd: string): boolean { |
|
|
try { |
|
|
execSync( |
|
|
process.platform === 'win32' ? `where.exe ${cmd}` : `command -v ${cmd}`, |
|
|
{ stdio: 'ignore' }, |
|
|
); |
|
|
return true; |
|
|
} catch { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
const editorCommands: Record<EditorType, { win32: string; default: string }> = { |
|
|
vscode: { win32: 'code.cmd', default: 'code' }, |
|
|
windsurf: { win32: 'windsurf', default: 'windsurf' }, |
|
|
cursor: { win32: 'cursor', default: 'cursor' }, |
|
|
vim: { win32: 'vim', default: 'vim' }, |
|
|
zed: { win32: 'zed', default: 'zed' }, |
|
|
}; |
|
|
|
|
|
export function checkHasEditorType(editor: EditorType): boolean { |
|
|
const commandConfig = editorCommands[editor]; |
|
|
const command = |
|
|
process.platform === 'win32' ? commandConfig.win32 : commandConfig.default; |
|
|
return commandExists(command); |
|
|
} |
|
|
|
|
|
export function allowEditorTypeInSandbox(editor: EditorType): boolean { |
|
|
const notUsingSandbox = !process.env.SANDBOX; |
|
|
if (['vscode', 'windsurf', 'cursor', 'zed'].includes(editor)) { |
|
|
return notUsingSandbox; |
|
|
} |
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function isEditorAvailable(editor: string | undefined): boolean { |
|
|
if (editor && isValidEditorType(editor)) { |
|
|
return ( |
|
|
checkHasEditorType(editor as EditorType) && |
|
|
allowEditorTypeInSandbox(editor as EditorType) |
|
|
); |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function getDiffCommand( |
|
|
oldPath: string, |
|
|
newPath: string, |
|
|
editor: EditorType, |
|
|
): DiffCommand | null { |
|
|
if (!isValidEditorType(editor)) { |
|
|
return null; |
|
|
} |
|
|
const commandConfig = editorCommands[editor]; |
|
|
const command = |
|
|
process.platform === 'win32' ? commandConfig.win32 : commandConfig.default; |
|
|
switch (editor) { |
|
|
case 'vscode': |
|
|
case 'windsurf': |
|
|
case 'cursor': |
|
|
case 'zed': |
|
|
return { command, args: ['--wait', '--diff', oldPath, newPath] }; |
|
|
case 'vim': |
|
|
return { |
|
|
command: 'vim', |
|
|
args: [ |
|
|
'-d', |
|
|
|
|
|
'-i', |
|
|
'NONE', |
|
|
|
|
|
'-c', |
|
|
'wincmd h | set readonly | wincmd l', |
|
|
|
|
|
'-c', |
|
|
'highlight DiffAdd cterm=bold ctermbg=22 guibg=#005f00 | highlight DiffChange cterm=bold ctermbg=24 guibg=#005f87 | highlight DiffText ctermbg=21 guibg=#0000af | highlight DiffDelete ctermbg=52 guibg=#5f0000', |
|
|
|
|
|
'-c', |
|
|
'set showtabline=2 | set tabline=[Instructions]\\ :wqa(save\\ &\\ quit)\\ \\|\\ i/esc(toggle\\ edit\\ mode)', |
|
|
'-c', |
|
|
'wincmd h | setlocal statusline=OLD\\ FILE', |
|
|
'-c', |
|
|
'wincmd l | setlocal statusline=%#StatusBold#NEW\\ FILE\\ :wqa(save\\ &\\ quit)\\ \\|\\ i/esc(toggle\\ edit\\ mode)', |
|
|
|
|
|
'-c', |
|
|
'autocmd WinClosed * wqa', |
|
|
oldPath, |
|
|
newPath, |
|
|
], |
|
|
}; |
|
|
default: |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function openDiff( |
|
|
oldPath: string, |
|
|
newPath: string, |
|
|
editor: EditorType, |
|
|
): Promise<void> { |
|
|
const diffCommand = getDiffCommand(oldPath, newPath, editor); |
|
|
if (!diffCommand) { |
|
|
console.error('No diff tool available. Install a supported editor.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
switch (editor) { |
|
|
case 'vscode': |
|
|
case 'windsurf': |
|
|
case 'cursor': |
|
|
case 'zed': |
|
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
const childProcess = spawn(diffCommand.command, diffCommand.args, { |
|
|
stdio: 'inherit', |
|
|
shell: true, |
|
|
}); |
|
|
|
|
|
childProcess.on('close', (code) => { |
|
|
if (code === 0) { |
|
|
resolve(); |
|
|
} else { |
|
|
reject(new Error(`${editor} exited with code ${code}`)); |
|
|
} |
|
|
}); |
|
|
|
|
|
childProcess.on('error', (error) => { |
|
|
reject(error); |
|
|
}); |
|
|
}); |
|
|
|
|
|
case 'vim': { |
|
|
|
|
|
const command = |
|
|
process.platform === 'win32' |
|
|
? `${diffCommand.command} ${diffCommand.args.join(' ')}` |
|
|
: `${diffCommand.command} ${diffCommand.args.map((arg) => `"${arg}"`).join(' ')}`; |
|
|
execSync(command, { |
|
|
stdio: 'inherit', |
|
|
encoding: 'utf8', |
|
|
}); |
|
|
break; |
|
|
} |
|
|
|
|
|
default: |
|
|
throw new Error(`Unsupported editor: ${editor}`); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error(error); |
|
|
} |
|
|
} |
|
|
|