| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889 |
- import type { CliRenderer } from "@opentui/core"
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"
- import { readFile, rm, writeFile } from "node:fs/promises"
- import os from "node:os"
- import path from "node:path"
- import { spawn } from "node:child_process"
- import type { Stream } from "node:stream"
- import { resolveZedDbPath, resolveZedSelection } from "./editor-zed"
- export { normalizePromptContent } from "./prompt/content"
- type EditorStdio = "inherit" | "pipe" | "ignore" | number | Stream
- export async function openEditor(input: { value: string; renderer: CliRenderer; cwd?: string; stdin?: EditorStdio }) {
- const editor = process.env.VISUAL || process.env.EDITOR
- if (!editor) return
- const file = path.join(os.tmpdir(), `${Date.now()}.md`)
- await writeFile(file, input.value)
- input.renderer.suspend()
- input.renderer.currentRenderBuffer.clear()
- try {
- await new Promise<void>((resolve, reject) => {
- const parts = editor.split(" ")
- const child = spawn(parts[0]!, [...parts.slice(1), file], {
- cwd: input.cwd && existsSync(input.cwd) ? input.cwd : process.cwd(),
- stdio: [input.stdin ?? "inherit", "inherit", "inherit"],
- shell: process.platform === "win32",
- })
- child.on("error", reject)
- child.on("exit", (code, signal) => {
- if (code === 0) return resolve()
- reject(new Error(`Editor exited with ${signal ? `signal ${signal}` : `code ${code}`}`))
- })
- })
- return (await readFile(file, "utf8")) || undefined
- } finally {
- await rm(file, { force: true }).catch(() => {})
- input.renderer.currentRenderBuffer.clear()
- input.renderer.resume()
- input.renderer.requestRender()
- }
- }
- export function discoverEditorConnection(directory: string) {
- const root = path.join(os.homedir(), ".claude", "ide")
- const contains = (parent: string) => {
- const resolved = path.resolve(parent)
- const relative = path.relative(resolved, path.resolve(directory))
- return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)) ? resolved.length : 0
- }
- try {
- return readdirSync(root)
- .filter((entry) => entry.endsWith(".lock"))
- .flatMap((entry) => {
- const file = path.join(root, entry)
- const port = Number.parseInt(path.basename(file, ".lock"), 10)
- if (!Number.isInteger(port) || port <= 0 || port > 65535) return []
- try {
- const value = JSON.parse(readFileSync(file, "utf8")) as Record<string, unknown>
- if (value.transport !== undefined && value.transport !== "ws") return []
- const folders = Array.isArray(value.workspaceFolders)
- ? value.workspaceFolders.filter((item): item is string => typeof item === "string")
- : []
- const score = Math.max(0, ...folders.map(contains))
- if (!score) return []
- return [
- {
- url: `ws://127.0.0.1:${port}`,
- authToken: typeof value.authToken === "string" ? value.authToken : undefined,
- source: `lock:${port}`,
- score,
- mtime: statSync(file).mtimeMs,
- },
- ]
- } catch {
- return []
- }
- })
- .sort((left, right) => right.score - left.score || right.mtime - left.mtime)
- .map(({ url, authToken, source }) => ({ url, authToken, source }))[0]
- } catch {
- return undefined
- }
- }
- export const editorIntegration = {
- connection: discoverEditorConnection,
- selection: (directory: string) => resolveZedSelection(resolveZedDbPath() ?? "", directory),
- }
|