From f3aad14ee9758976c4e8994565c8afe20cd5eedb Mon Sep 17 00:00:00 2001 From: can1357 Date: Wed, 15 Jul 2026 13:16:07 +0200 Subject: [PATCH] fix(tui): restored compact editor border by default - Restored the compact two-row `Editor` layout by default and gated the dedicated IME-safe bottom border behind `setImeSafeCursorLayout()`. - Added `tui.imeSafeCursor` as an opt-in appearance setting and applied it to initial and replacement editors. - Added regression coverage for compact default rendering while retaining terminal-local IME preedit protection. - Updated the TUI changelog for the opt-in compatibility layout. --- .../coding-agent/src/config/settings-schema.ts | 11 +++++++++++ .../coding-agent/src/modes/interactive-mode.ts | 2 ++ packages/tui/CHANGELOG.md | 5 +---- packages/tui/src/components/editor.ts | 18 +++++++++++++----- packages/tui/test/editor.test.ts | 11 +++++++++++ 5 files changed, 38 insertions(+), 9 deletions(-) diff --git a/packages/coding-agent/src/config/settings-schema.ts b/packages/coding-agent/src/config/settings-schema.ts index e6937f7de..3f350ea70 100644 --- a/packages/coding-agent/src/config/settings-schema.ts +++ b/packages/coding-agent/src/config/settings-schema.ts @@ -982,6 +982,17 @@ export const SETTINGS_SCHEMA = { }, }, + "tui.imeSafeCursor": { + type: "boolean", + default: false, + ui: { + tab: "appearance", + group: "Display", + label: "IME-Safe Prompt Layout", + description: "Move the prompt's bottom border to a separate row so macOS IME preedit cannot displace it", + }, + }, + // ──────────────────────────────────────────────────────────────────────── // Model // ──────────────────────────────────────────────────────────────────────── diff --git a/packages/coding-agent/src/modes/interactive-mode.ts b/packages/coding-agent/src/modes/interactive-mode.ts index e932f033f..3bdd97bee 100644 --- a/packages/coding-agent/src/modes/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive-mode.ts @@ -680,6 +680,7 @@ export class InteractiveMode implements InteractiveModeContext { this.errorBannerContainer = new AnchoredLiveContainer(); this.modelCycleContainer = new AnchoredLiveContainer(); this.editor = new CustomEditor(getEditorTheme()); + this.editor.setImeSafeCursorLayout(settings.get("tui.imeSafeCursor")); this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor()); this.editor.setAutocompleteMaxVisible(settings.get("autocompleteMaxVisible")); this.editor.onAutocompleteCancel = () => { @@ -3669,6 +3670,7 @@ export class InteractiveMode implements InteractiveModeContext { factory: ((tui: TUI, theme: EditorTheme, keybindings: KeybindingsManager) => CustomEditor) | undefined, ): void { const previousEditor = this.editor; + nextEditor.setImeSafeCursorLayout(this.settings.get("tui.imeSafeCursor")); const previousText = previousEditor.getText(); const nextEditor = factory ? factory(this.ui, getEditorTheme(), this.keybindings) diff --git a/packages/tui/CHANGELOG.md b/packages/tui/CHANGELOG.md index 9981683b8..82b5c0715 100644 --- a/packages/tui/CHANGELOG.md +++ b/packages/tui/CHANGELOG.md @@ -6,10 +6,7 @@ - Display LaTeX now renders `\underbrace`/`\overbrace` (and the bracket/paren variants) as drawn horizontal braces with centered labels, and stacks `\overset`/`\underset`/`\stackrel` annotations above/below the base instead of falling back to flat inline glyphs. - Display LaTeX renders multi-letter script words (`N_{turns}`) as raised/lowered blocks instead of ragged per-character Unicode sub/superscript glyphs; single letters and digits keep the compact Unicode forms. - -### Fixed - -- Fixed macOS IME marked text displacing the prompt editor border when entering fullwidth CJK punctuation ([#5563](https://github.com/can1357/oh-my-pi/issues/5563)). +- Added opt-in `Editor.setImeSafeCursorLayout()` protection for macOS IME preedit while retaining the compact bordered layout by default ([#5563](https://github.com/can1357/oh-my-pi/issues/5563)). ## [16.5.2] - 2026-07-14 diff --git a/packages/tui/src/components/editor.ts b/packages/tui/src/components/editor.ts index 2c12c811d..800bcd4b3 100644 --- a/packages/tui/src/components/editor.ts +++ b/packages/tui/src/components/editor.ts @@ -382,6 +382,7 @@ export class Editor implements Component, Focusable { #theme: EditorTheme; #useTerminalCursor = false; + #imeSafeCursorLayout = false; /** When set, replaces the normal cursor glyph at end-of-text with this ANSI-styled string. */ cursorOverride: string | undefined; @@ -539,6 +540,11 @@ export class Editor implements Component, Focusable { this.#useTerminalCursor = useTerminalCursor; } + /** Render a dedicated bottom border so terminal-local IME preedit cannot shift editor chrome. */ + setImeSafeCursorLayout(enabled: boolean): void { + this.#imeSafeCursorLayout = enabled; + } + getUseTerminalCursor(): boolean { return this.#useTerminalCursor; } @@ -923,7 +929,13 @@ export class Editor implements Component, Focusable { if (marker) { const before = displayText.slice(0, layoutLine.cursorPos); const after = displayText.slice(layoutLine.cursorPos); - if (after.length === 0 && inlineHint) { + if (this.#imeSafeCursorLayout && after.length === 0 && borderVisible) { + // Terminal frontends render IME marked text locally before committed bytes + // reach the application. Keep the end-of-input cursor row empty to its + // right so that insertion cannot shift box chrome onto the next row. + displayText = before + marker; + imeSafeCursorTail = true; + } else if (after.length === 0 && inlineHint) { const availWidth = Math.max(0, lineContentWidth - displayWidth); const hintText = hintStyle(truncateToWidth(inlineHint, availWidth)); displayText = before + marker + hintText; @@ -932,10 +944,6 @@ export class Editor implements Component, Focusable { displayText = this.#renderTerminalCursorMarker(before, marker, lineContentWidth); } else { displayText = before + marker + after; - // Terminal frontends render IME marked text locally before committed bytes - // reach the application. Keep the end-of-input cursor row empty to its - // right so that insertion cannot shift box chrome onto the next row. - imeSafeCursorTail = after.length === 0 && borderVisible; } } } else if (hasCursor && !this.#useTerminalCursor) { diff --git a/packages/tui/test/editor.test.ts b/packages/tui/test/editor.test.ts index 294a6710c..eaacef517 100644 --- a/packages/tui/test/editor.test.ts +++ b/packages/tui/test/editor.test.ts @@ -853,11 +853,22 @@ describe("Editor component", () => { } }); + it("keeps the terminal-cursor editor compact by default", () => { + const editor = new Editor(defaultEditorTheme); + editor.focused = true; + editor.setUseTerminalCursor(true); + editor.setText("ast"); + + const lines = editor.render(20).map(line => stripVTControlCharacters(line.replaceAll(CURSOR_MARKER, ""))); + expect(lines).toEqual(["+------------------+", "+- ast -+"]); + }); + it("keeps terminal-local IME preedit from displacing the editor border (#5563)", async () => { const width = 20; const terminal = new VirtualTerminal(width, 6, 1_000); const tui = new TUI(terminal, true); const editor = new Editor(defaultEditorTheme); + editor.setImeSafeCursorLayout(true); tui.addChild(editor); tui.setFocus(editor);