MCPcopy Create free account
hub / github.com/AnukarOP/claude-code-leaked / openFileInExternalEditor

Function openFileInExternalEditor

source code/utils/editor.ts:83–164  ·  view source on GitHub ↗
(
  filePath: string,
  line?: number,
)

Source from the content-addressed store, hash-verified

81 * Returns true if the editor was launched, false if no editor is available.
82 */
83export function openFileInExternalEditor(
84 filePath: string,
85 line?: number,
86): boolean {
87 const editor = getExternalEditor()
88 if (!editor) return false
89
90 // Spawn the user's actual binary (preserves code-insiders, abs paths, etc.).
91 // Split into binary + extra args so multi-word values like 'start /wait
92 // notepad' or 'code --wait' propagate all tokens to spawn.
93 const parts = editor.split(' ')
94 const base = parts[0] ?? editor
95 const editorArgs = parts.slice(1)
96 const guiFamily = classifyGuiEditor(editor)
97
98 if (guiFamily) {
99 const gotoArgv = guiGotoArgv(guiFamily, filePath, line)
100 const detachedOpts: SpawnOptions = { detached: true, stdio: 'ignore' }
101 let child
102 if (process.platform === 'win32') {
103 // shell: true on win32 so code.cmd / cursor.cmd / windsurf.cmd resolve —
104 // CreateProcess can't execute .cmd/.bat directly. Assemble quoted command
105 // string; cmd.exe doesn't expand $() or backticks inside double quotes.
106 // Quote each arg so paths with spaces survive the shell join.
107 const gotoStr = gotoArgv.map(a => `"${a}"`).join(' ')
108 child = spawn(`${editor} ${gotoStr}`, { ...detachedOpts, shell: true })
109 } else {
110 // POSIX: argv array with no shell — injection-safe. shell: true would
111 // expand $() / backticks inside double quotes, and filePath is
112 // filesystem-sourced (possible RCE from a malicious repo filename).
113 child = spawn(base, [...editorArgs, ...gotoArgv], detachedOpts)
114 }
115 // spawn() emits ENOENT asynchronously. ENOENT on $VISUAL/$EDITOR is a
116 // user-config error, not an internal bug — don't pollute error telemetry.
117 child.on('error', e =>
118 logForDebugging(`editor spawn failed: ${e}`, { level: 'error' }),
119 )
120 child.unref()
121 return true
122 }
123
124 // Terminal editor — needs alt-screen handoff since it takes over the
125 // terminal. Blocks until the editor exits.
126 const inkInstance = instances.get(process.stdout)
127 if (!inkInstance) return false
128 // Only prepend +N for editors known to support it — notepad treats +42 as a
129 // filename to open. Test basename so /home/vim/bin/kak doesn't match 'vim'
130 // via the directory segment.
131 const useGotoLine = line && PLUS_N_EDITORS.test(basename(base))
132 inkInstance.enterAlternateScreen()
133 try {
134 const syncOpts: SpawnSyncOptions = { stdio: 'inherit' }
135 let result
136 if (process.platform === 'win32') {
137 // On Windows use shell: true so cmd.exe builtins like `start` resolve.
138 // shell: true joins args unquoted, so assemble the command string with
139 // explicit quoting ourselves (matching promptEditor.ts:74). spawnSync
140 // returns errors in .error rather than throwing.

Callers 3

GlobalSearchDialogFunction · 0.85
QuickOpenDialogFunction · 0.85
REPLFunction · 0.85

Calls 7

classifyGuiEditorFunction · 0.85
guiGotoArgvFunction · 0.85
spawnFunction · 0.85
logForDebuggingFunction · 0.85
enterAlternateScreenMethod · 0.80
exitAlternateScreenMethod · 0.80
getMethod · 0.45

Tested by

no test coverage detected