Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
5.7 kB · 188 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189import { expandPastedTextRefs, formatPastedTextRef, getPastedTextRefNumLines,} from '../history.js'import instances from '../ink/instances.js'import type { PastedContent } from './config.js'import { classifyGuiEditor, getExternalEditor } from './editor.js'import { execSync_DEPRECATED } from './execSyncWrapper.js'import { getFsImplementation } from './fsOperations.js'import { toIDEDisplayName } from './ide.js'import { writeFileSync_DEPRECATED } from './slowOperations.js'import { generateTempFilePath } from './tempfile.js'
// Map of editor command overrides (e.g., to add wait flags)const EDITOR_OVERRIDES: Record<string, string> = { code: 'code -w', // VS Code: wait for file to be closed subl: 'subl --wait', // Sublime Text: wait for file to be closed}
function isGuiEditor(editor: string): boolean { return classifyGuiEditor(editor) !== undefined}
export type EditorResult = { content: string | null error?: string}
// sync IO: called from sync context (React components, sync command handlers)export function editFileInEditor(filePath: string): EditorResult { const fs = getFsImplementation() const inkInstance = instances.get(process.stdout) if (!inkInstance) { throw new Error('Ink instance not found - cannot pause rendering') }
const editor = getExternalEditor() if (!editor) { return { content: null } }
try { fs.statSync(filePath) } catch { return { content: null } }
const useAlternateScreen = !isGuiEditor(editor)
if (useAlternateScreen) { // Terminal editors (vi, nano, etc.) take over the terminal. Delegate to // Ink's alt-screen-aware handoff so fullscreen mode (where <AlternateScreen> // already entered alt screen) doesn't get knocked back to the main buffer // by a hardcoded ?1049l. enterAlternateScreen() internally calls pause() // and suspendStdin(); exitAlternateScreen() undoes both and resets frame // state so the next render writes from scratch. inkInstance.enterAlternateScreen() } else { // GUI editors (code, subl, etc.) open in a separate window — just pause // Ink and release stdin while they're open. inkInstance.pause() inkInstance.suspendStdin() }
try { // Use override command if available, otherwise use the editor as-is const editorCommand = EDITOR_OVERRIDES[editor] ?? editor execSync_DEPRECATED(`${editorCommand} "${filePath}"`, { stdio: 'inherit', })
// Read the edited content const editedContent = fs.readFileSync(filePath, { encoding: 'utf-8' }) return { content: editedContent } } catch (err) { if ( typeof err === 'object' && err !== null && 'status' in err && typeof (err as { status: unknown }).status === 'number' ) { const status = (err as { status: number }).status if (status !== 0) { const editorName = toIDEDisplayName(editor) return { content: null, error: `${editorName} exited with code ${status}`, } } } return { content: null } } finally { if (useAlternateScreen) { inkInstance.exitAlternateScreen() } else { inkInstance.resumeStdin() inkInstance.resume() } }}
/** * Re-collapse expanded pasted text by finding content that matches * pastedContents and replacing it with references. */function recollapsePastedContent( editedPrompt: string, originalPrompt: string, pastedContents: Record<number, PastedContent>,): string { let collapsed = editedPrompt
// Find pasted content in the edited text and re-collapse it for (const [id, content] of Object.entries(pastedContents)) { if (content.type === 'text') { const pasteId = parseInt(id) const contentStr = content.content
// Check if this exact content exists in the edited prompt const contentIndex = collapsed.indexOf(contentStr) if (contentIndex !== -1) { // Replace with reference const numLines = getPastedTextRefNumLines(contentStr) const ref = formatPastedTextRef(pasteId, numLines) collapsed = collapsed.slice(0, contentIndex) + ref + collapsed.slice(contentIndex + contentStr.length) } } }
return collapsed}
// sync IO: called from sync context (React components, sync command handlers)export function editPromptInEditor( currentPrompt: string, pastedContents?: Record<number, PastedContent>,): EditorResult { const fs = getFsImplementation() const tempFile = generateTempFilePath()
try { // Expand any pasted text references before editing const expandedPrompt = pastedContents ? expandPastedTextRefs(currentPrompt, pastedContents) : currentPrompt
// Write expanded prompt to temp file writeFileSync_DEPRECATED(tempFile, expandedPrompt, { encoding: 'utf-8', flush: true, })
// Delegate to editFileInEditor const result = editFileInEditor(tempFile)
if (result.content === null) { return result }
// Trim a single trailing newline if present (common editor behavior) let finalContent = result.content if (finalContent.endsWith('\n') && !finalContent.endsWith('\n\n')) { finalContent = finalContent.slice(0, -1) }
// Re-collapse pasted content if it wasn't edited if (pastedContents) { finalContent = recollapsePastedContent( finalContent, currentPrompt, pastedContents, ) }
return { content: finalContent } } finally { // Clean up temp file try { fs.unlinkSync(tempFile) } catch { // Ignore cleanup errors } }}