mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 06:02:42 +00:00
feat(tui): hardware cursor positioning for IME support
- Add Focusable interface for components that need hardware cursor positioning - Add CURSOR_MARKER (APC escape sequence) for marking cursor position in render output - Editor and Input components implement Focusable and emit marker when focused - TUI extracts cursor position from rendered output and positions hardware cursor - Track hardwareCursorRow separately from cursorRow for differential rendering - visibleWidth() and extractAnsiCode() now handle APC sequences - Update overlay-test.ts example to demonstrate Focusable usage - Add documentation for Focusable interface in docs/tui.md Closes #719, closes #525
This commit is contained in:
parent
d9464383ec
commit
07fad1362c
8 changed files with 193 additions and 17 deletions
|
|
@ -26,6 +26,32 @@ interface Component {
|
|||
|
||||
The TUI appends a full SGR reset and OSC 8 reset at the end of each rendered line. Styles do not carry across lines. If you emit multi-line text with styling, reapply styles per line or use `wrapTextWithAnsi()` so styles are preserved for each wrapped line.
|
||||
|
||||
## Focusable Interface (IME Support)
|
||||
|
||||
Components that display a text cursor and need IME (Input Method Editor) support should implement the `Focusable` interface:
|
||||
|
||||
```typescript
|
||||
import { CURSOR_MARKER, type Component, type Focusable } from "@mariozechner/pi-tui";
|
||||
|
||||
class MyInput implements Component, Focusable {
|
||||
focused: boolean = false; // Set by TUI when focus changes
|
||||
|
||||
render(width: number): string[] {
|
||||
const marker = this.focused ? CURSOR_MARKER : "";
|
||||
// Emit marker right before the fake cursor
|
||||
return [`> ${beforeCursor}${marker}\x1b[7m${atCursor}\x1b[27m${afterCursor}`];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When a `Focusable` component has focus, TUI:
|
||||
1. Sets `focused = true` on the component
|
||||
2. Scans rendered output for `CURSOR_MARKER` (a zero-width APC escape sequence)
|
||||
3. Positions the hardware terminal cursor at that location
|
||||
4. Shows the hardware cursor
|
||||
|
||||
This enables IME candidate windows to appear at the correct position for CJK input methods. The `Editor` and `Input` built-in components already implement this interface.
|
||||
|
||||
## Using Components
|
||||
|
||||
**In hooks** via `ctx.ui.custom()`:
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
import type { ExtensionAPI, ExtensionCommandContext, Theme } from "@mariozechner/pi-coding-agent";
|
||||
import { matchesKey, visibleWidth } from "@mariozechner/pi-tui";
|
||||
import { CURSOR_MARKER, type Focusable, matchesKey, visibleWidth } from "@mariozechner/pi-tui";
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.registerCommand("overlay-test", {
|
||||
|
|
@ -28,9 +28,12 @@ export default function (pi: ExtensionAPI) {
|
|||
});
|
||||
}
|
||||
|
||||
class OverlayTestComponent {
|
||||
class OverlayTestComponent implements Focusable {
|
||||
readonly width = 70;
|
||||
|
||||
/** Focusable interface - set by TUI when focus changes */
|
||||
focused = false;
|
||||
|
||||
private selected = 0;
|
||||
private items = [
|
||||
{ label: "Search", hasInput: true, text: "", cursor: 0 },
|
||||
|
|
@ -123,7 +126,9 @@ class OverlayTestComponent {
|
|||
const before = inputDisplay.slice(0, item.cursor);
|
||||
const cursorChar = item.cursor < inputDisplay.length ? inputDisplay[item.cursor] : " ";
|
||||
const after = inputDisplay.slice(item.cursor + 1);
|
||||
inputDisplay = `${before}\x1b[7m${cursorChar}\x1b[27m${after}`;
|
||||
// Emit hardware cursor marker for IME support when focused
|
||||
const marker = this.focused ? CURSOR_MARKER : "";
|
||||
inputDisplay = `${before}${marker}\x1b[7m${cursorChar}\x1b[27m${after}`;
|
||||
}
|
||||
content = `${prefix + label} ${inputDisplay}`;
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue