mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 09:01:14 +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
|
||||
|
||||
- **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))
|
||||
|
||||
## [0.9.3] - 2025-11-24
|
||||
|
|
|
|||
|
|
@ -203,33 +203,60 @@ function wrapSingleLine(line: string, width: number): string[] {
|
|||
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[] {
|
||||
const lines: string[] = [];
|
||||
let currentLine = tracker.getActiveCodes();
|
||||
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;
|
||||
const segments: Array<{ type: "ansi" | "grapheme"; value: string }> = [];
|
||||
|
||||
while (i < word.length) {
|
||||
const ansiResult = extractAnsiCode(word, i);
|
||||
if (ansiResult) {
|
||||
currentLine += ansiResult.code;
|
||||
tracker.process(ansiResult.code);
|
||||
segments.push({ type: "ansi", value: ansiResult.code });
|
||||
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;
|
||||
}
|
||||
|
||||
const char = word[i];
|
||||
const charWidth = visibleWidth(char);
|
||||
const grapheme = seg.value;
|
||||
const graphemeWidth = visibleWidth(grapheme);
|
||||
|
||||
if (currentWidth + charWidth > width) {
|
||||
if (currentWidth + graphemeWidth > width) {
|
||||
lines.push(currentLine);
|
||||
currentLine = tracker.getActiveCodes();
|
||||
currentWidth = 0;
|
||||
}
|
||||
|
||||
currentLine += char;
|
||||
currentWidth += charWidth;
|
||||
i++;
|
||||
currentLine += grapheme;
|
||||
currentWidth += graphemeWidth;
|
||||
}
|
||||
|
||||
if (currentLine) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue