diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 087ff062..506082af 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- Added `ctx.ui.getEditorComponent()` so extensions can wrap the currently configured custom editor factory ([#3935](https://github.com/badlogic/pi-mono/issues/3935)). + ### Fixed - Fixed idle follow-up submission to clear the editor like normal message submission ([#3926](https://github.com/badlogic/pi-mono/issues/3926)). diff --git a/packages/coding-agent/docs/extensions.md b/packages/coding-agent/docs/extensions.md index dbb4426c..1fce56ec 100644 --- a/packages/coding-agent/docs/extensions.md +++ b/packages/coding-agent/docs/extensions.md @@ -2225,6 +2225,10 @@ ctx.ui.setToolsExpanded(wasExpanded); // Custom editor (vim mode, emacs mode, etc.) ctx.ui.setEditorComponent((tui, theme, keybindings) => new VimEditor(tui, theme, keybindings)); +const currentEditor = ctx.ui.getEditorComponent(); +ctx.ui.setEditorComponent((tui, theme, keybindings) => + new WrappedEditor(tui, theme, keybindings, currentEditor?.(tui, theme, keybindings)) +); ctx.ui.setEditorComponent(undefined); // Restore default editor // Theme management (see themes.md for creating themes) @@ -2379,8 +2383,18 @@ export default function (pi: ExtensionAPI) { - Extend `CustomEditor` (not base `Editor`) to get app keybindings (escape to abort, ctrl+d, model switching) - Call `super.handleInput(data)` for keys you don't handle - Factory receives `theme` and `keybindings` from the app +- Use `ctx.ui.getEditorComponent()` before `setEditorComponent()` to wrap the previously configured custom editor - Pass `undefined` to restore default: `ctx.ui.setEditorComponent(undefined)` +To compose with another extension that already replaced the editor, capture the previous factory before setting yours: + +```typescript +const previous = ctx.ui.getEditorComponent(); +ctx.ui.setEditorComponent((tui, theme, keybindings) => + new MyEditor(tui, theme, keybindings, { base: previous?.(tui, theme, keybindings) }) +); +``` + See [tui.md](tui.md) Pattern 7 for a complete example with mode indicator. ### Message Rendering diff --git a/packages/coding-agent/src/core/extensions/index.ts b/packages/coding-agent/src/core/extensions/index.ts index d6158fa9..fdc235c6 100644 --- a/packages/coding-agent/src/core/extensions/index.ts +++ b/packages/coding-agent/src/core/extensions/index.ts @@ -47,6 +47,7 @@ export type { ContextUsage, CustomToolCallEvent, CustomToolResultEvent, + EditorFactory, EditToolCallEvent, EditToolResultEvent, ExecOptions, diff --git a/packages/coding-agent/src/core/extensions/runner.ts b/packages/coding-agent/src/core/extensions/runner.ts index 0fd52189..03c98077 100644 --- a/packages/coding-agent/src/core/extensions/runner.ts +++ b/packages/coding-agent/src/core/extensions/runner.ts @@ -207,6 +207,7 @@ const noOpUIContext: ExtensionUIContext = { editor: async () => undefined, addAutocompleteProvider: () => {}, setEditorComponent: () => {}, + getEditorComponent: () => undefined, get theme() { return theme; }, diff --git a/packages/coding-agent/src/core/extensions/types.ts b/packages/coding-agent/src/core/extensions/types.ts index 99bc3625..a2e98e5c 100644 --- a/packages/coding-agent/src/core/extensions/types.ts +++ b/packages/coding-agent/src/core/extensions/types.ts @@ -115,6 +115,7 @@ export interface WorkingIndicatorOptions { /** Wrap the current autocomplete provider with additional behavior. */ export type AutocompleteProviderFactory = (current: AutocompleteProvider) => AutocompleteProvider; +export type EditorFactory = (tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => EditorComponent; /** * UI context for extensions to request interactive UI. @@ -249,9 +250,10 @@ export interface ExtensionUIContext { * ); * ``` */ - setEditorComponent( - factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => EditorComponent) | undefined, - ): void; + setEditorComponent(factory: EditorFactory | undefined): void; + + /** Get the currently configured custom editor factory, or undefined when using the default editor. */ + getEditorComponent(): EditorFactory | undefined; /** Get the current theme for styling. */ readonly theme: Theme; diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 02af76a5..be3ebd15 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -20,7 +20,6 @@ import type { AutocompleteItem, AutocompleteProvider, EditorComponent, - EditorTheme, Keybinding, KeyId, MarkdownTheme, @@ -60,6 +59,7 @@ import { type AgentSession, type AgentSessionEvent, parseSkillBlock } from "../. import { type AgentSessionRuntime, SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js"; import type { AutocompleteProviderFactory, + EditorFactory, ExtensionCommandContext, ExtensionContext, ExtensionRunner, @@ -251,6 +251,7 @@ export class InteractiveMode { private statusContainer: Container; private defaultEditor: CustomEditor; private editor: EditorComponent; + private editorComponentFactory: EditorFactory | undefined; private autocompleteProvider: AutocompleteProvider | undefined; private autocompleteProviderWrappers: AutocompleteProviderFactory[] = []; private fdPath: string | undefined; @@ -1985,6 +1986,7 @@ export class InteractiveMode { this.setupAutocompleteProvider(); }, setEditorComponent: (factory) => this.setCustomEditorComponent(factory), + getEditorComponent: () => this.editorComponentFactory, get theme() { return theme; }, @@ -2182,9 +2184,9 @@ export class InteractiveMode { * Set a custom editor component from an extension. * Pass undefined to restore the default editor. */ - private setCustomEditorComponent( - factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => EditorComponent) | undefined, - ): void { + private setCustomEditorComponent(factory: EditorFactory | undefined): void { + this.editorComponentFactory = factory; + // Save text from current editor before switching const currentText = this.editor.getText(); diff --git a/packages/coding-agent/src/modes/rpc/rpc-mode.ts b/packages/coding-agent/src/modes/rpc/rpc-mode.ts index 46161e2e..b10692ed 100644 --- a/packages/coding-agent/src/modes/rpc/rpc-mode.ts +++ b/packages/coding-agent/src/modes/rpc/rpc-mode.ts @@ -271,6 +271,11 @@ export async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise