mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
parent
65ff8e7f6d
commit
37bedb7f0b
3 changed files with 45 additions and 11 deletions
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed narrow terminals crashing when the editor's bottom scroll indicator exceeded the terminal width ([#6962](https://github.com/earendil-works/pi/issues/6962)).
|
||||||
|
|
||||||
## [0.81.1] - 2026-07-21
|
## [0.81.1] - 2026-07-21
|
||||||
|
|
||||||
## [0.81.0] - 2026-07-21
|
## [0.81.0] - 2026-07-21
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
getGraphemeSegmenter,
|
getGraphemeSegmenter,
|
||||||
getWordSegmenter,
|
getWordSegmenter,
|
||||||
isWhitespaceChar,
|
isWhitespaceChar,
|
||||||
truncateToWidth,
|
sliceByColumn,
|
||||||
visibleWidth,
|
visibleWidth,
|
||||||
} from "../utils.ts";
|
} from "../utils.ts";
|
||||||
import { findWordBackward, findWordForward } from "../word-navigation.ts";
|
import { findWordBackward, findWordForward } from "../word-navigation.ts";
|
||||||
|
|
@ -256,6 +256,17 @@ function buildDebouncePattern(triggerCharacters: string[]): RegExp {
|
||||||
return new RegExp(`(?:^|[ \\t])(?:@(?:"[^"]*|[^\\s]*)|[${escapedWithoutAt.join("")}][^\\s]*)$`);
|
return new RegExp(`(?:^|[ \\t])(?:@(?:"[^"]*|[^\\s]*)|[${escapedWithoutAt.join("")}][^\\s]*)$`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createScrollBorder(direction: "↑" | "↓", hiddenLineCount: number, width: number): string {
|
||||||
|
const availableWidth = Math.max(0, width);
|
||||||
|
const indicator = `─── ${direction} ${hiddenLineCount} more `;
|
||||||
|
const remaining = availableWidth - visibleWidth(indicator);
|
||||||
|
if (remaining >= 0) return indicator + "─".repeat(remaining);
|
||||||
|
|
||||||
|
const ellipsis = "...".slice(0, availableWidth);
|
||||||
|
const indicatorWidth = availableWidth - visibleWidth(ellipsis);
|
||||||
|
return sliceByColumn(indicator, 0, indicatorWidth, true) + ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
export class Editor implements Component, Focusable {
|
export class Editor implements Component, Focusable {
|
||||||
private state: EditorState = {
|
private state: EditorState = {
|
||||||
lines: [""],
|
lines: [""],
|
||||||
|
|
@ -513,13 +524,8 @@ export class Editor implements Component, Focusable {
|
||||||
|
|
||||||
// Render top border (with scroll indicator if scrolled down)
|
// Render top border (with scroll indicator if scrolled down)
|
||||||
if (this.scrollOffset > 0) {
|
if (this.scrollOffset > 0) {
|
||||||
const indicator = `─── ↑ ${this.scrollOffset} more `;
|
const border = createScrollBorder("↑", this.scrollOffset, width);
|
||||||
const remaining = width - visibleWidth(indicator);
|
result.push(this.borderColor(border));
|
||||||
if (remaining >= 0) {
|
|
||||||
result.push(this.borderColor(indicator + "─".repeat(remaining)));
|
|
||||||
} else {
|
|
||||||
result.push(this.borderColor(truncateToWidth(indicator, width)));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
result.push(horizontal.repeat(width));
|
result.push(horizontal.repeat(width));
|
||||||
}
|
}
|
||||||
|
|
@ -575,9 +581,8 @@ export class Editor implements Component, Focusable {
|
||||||
// Render bottom border (with scroll indicator if more content below)
|
// Render bottom border (with scroll indicator if more content below)
|
||||||
const linesBelow = layoutLines.length - (this.scrollOffset + visibleLines.length);
|
const linesBelow = layoutLines.length - (this.scrollOffset + visibleLines.length);
|
||||||
if (linesBelow > 0) {
|
if (linesBelow > 0) {
|
||||||
const indicator = `─── ↓ ${linesBelow} more `;
|
const border = createScrollBorder("↓", linesBelow, width);
|
||||||
const remaining = width - visibleWidth(indicator);
|
result.push(this.borderColor(border));
|
||||||
result.push(this.borderColor(indicator + "─".repeat(Math.max(0, remaining))));
|
|
||||||
} else {
|
} else {
|
||||||
result.push(horizontal.repeat(width));
|
result.push(horizontal.repeat(width));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -699,6 +699,31 @@ describe("Editor component", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Scroll indicators", () => {
|
||||||
|
it("keeps truncated scroll indicators within width and preserves their color (issue #6962)", () => {
|
||||||
|
const width = 10;
|
||||||
|
const borderColor = (text: string) => `\x1b[35m${text}\x1b[39m`;
|
||||||
|
const editor = new Editor(createTestTUI(width), { ...defaultEditorTheme, borderColor });
|
||||||
|
editor.setText(Array.from({ length: 20 }, (_, index) => `line ${index}`).join("\n"));
|
||||||
|
|
||||||
|
// Render once to initialize wrapping, then move the cursor so content remains above and below the viewport.
|
||||||
|
editor.render(width);
|
||||||
|
for (let index = 0; index < 10; index++) editor.handleInput("\x1b[A");
|
||||||
|
|
||||||
|
const lines = editor.render(width);
|
||||||
|
const topBorder = lines[0]!;
|
||||||
|
const bottomBorder = lines.at(-1)!;
|
||||||
|
|
||||||
|
assert.match(stripVTControlCharacters(topBorder), /^─── ↑/);
|
||||||
|
assert.match(stripVTControlCharacters(bottomBorder), /^─── ↓/);
|
||||||
|
assert.strictEqual(topBorder, borderColor(stripVTControlCharacters(topBorder)));
|
||||||
|
assert.strictEqual(bottomBorder, borderColor(stripVTControlCharacters(bottomBorder)));
|
||||||
|
for (const line of lines) {
|
||||||
|
assert.strictEqual(visibleWidth(line), width, `line exceeds width ${width}: ${JSON.stringify(line)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Grapheme-aware text wrapping", () => {
|
describe("Grapheme-aware text wrapping", () => {
|
||||||
it("wraps lines correctly when text contains wide emojis", () => {
|
it("wraps lines correctly when text contains wide emojis", () => {
|
||||||
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
const editor = new Editor(createTestTUI(), defaultEditorTheme);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue