feat: add Neovim editor support (#1448)

This commit is contained in:
yuki yano 2025-06-30 02:25:22 +09:00 committed by GitHub
parent 87d4fc0560
commit c860dac233
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 68 additions and 30 deletions

View File

@ -23,6 +23,7 @@ export const EDITOR_DISPLAY_NAMES: Record<EditorType, string> = {
windsurf: 'Windsurf', windsurf: 'Windsurf',
cursor: 'Cursor', cursor: 'Cursor',
vim: 'Vim', vim: 'Vim',
neovim: 'Neovim',
}; };
class EditorSettingsManager { class EditorSettingsManager {
@ -36,6 +37,7 @@ class EditorSettingsManager {
'windsurf', 'windsurf',
'cursor', 'cursor',
'vim', 'vim',
'neovim',
]; ];
this.availableEditors = [ this.availableEditors = [
{ {

View File

@ -60,6 +60,7 @@ describe('editor utils', () => {
{ editor: 'windsurf', command: 'windsurf', win32Command: 'windsurf' }, { editor: 'windsurf', command: 'windsurf', win32Command: 'windsurf' },
{ editor: 'cursor', command: 'cursor', win32Command: 'cursor' }, { editor: 'cursor', command: 'cursor', win32Command: 'cursor' },
{ editor: 'vim', command: 'vim', win32Command: 'vim' }, { editor: 'vim', command: 'vim', win32Command: 'vim' },
{ editor: 'neovim', command: 'nvim', win32Command: 'nvim' },
{ editor: 'zed', command: 'zed', win32Command: 'zed' }, { editor: 'zed', command: 'zed', win32Command: 'zed' },
]; ];
@ -139,31 +140,41 @@ describe('editor utils', () => {
}); });
} }
it('should return the correct command for vim', () => { const terminalEditors: Array<{
const command = getDiffCommand('old.txt', 'new.txt', 'vim'); editor: EditorType;
expect(command).toEqual({ command: string;
command: 'vim', }> = [
args: [ { editor: 'vim', command: 'vim' },
'-d', { editor: 'neovim', command: 'nvim' },
'-i', ];
'NONE',
'-c', for (const { editor, command } of terminalEditors) {
'wincmd h | set readonly | wincmd l', it(`should return the correct command for ${editor}`, () => {
'-c', const diffCommand = getDiffCommand('old.txt', 'new.txt', editor);
'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', expect(diffCommand).toEqual({
'-c', command,
'set showtabline=2 | set tabline=[Instructions]\\ :wqa(save\\ &\\ quit)\\ \\|\\ i/esc(toggle\\ edit\\ mode)', args: [
'-c', '-d',
'wincmd h | setlocal statusline=OLD\\ FILE', '-i',
'-c', 'NONE',
'wincmd l | setlocal statusline=%#StatusBold#NEW\\ FILE\\ :wqa(save\\ &\\ quit)\\ \\|\\ i/esc(toggle\\ edit\\ mode)', '-c',
'-c', 'wincmd h | set readonly | wincmd l',
'autocmd WinClosed * wqa', '-c',
'old.txt', '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',
'new.txt', '-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', () => { it('should return null for an unsupported editor', () => {
// @ts-expect-error Testing 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) { for (const editor of execSyncEditors) {
it(`should call execSync for ${editor} on non-windows`, async () => { it(`should call execSync for ${editor} on non-windows`, async () => {
Object.defineProperty(process, 'platform', { value: 'linux' }); Object.defineProperty(process, 'platform', { value: 'linux' });
@ -293,6 +304,15 @@ describe('editor utils', () => {
expect(allowEditorTypeInSandbox('vim')).toBe(true); 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[] = [ const guiEditors: EditorType[] = [
'vscode', 'vscode',
'vscodium', 'vscodium',
@ -348,5 +368,11 @@ describe('editor utils', () => {
process.env.SANDBOX = 'sandbox'; process.env.SANDBOX = 'sandbox';
expect(isEditorAvailable('vim')).toBe(true); 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);
});
}); });
}); });

View File

@ -12,12 +12,19 @@ export type EditorType =
| 'windsurf' | 'windsurf'
| 'cursor' | 'cursor'
| 'vim' | 'vim'
| 'neovim'
| 'zed'; | 'zed';
function isValidEditorType(editor: string): editor is EditorType { function isValidEditorType(editor: string): editor is EditorType {
return ['vscode', 'vscodium', 'windsurf', 'cursor', 'vim', 'zed'].includes( return [
editor, 'vscode',
); 'vscodium',
'windsurf',
'cursor',
'vim',
'neovim',
'zed',
].includes(editor);
} }
interface DiffCommand { interface DiffCommand {
@ -43,6 +50,7 @@ const editorCommands: Record<EditorType, { win32: string; default: string }> = {
windsurf: { win32: 'windsurf', default: 'windsurf' }, windsurf: { win32: 'windsurf', default: 'windsurf' },
cursor: { win32: 'cursor', default: 'cursor' }, cursor: { win32: 'cursor', default: 'cursor' },
vim: { win32: 'vim', default: 'vim' }, vim: { win32: 'vim', default: 'vim' },
neovim: { win32: 'nvim', default: 'nvim' },
zed: { win32: 'zed', default: 'zed' }, zed: { win32: 'zed', default: 'zed' },
}; };
@ -97,8 +105,9 @@ export function getDiffCommand(
case 'zed': case 'zed':
return { command, args: ['--wait', '--diff', oldPath, newPath] }; return { command, args: ['--wait', '--diff', oldPath, newPath] };
case 'vim': case 'vim':
case 'neovim':
return { return {
command: 'vim', command,
args: [ args: [
'-d', '-d',
// skip viminfo file to avoid E138 errors // 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 // Use execSync for terminal-based editors
const command = const command =
process.platform === 'win32' process.platform === 'win32'