feat(coding-agent,tui): add stacked autocomplete providers closes #2983
This commit is contained in:
@@ -29,6 +29,7 @@ export type {
|
||||
AppendEntryHandler,
|
||||
// App keybindings (for custom editors)
|
||||
AppKeybinding,
|
||||
AutocompleteProviderFactory,
|
||||
// Events - Tool (ToolCallEvent types)
|
||||
BashToolCallEvent,
|
||||
BashToolResultEvent,
|
||||
|
||||
@@ -204,6 +204,7 @@ const noOpUIContext: ExtensionUIContext = {
|
||||
setEditorText: () => {},
|
||||
getEditorText: () => "",
|
||||
editor: async () => undefined,
|
||||
addAutocompleteProvider: () => {},
|
||||
setEditorComponent: () => {},
|
||||
get theme() {
|
||||
return theme;
|
||||
|
||||
@@ -30,6 +30,7 @@ import type {
|
||||
} from "@mariozechner/pi-ai";
|
||||
import type {
|
||||
AutocompleteItem,
|
||||
AutocompleteProvider,
|
||||
Component,
|
||||
EditorComponent,
|
||||
EditorTheme,
|
||||
@@ -112,6 +113,9 @@ export interface WorkingIndicatorOptions {
|
||||
intervalMs?: number;
|
||||
}
|
||||
|
||||
/** Wrap the current autocomplete provider with additional behavior. */
|
||||
export type AutocompleteProviderFactory = (current: AutocompleteProvider) => AutocompleteProvider;
|
||||
|
||||
/**
|
||||
* UI context for extensions to request interactive UI.
|
||||
* Each mode (interactive, RPC, print) provides its own implementation.
|
||||
@@ -206,6 +210,9 @@ export interface ExtensionUIContext {
|
||||
/** Show a multi-line editor for text editing. */
|
||||
editor(title: string, prefill?: string): Promise<string | undefined>;
|
||||
|
||||
/** Stack additional autocomplete behavior on top of the built-in provider. */
|
||||
addAutocompleteProvider(factory: AutocompleteProviderFactory): void;
|
||||
|
||||
/**
|
||||
* Set a custom editor component via factory function.
|
||||
* Pass undefined to restore the default editor.
|
||||
|
||||
@@ -54,6 +54,7 @@ export type {
|
||||
AgentToolResult,
|
||||
AgentToolUpdateCallback,
|
||||
AppKeybinding,
|
||||
AutocompleteProviderFactory,
|
||||
BashToolCallEvent,
|
||||
BeforeAgentStartEvent,
|
||||
BeforeAgentStartEventResult,
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import type { AssistantMessage, ImageContent, Message, Model, OAuthProviderId } from "@mariozechner/pi-ai";
|
||||
import type {
|
||||
AutocompleteItem,
|
||||
AutocompleteProvider,
|
||||
EditorComponent,
|
||||
EditorTheme,
|
||||
Keybinding,
|
||||
@@ -50,6 +51,7 @@ import {
|
||||
import { type AgentSession, type AgentSessionEvent, parseSkillBlock } from "../../core/agent-session.js";
|
||||
import { type AgentSessionRuntime, SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
||||
import type {
|
||||
AutocompleteProviderFactory,
|
||||
ExtensionCommandContext,
|
||||
ExtensionContext,
|
||||
ExtensionRunner,
|
||||
@@ -191,7 +193,8 @@ export class InteractiveMode {
|
||||
private statusContainer: Container;
|
||||
private defaultEditor: CustomEditor;
|
||||
private editor: EditorComponent;
|
||||
private autocompleteProvider: CombinedAutocompleteProvider | undefined;
|
||||
private autocompleteProvider: AutocompleteProvider | undefined;
|
||||
private autocompleteProviderWrappers: AutocompleteProviderFactory[] = [];
|
||||
private fdPath: string | undefined;
|
||||
private editorContainer: Container;
|
||||
private footer: FooterComponent;
|
||||
@@ -391,7 +394,7 @@ export class InteractiveMode {
|
||||
}));
|
||||
}
|
||||
|
||||
private setupAutocomplete(fdPath: string | undefined): void {
|
||||
private createBaseAutocompleteProvider(): AutocompleteProvider {
|
||||
// Define commands for autocomplete
|
||||
const slashCommands: SlashCommand[] = BUILTIN_SLASH_COMMANDS.map((command) => ({
|
||||
name: command.name,
|
||||
@@ -461,15 +464,23 @@ export class InteractiveMode {
|
||||
}
|
||||
}
|
||||
|
||||
// Setup autocomplete
|
||||
this.autocompleteProvider = new CombinedAutocompleteProvider(
|
||||
return new CombinedAutocompleteProvider(
|
||||
[...slashCommands, ...templateCommands, ...extensionCommands, ...skillCommandList],
|
||||
this.sessionManager.getCwd(),
|
||||
fdPath,
|
||||
this.fdPath,
|
||||
);
|
||||
this.defaultEditor.setAutocompleteProvider(this.autocompleteProvider);
|
||||
}
|
||||
|
||||
private setupAutocompleteProvider(): void {
|
||||
let provider = this.createBaseAutocompleteProvider();
|
||||
for (const wrapProvider of this.autocompleteProviderWrappers) {
|
||||
provider = wrapProvider(provider);
|
||||
}
|
||||
|
||||
this.autocompleteProvider = provider;
|
||||
this.defaultEditor.setAutocompleteProvider(provider);
|
||||
if (this.editor !== this.defaultEditor) {
|
||||
this.editor.setAutocompleteProvider?.(this.autocompleteProvider);
|
||||
this.editor.setAutocompleteProvider?.(provider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1492,7 +1503,7 @@ export class InteractiveMode {
|
||||
});
|
||||
|
||||
setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
|
||||
this.setupAutocomplete(this.fdPath);
|
||||
this.setupAutocompleteProvider();
|
||||
|
||||
const extensionRunner = this.session.extensionRunner;
|
||||
this.setupExtensionShortcuts(extensionRunner);
|
||||
@@ -1708,7 +1719,9 @@ export class InteractiveMode {
|
||||
this.clearExtensionWidgets();
|
||||
this.footerDataProvider.clearExtensionStatuses();
|
||||
this.footer.invalidate();
|
||||
this.autocompleteProviderWrappers = [];
|
||||
this.setCustomEditorComponent(undefined);
|
||||
this.setupAutocompleteProvider();
|
||||
this.defaultEditor.onExtensionShortcut = undefined;
|
||||
this.updateTerminalTitle();
|
||||
this.pendingWorkingMessage = undefined;
|
||||
@@ -1886,6 +1899,10 @@ export class InteractiveMode {
|
||||
setEditorText: (text) => this.editor.setText(text),
|
||||
getEditorText: () => this.editor.getExpandedText?.() ?? this.editor.getText(),
|
||||
editor: (title, prefill) => this.showExtensionEditor(title, prefill),
|
||||
addAutocompleteProvider: (factory) => {
|
||||
this.autocompleteProviderWrappers.push(factory);
|
||||
this.setupAutocompleteProvider();
|
||||
},
|
||||
setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
|
||||
get theme() {
|
||||
return theme;
|
||||
@@ -3705,7 +3722,7 @@ export class InteractiveMode {
|
||||
},
|
||||
onEnableSkillCommandsChange: (enabled) => {
|
||||
this.settingsManager.setEnableSkillCommands(enabled);
|
||||
this.setupAutocomplete(this.fdPath);
|
||||
this.setupAutocompleteProvider();
|
||||
},
|
||||
onSteeringModeChange: (mode) => {
|
||||
this.session.setSteeringMode(mode);
|
||||
@@ -4493,7 +4510,7 @@ export class InteractiveMode {
|
||||
}
|
||||
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
||||
this.ui.setClearOnShrink(this.settingsManager.getClearOnShrink());
|
||||
this.setupAutocomplete(this.fdPath);
|
||||
this.setupAutocompleteProvider();
|
||||
const runner = this.session.extensionRunner;
|
||||
this.setupExtensionShortcuts(runner);
|
||||
this.rebuildChatFromMessages();
|
||||
|
||||
@@ -259,6 +259,10 @@ export async function runRpcMode(runtimeHost: AgentSessionRuntime): Promise<neve
|
||||
});
|
||||
},
|
||||
|
||||
addAutocompleteProvider(): void {
|
||||
// Autocomplete provider composition is not supported in RPC mode
|
||||
},
|
||||
|
||||
setEditorComponent(): void {
|
||||
// Custom editor components not supported in RPC mode
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user