diff --git a/packages/opencode/src/cli/cmd/debug/file.ts b/packages/opencode/src/cli/cmd/debug/file.ts index 96c4d91b..021c49db 100644 --- a/packages/opencode/src/cli/cmd/debug/file.ts +++ b/packages/opencode/src/cli/cmd/debug/file.ts @@ -2,12 +2,6 @@ import { File } from "../../../file" import { bootstrap } from "../../bootstrap" import { cmd } from "../cmd" -export const FileCommand = cmd({ - command: "file", - builder: (yargs) => yargs.command(FileReadCommand).demandCommand(), - async handler() {}, -}) - const FileReadCommand = cmd({ command: "read ", builder: (yargs) => @@ -23,3 +17,21 @@ const FileReadCommand = cmd({ }) }, }) + +const FileStatusCommand = cmd({ + command: "status", + builder: (yargs) => yargs, + async handler() { + await bootstrap({ cwd: process.cwd() }, async () => { + const status = await File.status() + console.log(JSON.stringify(status, null, 2)) + }) + }, +}) + +export const FileCommand = cmd({ + command: "file", + builder: (yargs) => + yargs.command(FileReadCommand).command(FileStatusCommand).demandCommand(), + async handler() {}, +}) diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index 4f487cab..29331a58 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -3,13 +3,13 @@ import { Bus } from "../bus" import { $ } from "bun" import { createPatch } from "diff" import path from "path" -import { status } from "isomorphic-git" +import * as git from "isomorphic-git" import { App } from "../app/app" import fs from "fs" import { Log } from "../util/log" export namespace File { - const log = Log.create({ service: "files" }) + const log = Log.create({ service: "file" }) export const Event = { Edited: Bus.event( @@ -20,6 +20,84 @@ export namespace File { ), } + export async function status() { + const app = App.info() + if (!app.git) return [] + + // Get all changed files with line counts in one command + const diffOutput = await $`git diff --numstat HEAD` + .cwd(app.path.root) + .quiet() + .nothrow() + .text() + + const changedFiles = [] + + if (diffOutput.trim()) { + const lines = diffOutput.trim().split("\n") + for (const line of lines) { + const [added, removed, filepath] = line.split("\t") + changedFiles.push({ + file: filepath, + added: added === "-" ? 0 : parseInt(added, 10), + removed: removed === "-" ? 0 : parseInt(removed, 10), + status: "modified", + }) + } + } + + // Get untracked files + const untrackedOutput = await $`git ls-files --others --exclude-standard` + .cwd(app.path.root) + .quiet() + .nothrow() + .text() + + if (untrackedOutput.trim()) { + const untrackedFiles = untrackedOutput.trim().split("\n") + for (const filepath of untrackedFiles) { + try { + const content = await Bun.file( + path.join(app.path.root, filepath), + ).text() + const lines = content.split("\n").length + changedFiles.push({ + file: filepath, + added: lines, + removed: 0, + status: "added", + }) + } catch { + continue + } + } + } + + // Get deleted files + const deletedOutput = await $`git diff --name-only --diff-filter=D HEAD` + .cwd(app.path.root) + .quiet() + .nothrow() + .text() + + if (deletedOutput.trim()) { + const deletedFiles = deletedOutput.trim().split("\n") + for (const filepath of deletedFiles) { + changedFiles.push({ + file: filepath, + added: 0, + removed: 0, // Could get original line count but would require another git command + status: "deleted", + }) + } + } + + return changedFiles.map((x) => ({ + ...x, + file: path.relative(app.path.cwd, path.join(app.path.root, x.file)), + })) + } + export async function read(file: string) { using _ = log.time("read", { file }) const app = App.info() @@ -27,7 +105,7 @@ export namespace File { const content = await Bun.file(full).text() if (app.git) { const rel = path.relative(app.path.root, full) - const diff = await status({ + const diff = await git.status({ fs, dir: app.path.root, filepath: rel, @@ -38,7 +116,9 @@ export namespace File { .quiet() .nothrow() .text() - const patch = createPatch(file, original, content) + const patch = createPatch(file, original, content, "old", "new", { + context: Infinity, + }) return patch } } diff --git a/packages/opencode/src/file/watch.ts b/packages/opencode/src/file/watch.ts index 2a702984..8bc70cd8 100644 --- a/packages/opencode/src/file/watch.ts +++ b/packages/opencode/src/file/watch.ts @@ -22,28 +22,30 @@ export namespace FileWatcher { "file.watcher", () => { const app = App.use() - const watcher = fs.watch( - app.info.path.cwd, - { recursive: true }, - (event, file) => { - log.info("change", { file, event }) - if (!file) return - // for some reason async local storage is lost here - // https://github.com/oven-sh/bun/issues/20754 - App.provideExisting(app, async () => { - Bus.publish(Event.Updated, { - file, - event, + try { + const watcher = fs.watch( + app.info.path.cwd, + { recursive: true }, + (event, file) => { + log.info("change", { file, event }) + if (!file) return + // for some reason async local storage is lost here + // https://github.com/oven-sh/bun/issues/20754 + App.provideExisting(app, async () => { + Bus.publish(Event.Updated, { + file, + event, + }) }) - }) - }, - ) - return { - watcher, + }, + ) + return { watcher } + } finally { + return {} } }, async (state) => { - state.watcher.close() + state.watcher?.close() }, )() }