diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index f8ff64e4..05a4adad 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -5,6 +5,8 @@ ### Added - Export `keyHint`, `appKeyHint`, `editorKey`, `appKey`, `rawKeyHint` for extensions to format keybinding hints consistently +- Added `showHardwareCursor` setting to control cursor visibility while still positioning it for IME support. ([#800](https://github.com/badlogic/pi-mono/pull/800) by [@ghoulr](https://github.com/ghoulr)) +- Added `ctx.compact()` and `ctx.getContextUsage()` to extension contexts for programmatic compaction and context usage checks. ## [0.48.0] - 2026-01-16 diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index 4ecd837d..23d978c0 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -789,6 +789,7 @@ Global `~/.pi/agent/settings.json` stores persistent preferences: "autoResize": true, "blockImages": false }, + "showHardwareCursor": false, "extensions": ["/path/to/extension.ts"] } ``` @@ -816,6 +817,7 @@ Global `~/.pi/agent/settings.json` stores persistent preferences: | `terminal.showImages` | Render images inline (supported terminals) | `true` | | `images.autoResize` | Auto-resize images to 2000x2000 max for better model compatibility | `true` | | `images.blockImages` | Prevent images from being sent to LLM providers | `false` | +| `showHardwareCursor` | Show terminal cursor while still positioning it for IME support | `false` | | `doubleEscapeAction` | Action for double-escape with empty editor: `tree` or `branch` | `tree` | | `editorPaddingX` | Horizontal padding for input editor (0-3) | `0` | | `extensions` | Additional extension file paths | `[]` | diff --git a/packages/coding-agent/src/core/settings-manager.ts b/packages/coding-agent/src/core/settings-manager.ts index cc247c13..84d0b2b3 100644 --- a/packages/coding-agent/src/core/settings-manager.ts +++ b/packages/coding-agent/src/core/settings-manager.ts @@ -71,6 +71,7 @@ export interface Settings { doubleEscapeAction?: "fork" | "tree"; // Action for double-escape with empty editor (default: "tree") thinkingBudgets?: ThinkingBudgetsSettings; // Custom token budgets for thinking levels editorPaddingX?: number; // Horizontal padding for input editor (default: 0) + showHardwareCursor?: boolean; // Show terminal cursor while still positioning it for IME } /** Deep merge settings: project/overrides take precedence, nested objects merge recursively */ @@ -482,6 +483,15 @@ export class SettingsManager { this.save(); } + getShowHardwareCursor(): boolean { + return this.settings.showHardwareCursor ?? process.env.PI_HARDWARE_CURSOR === "1"; + } + + setShowHardwareCursor(enabled: boolean): void { + this.globalSettings.showHardwareCursor = enabled; + this.save(); + } + getEditorPaddingX(): number { return this.settings.editorPaddingX ?? 0; } diff --git a/packages/coding-agent/src/modes/interactive/components/settings-selector.ts b/packages/coding-agent/src/modes/interactive/components/settings-selector.ts index 2ac563ef..70321090 100644 --- a/packages/coding-agent/src/modes/interactive/components/settings-selector.ts +++ b/packages/coding-agent/src/modes/interactive/components/settings-selector.ts @@ -36,6 +36,7 @@ export interface SettingsConfig { hideThinkingBlock: boolean; collapseChangelog: boolean; doubleEscapeAction: "fork" | "tree"; + showHardwareCursor: boolean; editorPaddingX: number; } @@ -53,6 +54,7 @@ export interface SettingsCallbacks { onHideThinkingBlockChange: (hidden: boolean) => void; onCollapseChangelogChange: (collapsed: boolean) => void; onDoubleEscapeActionChange: (action: "fork" | "tree") => void; + onShowHardwareCursorChange: (enabled: boolean) => void; onEditorPaddingXChange: (padding: number) => void; onCancel: () => void; } @@ -269,9 +271,19 @@ export class SettingsSelectorComponent extends Container { values: ["true", "false"], }); - // Editor padding toggle (insert after skill-commands) + // Hardware cursor toggle (insert after skill-commands) const skillCommandsIndex = items.findIndex((item) => item.id === "skill-commands"); items.splice(skillCommandsIndex + 1, 0, { + id: "show-hardware-cursor", + label: "Show hardware cursor", + description: "Show the terminal cursor while still positioning it for IME support", + currentValue: config.showHardwareCursor ? "true" : "false", + values: ["true", "false"], + }); + + // Editor padding toggle (insert after show-hardware-cursor) + const hardwareCursorIndex = items.findIndex((item) => item.id === "show-hardware-cursor"); + items.splice(hardwareCursorIndex + 1, 0, { id: "editor-padding", label: "Editor padding", description: "Horizontal padding for input editor (0-3)", @@ -318,6 +330,9 @@ export class SettingsSelectorComponent extends Container { case "double-escape-action": callbacks.onDoubleEscapeActionChange(newValue as "fork" | "tree"); break; + case "show-hardware-cursor": + callbacks.onShowHardwareCursorChange(newValue === "true"); + break; case "editor-padding": callbacks.onEditorPaddingXChange(parseInt(newValue, 10)); break; diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 529b0f53..813c94f5 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -235,7 +235,7 @@ export class InteractiveMode { ) { this.session = session; this.version = VERSION; - this.ui = new TUI(new ProcessTerminal()); + this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor()); this.chatContainer = new Container(); this.pendingMessagesContainer = new Container(); this.statusContainer = new Container(); @@ -689,6 +689,18 @@ export class InteractiveMode { shutdown: () => { this.shutdownRequested = true; }, + getContextUsage: () => this.session.getContextUsage(), + compact: (options) => { + void (async () => { + try { + const result = await this.session.compact(options?.customInstructions); + options?.onComplete?.(result); + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + options?.onError?.(err); + } + })(); + }, }, // ExtensionCommandContextActions - for ctx.* in command handlers { @@ -813,6 +825,18 @@ export class InteractiveMode { shutdown: () => { this.shutdownRequested = true; }, + getContextUsage: () => this.session.getContextUsage(), + compact: (options) => { + void (async () => { + try { + const result = await this.session.compact(options?.customInstructions); + options?.onComplete?.(result); + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + options?.onError?.(err); + } + })(); + }, }); // Set up the extension shortcut handler on the default editor @@ -2521,6 +2545,7 @@ export class InteractiveMode { hideThinkingBlock: this.hideThinkingBlock, collapseChangelog: this.settingsManager.getCollapseChangelog(), doubleEscapeAction: this.settingsManager.getDoubleEscapeAction(), + showHardwareCursor: this.settingsManager.getShowHardwareCursor(), editorPaddingX: this.settingsManager.getEditorPaddingX(), }, { @@ -2589,6 +2614,10 @@ export class InteractiveMode { onDoubleEscapeActionChange: (action) => { this.settingsManager.setDoubleEscapeAction(action); }, + onShowHardwareCursorChange: (enabled) => { + this.settingsManager.setShowHardwareCursor(enabled); + this.ui.setShowHardwareCursor(enabled); + }, onEditorPaddingXChange: (padding) => { this.settingsManager.setEditorPaddingX(padding); this.defaultEditor.setPaddingX(padding); diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 6bc04801..37f6ffed 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- Added `showHardwareCursor` getter and setter to control cursor visibility while keeping IME positioning active. ([#800](https://github.com/badlogic/pi-mono/pull/800) by [@ghoulr](https://github.com/ghoulr)) + ## [0.48.0] - 2026-01-16 ### Added diff --git a/packages/tui/src/tui.ts b/packages/tui/src/tui.ts index 20f32446..95af0629 100644 --- a/packages/tui/src/tui.ts +++ b/packages/tui/src/tui.ts @@ -208,7 +208,7 @@ export class TUI extends Container { private hardwareCursorRow = 0; // Actual terminal cursor row (may differ due to IME positioning) private inputBuffer = ""; // Buffer for parsing terminal responses private cellSizeQueryPending = false; - private useHardwareCursor = process.env.PI_HARDWARE_CURSOR === "1"; + private showHardwareCursor = process.env.PI_HARDWARE_CURSOR === "1"; // Overlay stack for modal components rendered on top of base content private overlayStack: { @@ -218,9 +218,25 @@ export class TUI extends Container { hidden: boolean; }[] = []; - constructor(terminal: Terminal) { + constructor(terminal: Terminal, showHardwareCursor?: boolean) { super(); this.terminal = terminal; + if (showHardwareCursor !== undefined) { + this.showHardwareCursor = showHardwareCursor; + } + } + + getShowHardwareCursor(): boolean { + return this.showHardwareCursor; + } + + setShowHardwareCursor(enabled: boolean): void { + if (this.showHardwareCursor === enabled) return; + this.showHardwareCursor = enabled; + if (!enabled) { + this.terminal.hideCursor(); + } + this.requestRender(); } setFocus(component: Component | null): void { @@ -982,12 +998,6 @@ export class TUI extends Container { * @param totalLines Total number of rendered lines */ private positionHardwareCursor(cursorPos: { row: number; col: number } | null, totalLines: number): void { - // PI_HARDWARE_CURSOR=1 enables hardware cursor (disabled by default due to terminal compatibility issues) - if (!this.useHardwareCursor) { - this.terminal.hideCursor(); - return; - } - if (!cursorPos || totalLines <= 0) { this.terminal.hideCursor(); return; @@ -1013,6 +1023,10 @@ export class TUI extends Container { } this.hardwareCursorRow = targetRow; - this.terminal.showCursor(); + if (this.showHardwareCursor) { + this.terminal.showCursor(); + } else { + this.terminal.hideCursor(); + } } }