diff --git a/packages/cli/src/ui/editors/editorSettingsManager.ts b/packages/cli/src/ui/editors/editorSettingsManager.ts index 7e45b42e..ae089902 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', + emacs: 'Emacs', neovim: 'Neovim', }; diff --git a/packages/core/src/utils/editor.test.ts b/packages/core/src/utils/editor.test.ts index a86d6f59..203223ae 100644 --- a/packages/core/src/utils/editor.test.ts +++ b/packages/core/src/utils/editor.test.ts @@ -70,6 +70,7 @@ describe('editor utils', () => { { editor: 'vim', commands: ['vim'], win32Commands: ['vim'] }, { editor: 'neovim', commands: ['nvim'], win32Commands: ['nvim'] }, { editor: 'zed', commands: ['zed', 'zeditor'], win32Commands: ['zed'] }, + { editor: 'emacs', commands: ['emacs'], win32Commands: ['emacs.exe'] }, ]; for (const { editor, commands, win32Commands } of testCases) { @@ -297,6 +298,14 @@ describe('editor utils', () => { }); } + it('should return the correct command for emacs', () => { + const command = getDiffCommand('old.txt', 'new.txt', 'emacs'); + expect(command).toEqual({ + command: 'emacs', + args: ['--eval', '(ediff "old.txt" "new.txt")'], + }); + }); + it('should return null for an unsupported editor', () => { // @ts-expect-error Testing unsupported editor const command = getDiffCommand('old.txt', 'new.txt', 'foobar'); @@ -372,7 +381,7 @@ describe('editor utils', () => { }); } - const execSyncEditors: EditorType[] = ['vim', 'neovim']; + const execSyncEditors: EditorType[] = ['vim', 'neovim', 'emacs']; for (const editor of execSyncEditors) { it(`should call execSync for ${editor} on non-windows`, async () => { Object.defineProperty(process, 'platform', { value: 'linux' }); @@ -425,6 +434,15 @@ describe('editor utils', () => { expect(allowEditorTypeInSandbox('vim')).toBe(true); }); + it('should allow emacs in sandbox mode', () => { + process.env.SANDBOX = 'sandbox'; + expect(allowEditorTypeInSandbox('emacs')).toBe(true); + }); + + it('should allow emacs when not in sandbox mode', () => { + expect(allowEditorTypeInSandbox('emacs')).toBe(true); + }); + it('should allow neovim in sandbox mode', () => { process.env.SANDBOX = 'sandbox'; expect(allowEditorTypeInSandbox('neovim')).toBe(true); @@ -490,6 +508,12 @@ describe('editor utils', () => { expect(isEditorAvailable('vim')).toBe(true); }); + it('should return true for emacs when installed and in sandbox mode', () => { + (execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/emacs')); + process.env.SANDBOX = 'sandbox'; + expect(isEditorAvailable('emacs')).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'; diff --git a/packages/core/src/utils/editor.ts b/packages/core/src/utils/editor.ts index 2d65d525..704d1cbb 100644 --- a/packages/core/src/utils/editor.ts +++ b/packages/core/src/utils/editor.ts @@ -13,7 +13,8 @@ export type EditorType = | 'cursor' | 'vim' | 'neovim' - | 'zed'; + | 'zed' + | 'emacs'; function isValidEditorType(editor: string): editor is EditorType { return [ @@ -24,6 +25,7 @@ function isValidEditorType(editor: string): editor is EditorType { 'vim', 'neovim', 'zed', + 'emacs', ].includes(editor); } @@ -59,6 +61,7 @@ const editorCommands: Record< vim: { win32: ['vim'], default: ['vim'] }, neovim: { win32: ['nvim'], default: ['nvim'] }, zed: { win32: ['zed'], default: ['zed', 'zeditor'] }, + emacs: { win32: ['emacs.exe'], default: ['emacs'] }, }; export function checkHasEditorType(editor: EditorType): boolean { @@ -73,6 +76,7 @@ export function allowEditorTypeInSandbox(editor: EditorType): boolean { if (['vscode', 'vscodium', 'windsurf', 'cursor', 'zed'].includes(editor)) { return notUsingSandbox; } + // For terminal-based editors like vim and emacs, allow in sandbox. return true; } @@ -141,6 +145,11 @@ export function getDiffCommand( newPath, ], }; + case 'emacs': + return { + command: 'emacs', + args: ['--eval', `(ediff "${oldPath}" "${newPath}")`], + }; default: return null; } @@ -190,6 +199,7 @@ export async function openDiff( }); case 'vim': + case 'emacs': case 'neovim': { // Use execSync for terminal-based editors const command =