cloudaxe-opencode/packages/opencode/src/cli/cmd/tui/util/editor.ts
2026-06-02 14:43:46 +02:00

83 lines
2.7 KiB
TypeScript

import { defer } from "@/util/defer"
import { mkdtemp, rm } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
import type { CliRenderer } from "@opentui/core"
import { Filesystem } from "@/util/filesystem"
import { Process } from "@/util/process"
import systemOpen from "open"
export async function open(opts: { value: string; renderer: CliRenderer; cwd: string }): Promise<string | undefined> {
const editor = configuredEditor()
if (!editor) return
const draft = await createDraft(opts.value)
await using _ = defer(draft.remove)
await openEditor(draft.filepath, opts, editor)
return Filesystem.readText(draft.filepath)
}
export async function openTemporary(opts: { value: string; renderer: CliRenderer; cwd: string }) {
const draft = await createDraft(opts.value)
const mode = await openPath(draft.filepath, opts).catch(async (error) => {
await draft.remove()
throw error
})
// System openers detach, so retain the draft in the OS temp dir for the application to read.
if (mode === "system") return
await draft.remove()
}
export async function openFile(opts: { filepath: string; renderer: CliRenderer; cwd: string; directory: string }) {
await openPath(Filesystem.resolveFilePath(opts.directory, opts.filepath), opts)
}
async function createDraft(value: string) {
const dir = await mkdtemp(join(tmpdir(), "opencode-editor-"))
const filepath = join(dir, "draft.md")
await Filesystem.write(filepath, value).catch(async (error) => {
await rm(dir, { force: true, recursive: true })
throw error
})
return {
filepath,
remove: () => rm(dir, { force: true, recursive: true }),
}
}
async function openPath(filepath: string, opts: { renderer: CliRenderer; cwd: string }) {
const editor = configuredEditor()
if (editor) {
await openEditor(filepath, opts, editor)
return "editor" as const
}
await systemOpen(filepath)
return "system" as const
}
function configuredEditor() {
return process.env["VISUAL"]?.trim() || process.env["EDITOR"]?.trim()
}
async function openEditor(filepath: string, opts: { renderer: CliRenderer; cwd: string }, editor: string) {
opts.renderer.suspend()
opts.renderer.currentRenderBuffer.clear()
try {
const parts = editor.split(/\s+/)
const proc = Process.spawn([...parts, filepath], {
cwd: opts.cwd,
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
shell: process.platform === "win32",
})
const code = await proc.exited
if (code !== 0) throw new Error(`Editor exited with code ${code}`)
} finally {
opts.renderer.currentRenderBuffer.clear()
opts.renderer.resume()
opts.renderer.requestRender()
}
}
export * as Editor from "./editor"