diff --git a/packages/cli/src/ui/editors/editorSettingsManager.ts b/packages/cli/src/ui/editors/editorSettingsManager.ts index 550569c4..7e45b42e 100644 --- a/packages/cli/src/ui/editors/editorSettingsManager.ts +++ b/packages/cli/src/ui/editors/editorSettingsManager.ts @@ -23,6 +23,7 @@ export const EDITOR_DISPLAY_NAMES: Record = { windsurf: 'Windsurf', cursor: 'Cursor', vim: 'Vim', + neovim: 'Neovim', }; class EditorSettingsManager { @@ -36,6 +37,7 @@ class EditorSettingsManager { 'windsurf', 'cursor', 'vim', + 'neovim', ]; this.availableEditors = [ { diff --git a/packages/core/src/utils/editor.test.ts b/packages/core/src/utils/editor.test.ts index 09bd496f..382e5e18 100644 --- a/packages/core/src/utils/editor.test.ts +++ b/packages/core/src/utils/editor.test.ts @@ -60,6 +60,7 @@ describe('editor utils', () => { { 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' }, ]; @@ -139,31 +140,41 @@ describe('editor utils', () => { }); } - it('should return the correct command for vim', () => { - const command = getDiffCommand('old.txt', 'new.txt', 'vim'); - expect(command).toEqual({ - 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', - 'old.txt', - 'new.txt', - ], + const terminalEditors: Array<{ + editor: EditorType; + command: string; + }> = [ + { editor: 'vim', command: 'vim' }, + { editor: 'neovim', command: 'nvim' }, + ]; + + for (const { editor, command } of terminalEditors) { + it(`should return the correct command for ${editor}`, () => { + const diffCommand = getDiffCommand('old.txt', 'new.txt', editor); + expect(diffCommand).toEqual({ + command, + 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', + 'old.txt', + 'new.txt', + ], + }); }); - }); + } it('should return null for an unsupported editor', () => { // @ts-expect-error Testing unsupported editor @@ -240,7 +251,7 @@ describe('editor utils', () => { }); } - const execSyncEditors: EditorType[] = ['vim']; + const execSyncEditors: EditorType[] = ['vim', 'neovim']; for (const editor of execSyncEditors) { it(`should call execSync for ${editor} on non-windows`, async () => { Object.defineProperty(process, 'platform', { value: 'linux' }); @@ -293,6 +304,15 @@ describe('editor utils', () => { expect(allowEditorTypeInSandbox('vim')).toBe(true); }); + it('should allow neovim in sandbox mode', () => { + process.env.SANDBOX = 'sandbox'; + expect(allowEditorTypeInSandbox('neovim')).toBe(true); + }); + + it('should allow neovim when not in sandbox mode', () => { + expect(allowEditorTypeInSandbox('neovim')).toBe(true); + }); + const guiEditors: EditorType[] = [ 'vscode', 'vscodium', @@ -348,5 +368,11 @@ describe('editor utils', () => { process.env.SANDBOX = 'sandbox'; expect(isEditorAvailable('vim')).toBe(true); }); + + it('should return true for neovim when installed and in sandbox mode', () => { + (execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/nvim')); + process.env.SANDBOX = 'sandbox'; + expect(isEditorAvailable('neovim')).toBe(true); + }); }); }); diff --git a/packages/core/src/utils/editor.ts b/packages/core/src/utils/editor.ts index 174dae3e..ac83b409 100644 --- a/packages/core/src/utils/editor.ts +++ b/packages/core/src/utils/editor.ts @@ -12,12 +12,19 @@ export type EditorType = | 'windsurf' | 'cursor' | 'vim' + | 'neovim' | 'zed'; function isValidEditorType(editor: string): editor is EditorType { - return ['vscode', 'vscodium', 'windsurf', 'cursor', 'vim', 'zed'].includes( - editor, - ); + return [ + 'vscode', + 'vscodium', + 'windsurf', + 'cursor', + 'vim', + 'neovim', + 'zed', + ].includes(editor); } interface DiffCommand { @@ -43,6 +50,7 @@ const editorCommands: Record = { 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' }, }; @@ -97,8 +105,9 @@ export function getDiffCommand( case 'zed': return { command, args: ['--wait', '--diff', oldPath, newPath] }; case 'vim': + case 'neovim': return { - command: 'vim', + command, args: [ '-d', // skip viminfo file to avoid E138 errors @@ -172,7 +181,8 @@ export async function openDiff( }); }); - case 'vim': { + case 'vim': + case 'neovim': { // Use execSync for terminal-based editors const command = process.platform === 'win32'