mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 06:04:51 +00:00
fix: emoji text wrapping crash in pi-tui
Fixed crash when rendering text containing emojis followed by long content. The breakLongWord function was iterating over UTF-16 code units instead of grapheme clusters, causing emojis (surrogate pairs) to be miscounted during line wrapping. Now uses Intl.Segmenter to properly handle multi-codepoint characters.
This commit is contained in:
parent
5b940c2686
commit
e7c48e33a2
3 changed files with 48 additions and 8 deletions
12
fuck.txt
Normal file
12
fuck.txt
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
↑21 ↓442 R12k W10k $0.048 3.7% claude-sonnet-4-5file:///opt/homebrew/lib/node_modules/@mariozechner/pi-coding-agent/node_modules/@mariozechner/pi-tui/dist/tui.js:174
|
||||||
|
throw new Error(`Rendered line ${i} exceeds terminal width\n\n${newLines[i]}`);
|
||||||
|
^
|
||||||
|
|
||||||
|
Error: Rendered line 160 exceeds terminal width
|
||||||
|
|
||||||
|
😂\n\n[2025-11-21T21-45-04-863Z_b85e1b94-c1f2-4592-8521-b36cb24f93e7.html](https:
|
||||||
|
at TUI.doRender (file:///opt/homebrew/lib/node_modules/@mariozechner/pi-coding-agent/node_modules/@mariozechner/pi-tui/dist/tui.js:174:23)
|
||||||
|
at file:///opt/homebrew/lib/node_modules/@mariozechner/pi-coding-agent/node_modules/@mariozechner/pi-tui/dist/tui.js:68:18
|
||||||
|
at process.processTicksAndRejections (node:internal/process/task_queues:85:11)
|
||||||
|
|
||||||
|
Node.js v23.4.0
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- **Emoji Text Wrapping Crash**: Fixed crash when rendering text containing emojis (e.g., 😂) followed by long content like URLs. The `breakLongWord` function in `pi-tui` was iterating over UTF-16 code units instead of grapheme clusters, causing emojis (which are surrogate pairs) to be miscounted during line wrapping. Now uses `Intl.Segmenter` to properly handle multi-codepoint characters.
|
||||||
- **Footer Cost Display**: Added `$` prefix to cost display in footer. Now shows `$0.078` instead of `0.078`. ([#53](https://github.com/badlogic/pi-mono/issues/53))
|
- **Footer Cost Display**: Added `$` prefix to cost display in footer. Now shows `$0.078` instead of `0.078`. ([#53](https://github.com/badlogic/pi-mono/issues/53))
|
||||||
|
|
||||||
## [0.9.3] - 2025-11-24
|
## [0.9.3] - 2025-11-24
|
||||||
|
|
|
||||||
|
|
@ -203,33 +203,60 @@ function wrapSingleLine(line: string, width: number): string[] {
|
||||||
return wrapped.length > 0 ? wrapped : [""];
|
return wrapped.length > 0 ? wrapped : [""];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Grapheme segmenter for proper Unicode iteration (handles emojis, etc.)
|
||||||
|
const segmenter = new Intl.Segmenter();
|
||||||
|
|
||||||
function breakLongWord(word: string, width: number, tracker: AnsiCodeTracker): string[] {
|
function breakLongWord(word: string, width: number, tracker: AnsiCodeTracker): string[] {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
let currentLine = tracker.getActiveCodes();
|
let currentLine = tracker.getActiveCodes();
|
||||||
let currentWidth = 0;
|
let currentWidth = 0;
|
||||||
|
|
||||||
|
// First, separate ANSI codes from visible content
|
||||||
|
// We need to handle ANSI codes specially since they're not graphemes
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
const segments: Array<{ type: "ansi" | "grapheme"; value: string }> = [];
|
||||||
|
|
||||||
while (i < word.length) {
|
while (i < word.length) {
|
||||||
const ansiResult = extractAnsiCode(word, i);
|
const ansiResult = extractAnsiCode(word, i);
|
||||||
if (ansiResult) {
|
if (ansiResult) {
|
||||||
currentLine += ansiResult.code;
|
segments.push({ type: "ansi", value: ansiResult.code });
|
||||||
tracker.process(ansiResult.code);
|
|
||||||
i += ansiResult.length;
|
i += ansiResult.length;
|
||||||
|
} else {
|
||||||
|
// Find the next ANSI code or end of string
|
||||||
|
let end = i;
|
||||||
|
while (end < word.length) {
|
||||||
|
const nextAnsi = extractAnsiCode(word, end);
|
||||||
|
if (nextAnsi) break;
|
||||||
|
end++;
|
||||||
|
}
|
||||||
|
// Segment this non-ANSI portion into graphemes
|
||||||
|
const textPortion = word.slice(i, end);
|
||||||
|
for (const seg of segmenter.segment(textPortion)) {
|
||||||
|
segments.push({ type: "grapheme", value: seg.segment });
|
||||||
|
}
|
||||||
|
i = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now process segments
|
||||||
|
for (const seg of segments) {
|
||||||
|
if (seg.type === "ansi") {
|
||||||
|
currentLine += seg.value;
|
||||||
|
tracker.process(seg.value);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char = word[i];
|
const grapheme = seg.value;
|
||||||
const charWidth = visibleWidth(char);
|
const graphemeWidth = visibleWidth(grapheme);
|
||||||
|
|
||||||
if (currentWidth + charWidth > width) {
|
if (currentWidth + graphemeWidth > width) {
|
||||||
lines.push(currentLine);
|
lines.push(currentLine);
|
||||||
currentLine = tracker.getActiveCodes();
|
currentLine = tracker.getActiveCodes();
|
||||||
currentWidth = 0;
|
currentWidth = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentLine += char;
|
currentLine += grapheme;
|
||||||
currentWidth += charWidth;
|
currentWidth += graphemeWidth;
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentLine) {
|
if (currentLine) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue