Merge remote-tracking branch 'origin/farm/f80f626f/tui-fullwidth-cursor-drift'
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed bordered `Editor` rendering 1–2 cells past the terminal width when the end-of-line cursor glyph landed past a wide trailing grapheme (CJK comma `,`, emoji, etc.), wrapping the bottom-right corner (`╯`) to its own row. The right chrome (padding + `─` + corner) now shrinks by the exact cursor overflow cell count instead of a 1-cell boolean, so the box stays inside `width` for any `paddingX` ([#3431](https://github.com/can1357/oh-my-pi/issues/3431)).
|
||||
|
||||
## [16.1.17] - 2026-06-24
|
||||
|
||||
### Added
|
||||
|
||||
@@ -837,7 +837,7 @@ export class Editor implements Component, Focusable {
|
||||
const layoutLine = visibleLayoutLines[visibleIndex]!;
|
||||
let displayText = layoutLine.text;
|
||||
let displayWidth = visibleWidth(layoutLine.text);
|
||||
let cursorInPadding = false;
|
||||
let cursorPaddingOverflow = 0;
|
||||
let decorated = false;
|
||||
const showPromptGutter = promptGutter !== undefined && visibleIndex === 0;
|
||||
const gutterText =
|
||||
@@ -963,7 +963,7 @@ export class Editor implements Component, Focusable {
|
||||
displayWidth += cursorWidth;
|
||||
}
|
||||
if (displayWidth > lineContentWidth && paddingX > 0) {
|
||||
cursorInPadding = true;
|
||||
cursorPaddingOverflow = displayWidth - lineContentWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -982,18 +982,22 @@ export class Editor implements Component, Focusable {
|
||||
continue;
|
||||
}
|
||||
|
||||
// All lines have consistent borders based on padding
|
||||
// All lines have consistent borders based on padding. When the end-of-line cursor
|
||||
// glyph (or a wide trailing grapheme) extends past `lineContentWidth`, shrink the
|
||||
// right chrome by the exact overflow count: drop padding spaces first, then the
|
||||
// trailing `─`, but never the corner/vertical bar itself.
|
||||
const isLastLine = visibleIndex === visibleLayoutLines.length - 1;
|
||||
const rightPaddingWidth = Math.max(0, paddingX - (cursorInPadding ? 1 : 0));
|
||||
const rightChromeCells = Math.max(1, paddingX + 1 - cursorPaddingOverflow);
|
||||
if (isLastLine) {
|
||||
const bottomRightPadding = Math.max(0, paddingX - 1 - (cursorInPadding ? 1 : 0));
|
||||
const rightPad = Math.max(0, rightChromeCells - 2);
|
||||
const includeHorizontal = rightChromeCells >= 2;
|
||||
const bottomRightAdjusted = this.borderColor(
|
||||
`${padding(bottomRightPadding)}${box.horizontal}${box.bottomRight}`,
|
||||
`${padding(rightPad)}${includeHorizontal ? box.horizontal : ""}${box.bottomRight}`,
|
||||
);
|
||||
result.push(`${bottomLeft}${displayText}${linePad}${bottomRightAdjusted}`);
|
||||
} else {
|
||||
const leftBorder = this.borderColor(`${box.vertical}${padding(paddingX)}`);
|
||||
const rightBorder = this.borderColor(`${padding(rightPaddingWidth)}${box.vertical}`);
|
||||
const rightBorder = this.borderColor(`${padding(Math.max(0, rightChromeCells - 1))}${box.vertical}`);
|
||||
result.push(leftBorder + displayText + linePad + rightBorder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -754,6 +754,28 @@ describe("Editor component", () => {
|
||||
expect(visibleWidth(contentLine)).toBeLessThanOrEqual(width);
|
||||
});
|
||||
|
||||
it("keeps the bordered editor inside `width` when the cursor lands past a wide trailing grapheme (#3431)", () => {
|
||||
// Regression: typing a fullwidth char (e.g. CJK comma `,`, U+FF0C) at the end
|
||||
// of the input used to push the bottom-right `─╯` 1–2 cells past the terminal
|
||||
// edge, wrapping `╯` to its own row. The end-of-line cursor glyph + wide grapheme
|
||||
// extends into the right padding zone; the right chrome must shrink by the exact
|
||||
// overflow cell count.
|
||||
for (const paddingX of [1, 2]) {
|
||||
const theme = { ...defaultEditorTheme, editorPaddingX: paddingX };
|
||||
const minContentWidth = 2 * (paddingX + 1) + 3; // chrome + "," (2) + cursor (1)
|
||||
for (let width = minContentWidth; width <= minContentWidth + 6; width++) {
|
||||
const editor = new Editor(theme);
|
||||
editor.focused = true;
|
||||
for (const c of "asd,") editor.handleInput(c);
|
||||
const lines = editor.render(width);
|
||||
for (const line of lines) {
|
||||
const stripped = line.replaceAll(CURSOR_MARKER, "");
|
||||
expect(visibleWidth(stripped)).toBeLessThanOrEqual(width);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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