93 lines
2.8 KiB
TypeScript
93 lines
2.8 KiB
TypeScript
/**
|
|
* Edit Session Extension
|
|
*
|
|
* Adds /edit-session command to open the current session JSONL in your default editor.
|
|
*
|
|
* Usage:
|
|
* /edit-session - Open current session file in $EDITOR
|
|
*
|
|
* The editor is determined by $VISUAL, then $EDITOR, then falls back to 'vi'.
|
|
*/
|
|
|
|
import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@mariozechner/pi-coding-agent";
|
|
import type { TUI, KeybindingsManager, Component } from "@mariozechner/pi-tui";
|
|
import { spawnSync } from "node:child_process";
|
|
|
|
export default function editSessionExtension(pi: ExtensionAPI) {
|
|
|
|
pi.registerCommand("edit-session", {
|
|
description: "Open session JSONL in default editor ($VISUAL/$EDITOR/vi)",
|
|
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
|
|
if (!sessionFile) {
|
|
ctx.ui.notify("No session file (ephemeral mode)", "error");
|
|
return;
|
|
}
|
|
|
|
const editorCmd = process.env.VISUAL || process.env.EDITOR;
|
|
if (!editorCmd) {
|
|
ctx.ui.notify("No editor configured. Set $VISUAL or $EDITOR.", "error");
|
|
return;
|
|
}
|
|
|
|
// Factory function to create component and run editor
|
|
const factory = (
|
|
tui: TUI,
|
|
_theme: Theme,
|
|
_kb: KeybindingsManager,
|
|
done: (result: void) => void
|
|
): Component => {
|
|
// Stop TUI to release terminal (same as Ctrl+G)
|
|
tui.stop();
|
|
|
|
// Split editor command to support args (e.g., "code --wait")
|
|
const [editor, ...editorArgs] = editorCmd.split(" ");
|
|
|
|
// Spawn editor synchronously with inherited stdio
|
|
const result = spawnSync(editor, [...editorArgs, sessionFile], {
|
|
stdio: "inherit",
|
|
shell: process.platform === "win32",
|
|
});
|
|
|
|
// Restart TUI
|
|
tui.start();
|
|
// Force full re-render since external editor uses alternate screen
|
|
tui.requestRender(true);
|
|
|
|
if (result.status !== 0 && result.status !== null) {
|
|
ctx.ui.notify(`Editor exited with code ${result.status}`, "warning");
|
|
}
|
|
|
|
done(undefined);
|
|
|
|
// Return dummy component
|
|
return createDummyComponent();
|
|
};
|
|
|
|
// Use ctx.ui.custom to get access to the TUI
|
|
await ctx.ui.custom<void>(factory);
|
|
|
|
// Signal that we're about to reload the session (so confirm-destructive skips)
|
|
pi.events.emit("edit-session:reload", undefined);
|
|
|
|
// Reload the session by switching to the same file (forces re-read from disk)
|
|
ctx.ui.notify("Reloading session...", "info");
|
|
const result = await ctx.switchSession(sessionFile);
|
|
|
|
if (result.cancelled) {
|
|
// If still cancelled (by another extension), just notify without warning
|
|
ctx.ui.notify("Session reload skipped. Run /reload to apply changes.", "info");
|
|
} else {
|
|
ctx.ui.notify("Session reloaded with changes.", "info");
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
function createDummyComponent(): Component {
|
|
return {
|
|
render: (_width: number): string[] => [],
|
|
invalidate: (): void => {},
|
|
};
|
|
} |