From e1d95538bcc948c30e48e065975c0941a3f579cc Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Wed, 15 Apr 2026 13:30:21 +0200 Subject: [PATCH] chore: remove diff extension --- .pi/extensions/diff.ts | 218 ----------------------------------------- 1 file changed, 218 deletions(-) delete mode 100644 .pi/extensions/diff.ts diff --git a/.pi/extensions/diff.ts b/.pi/extensions/diff.ts deleted file mode 100644 index dcd426b4..00000000 --- a/.pi/extensions/diff.ts +++ /dev/null @@ -1,218 +0,0 @@ -/** - * Diff Extension - * - * /diff command shows modified/deleted/new files from git status and opens - * the selected file in VS Code's diff view. - */ - -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { DynamicBorder } from "@mariozechner/pi-coding-agent"; -import { Container, Key, matchesKey, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui"; - -interface FileInfo { - status: string; - statusLabel: string; - file: string; -} - -export default function (pi: ExtensionAPI) { - pi.registerCommand("diff", { - description: "Show git changes and open in VS Code diff view", - handler: async (_args, ctx) => { - if (!ctx.hasUI) { - ctx.ui.notify("No UI available", "error"); - return; - } - - // Get changed files from git status - const result = await pi.exec("git", ["status", "--porcelain"], { cwd: ctx.cwd }); - - if (result.code !== 0) { - ctx.ui.notify(`git status failed: ${result.stderr}`, "error"); - return; - } - - if (!result.stdout || !result.stdout.trim()) { - ctx.ui.notify("No changes in working tree", "info"); - return; - } - - // Parse git status output - // Format: XY filename (where XY is two-letter status, then space, then filename) - const lines = result.stdout.split("\n"); - const files: FileInfo[] = []; - - for (const line of lines) { - if (line.length < 4) continue; // Need at least "XY f" - - const status = line.slice(0, 2); - const file = line.slice(2).trimStart(); - - // Translate status codes to short labels - let statusLabel: string; - if (status.includes("M")) statusLabel = "M"; - else if (status.includes("A")) statusLabel = "A"; - else if (status.includes("D")) statusLabel = "D"; - else if (status.includes("?")) statusLabel = "?"; - else if (status.includes("R")) statusLabel = "R"; - else if (status.includes("C")) statusLabel = "C"; - else statusLabel = status.trim() || "~"; - - files.push({ status: statusLabel, statusLabel, file }); - } - - if (files.length === 0) { - ctx.ui.notify("No changes found", "info"); - return; - } - - const WINDOWS_UNSAFE_CMD_CHARS_RE = /[&|<>^%\r\n]/; - const quoteCmdArg = (value: string) => `"${value.replace(/"/g, '""')}"`; - - const openWithCode = async (file: string) => { - if (process.platform === "win32") { - if (WINDOWS_UNSAFE_CMD_CHARS_RE.test(file)) { - ctx.ui.notify( - `Refusing to open ${file}: path contains Windows cmd metacharacters (& | < > ^ % or newline).`, - "error", - ); - return null; - } - const commandLine = `code -g ${quoteCmdArg(file)}`; - return pi.exec("cmd", ["/d", "/s", "/c", commandLine], { cwd: ctx.cwd }); - } - return pi.exec("code", ["-g", file], { cwd: ctx.cwd }); - }; - - const openSelected = async (fileInfo: FileInfo): Promise => { - try { - // Open in VS Code diff view. - // For untracked files, git difftool won't work, so fall back to just opening the file. - if (fileInfo.status === "?") { - const openResult = await openWithCode(fileInfo.file); - if (!openResult) return; - if (openResult.code !== 0) { - const openStderr = openResult.stderr.trim(); - ctx.ui.notify( - `Failed to open ${fileInfo.file} (exit ${openResult.code})${openStderr ? `: ${openStderr}` : ""}`, - "error", - ); - } - return; - } - - const diffResult = await pi.exec("git", ["difftool", "-y", "--tool=vscode", fileInfo.file], { - cwd: ctx.cwd, - }); - if (diffResult.code !== 0) { - const diffStderr = diffResult.stderr.trim(); - ctx.ui.notify( - `Failed to show diff with vscode for ${fileInfo.file} (exit ${diffResult.code})${diffStderr ? `: ${diffStderr}` : ""}`, - "error", - ); - ctx.ui.notify( - "Troubleshooting: check git difftool config (e.g. `git config --get difftool.vscode.cmd`).", - "info", - ); - - const openResult = await openWithCode(fileInfo.file); - if (!openResult) return; - if (openResult.code !== 0) { - const openStderr = openResult.stderr.trim(); - ctx.ui.notify( - `Failed to open ${fileInfo.file} (exit ${openResult.code})${openStderr ? `: ${openStderr}` : ""}`, - "error", - ); - } - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - ctx.ui.notify(`Failed to open ${fileInfo.file}: ${message}`, "error"); - } - }; - - // Show file picker with SelectList - await ctx.ui.custom((tui, theme, _kb, done) => { - const container = new Container(); - - // Top border - container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); - - // Title - container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to diff")), 0, 0)); - - // Build select items with colored status - const items: SelectItem[] = files.map((f) => { - let statusColor: string; - switch (f.status) { - case "M": - statusColor = theme.fg("warning", f.status); - break; - case "A": - statusColor = theme.fg("success", f.status); - break; - case "D": - statusColor = theme.fg("error", f.status); - break; - case "?": - statusColor = theme.fg("muted", f.status); - break; - default: - statusColor = theme.fg("dim", f.status); - } - return { - value: f, - label: `${statusColor} ${f.file}`, - }; - }); - - const visibleRows = Math.min(files.length, 15); - let currentIndex = 0; - - const selectList = new SelectList(items, visibleRows, { - selectedPrefix: (t) => theme.fg("accent", t), - selectedText: (t) => t, // Keep existing colors - description: (t) => theme.fg("muted", t), - scrollInfo: (t) => theme.fg("dim", t), - noMatch: (t) => theme.fg("warning", t), - }); - selectList.onSelect = (item) => { - void openSelected(item.value as FileInfo); - }; - selectList.onCancel = () => done(); - selectList.onSelectionChange = (item) => { - currentIndex = items.indexOf(item); - }; - container.addChild(selectList); - - // Help text - container.addChild( - new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0), - ); - - // Bottom border - container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); - - return { - render: (w) => container.render(w), - invalidate: () => container.invalidate(), - handleInput: (data) => { - // Add paging with left/right - if (matchesKey(data, Key.left)) { - // Page up - clamp to 0 - currentIndex = Math.max(0, currentIndex - visibleRows); - selectList.setSelectedIndex(currentIndex); - } else if (matchesKey(data, Key.right)) { - // Page down - clamp to last - currentIndex = Math.min(items.length - 1, currentIndex + visibleRows); - selectList.setSelectedIndex(currentIndex); - } else { - selectList.handleInput(data); - } - tui.requestRender(); - }, - }; - }); - }, - }); -}