Merge remote-tracking branch 'origin/farm/27468d1d/fix-tui-input-fullwidth-cursor-drift'
This commit is contained in:
@@ -7,6 +7,10 @@
|
||||
- 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)).
|
||||
|
||||
## [16.5.2] - 2026-07-14
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -866,6 +866,7 @@ export class Editor implements Component, Focusable {
|
||||
let displayWidth = visibleWidth(layoutLine.text);
|
||||
let cursorPaddingOverflow = 0;
|
||||
let decorated = false;
|
||||
let imeSafeCursorTail = false;
|
||||
const showPromptGutter = promptGutter !== undefined && visibleIndex === 0;
|
||||
const gutterText =
|
||||
promptGutter === undefined ? "" : showPromptGutter ? promptGutter.firstLine : promptGutter.continuation;
|
||||
@@ -931,6 +932,10 @@ 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) {
|
||||
@@ -1022,6 +1027,15 @@ export class Editor implements Component, Focusable {
|
||||
// trailing `─`, but never the corner/vertical bar itself.
|
||||
const isLastLine = visibleIndex === visibleLayoutLines.length - 1;
|
||||
const rightChromeCells = Math.max(1, paddingX + 1 - cursorPaddingOverflow);
|
||||
if (isLastLine && imeSafeCursorTail) {
|
||||
const leftBorder = this.borderColor(`${box.vertical}${padding(paddingX)}`);
|
||||
const bottomBorder = this.borderColor(
|
||||
`${box.bottomLeft}${box.horizontal.repeat(Math.max(0, width - 2))}${box.bottomRight}`,
|
||||
);
|
||||
result.push(leftBorder + displayText);
|
||||
result.push(bottomBorder);
|
||||
continue;
|
||||
}
|
||||
if (isLastLine) {
|
||||
const rightPad = Math.max(0, rightChromeCells - 2);
|
||||
const includeHorizontal = rightChromeCells >= 2;
|
||||
|
||||
@@ -3,13 +3,14 @@ import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { stripVTControlCharacters } from "node:util";
|
||||
import { CURSOR_MARKER } from "@oh-my-pi/pi-tui";
|
||||
import { CURSOR_MARKER, TUI } from "@oh-my-pi/pi-tui";
|
||||
import { CombinedAutocompleteProvider } from "@oh-my-pi/pi-tui/autocomplete";
|
||||
import { Editor } from "@oh-my-pi/pi-tui/components/editor";
|
||||
import { KeybindingsManager, setKeybindings, TUI_KEYBINDINGS } from "@oh-my-pi/pi-tui/keybindings";
|
||||
import { setKittyProtocolActive } from "@oh-my-pi/pi-tui/keys";
|
||||
import { visibleWidth } from "@oh-my-pi/pi-tui/utils";
|
||||
import { defaultEditorTheme } from "./test-themes";
|
||||
import { VirtualTerminal } from "./virtual-terminal";
|
||||
|
||||
describe("Editor component", () => {
|
||||
afterEach(() => {
|
||||
@@ -852,6 +853,36 @@ describe("Editor component", () => {
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
tui.addChild(editor);
|
||||
tui.setFocus(editor);
|
||||
|
||||
try {
|
||||
tui.start();
|
||||
await terminal.waitForRender();
|
||||
for (const char of "ast") editor.handleInput(char);
|
||||
tui.requestRender();
|
||||
await terminal.waitForRender();
|
||||
|
||||
const beforePreedit = terminal.getViewport().map(row => row.trimEnd());
|
||||
expect(beforePreedit.slice(0, 3)).toEqual(["+------------------+", "| ast", "+------------------+"]);
|
||||
|
||||
// macOS Terminal renders marked text locally in insertion mode before
|
||||
// committed bytes reach OMP. The open cursor row must not carry right
|
||||
// chrome that the marked text can shift onto another row.
|
||||
terminal.write("\x1b[4hast,\x1b[4l");
|
||||
const afterPreedit = terminal.getViewport().map(row => row.trimEnd());
|
||||
expect(afterPreedit[1]).toBe("| astast,");
|
||||
expect(afterPreedit[2]).toBe(beforePreedit[2]);
|
||||
} finally {
|
||||
tui.stop();
|
||||
}
|
||||
});
|
||||
|
||||
it("shows cursor at end before wrap and wraps on next char", () => {
|
||||
for (const paddingX of [0, 1]) {
|
||||
const editor = new Editor({ ...defaultEditorTheme, editorPaddingX: paddingX });
|
||||
|
||||
Reference in New Issue
Block a user