@fleetagent/pi-tui 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/editor.d.ts.map +1 -1
- package/dist/components/editor.js +24 -83
- package/dist/components/editor.js.map +1 -1
- package/dist/components/input.d.ts.map +1 -1
- package/dist/components/input.js +7 -55
- package/dist/components/input.js.map +1 -1
- package/dist/components/markdown.d.ts +7 -1
- package/dist/components/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +12 -2
- package/dist/components/markdown.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/native-modifiers.d.ts +3 -0
- package/dist/native-modifiers.d.ts.map +1 -0
- package/dist/native-modifiers.js +53 -0
- package/dist/native-modifiers.js.map +1 -0
- package/dist/terminal-image.d.ts +1 -1
- package/dist/terminal-image.d.ts.map +1 -1
- package/dist/terminal-image.js +38 -8
- package/dist/terminal-image.js.map +1 -1
- package/dist/terminal.d.ts +35 -10
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +182 -35
- package/dist/terminal.js.map +1 -1
- package/dist/utils.d.ts +6 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +27 -15
- package/dist/utils.js.map +1 -1
- package/dist/word-navigation.d.ts +25 -0
- package/dist/word-navigation.d.ts.map +1 -0
- package/dist/word-navigation.js +96 -0
- package/dist/word-navigation.js.map +1 -0
- package/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
- package/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
- package/package.json +2 -1
|
@@ -3,9 +3,11 @@ import { decodePrintableKey, matchesKey } from "../keys.js";
|
|
|
3
3
|
import { KillRing } from "../kill-ring.js";
|
|
4
4
|
import { CURSOR_MARKER } from "../tui.js";
|
|
5
5
|
import { UndoStack } from "../undo-stack.js";
|
|
6
|
-
import {
|
|
6
|
+
import { getGraphemeSegmenter, getWordSegmenter, isWhitespaceChar, truncateToWidth, visibleWidth } from "../utils.js";
|
|
7
|
+
import { findWordBackward, findWordForward } from "../word-navigation.js";
|
|
7
8
|
import { SelectList } from "./select-list.js";
|
|
8
|
-
const
|
|
9
|
+
const graphemeSegmenter = getGraphemeSegmenter();
|
|
10
|
+
const wordSegmenter = getWordSegmenter();
|
|
9
11
|
/** Regex matching paste markers like `[paste #1 +123 lines]` or `[paste #2 1234 chars]`. */
|
|
10
12
|
const PASTE_MARKER_REGEX = /\[paste #(\d+)( (\+\d+ lines|\d+ chars))?\]/g;
|
|
11
13
|
/** Non-global version for single-segment testing. */
|
|
@@ -21,7 +23,7 @@ function isPasteMarker(segment) {
|
|
|
21
23
|
*
|
|
22
24
|
* Only markers whose numeric ID exists in `validIds` are merged.
|
|
23
25
|
*/
|
|
24
|
-
function segmentWithMarkers(text, validIds) {
|
|
26
|
+
function segmentWithMarkers(text, baseSegmenter, validIds) {
|
|
25
27
|
// Fast path: no paste markers in the text or no valid IDs.
|
|
26
28
|
if (validIds.size === 0 || !text.includes("[paste #")) {
|
|
27
29
|
return baseSegmenter.segment(text);
|
|
@@ -86,7 +88,7 @@ export function wordWrapLine(line, maxWidth, preSegmented) {
|
|
|
86
88
|
return [{ text: line, startIndex: 0, endIndex: line.length }];
|
|
87
89
|
}
|
|
88
90
|
const chunks = [];
|
|
89
|
-
const segments = preSegmented ?? [...
|
|
91
|
+
const segments = preSegmented ?? [...graphemeSegmenter.segment(line)];
|
|
90
92
|
let currentWidth = 0;
|
|
91
93
|
let chunkStart = 0;
|
|
92
94
|
// Wrap opportunity: the position after the last whitespace before a non-whitespace
|
|
@@ -225,8 +227,8 @@ export class Editor {
|
|
|
225
227
|
return new Set(this.pastes.keys());
|
|
226
228
|
}
|
|
227
229
|
/** Segment text with paste-marker awareness, only merging markers with valid IDs. */
|
|
228
|
-
segment(text) {
|
|
229
|
-
return segmentWithMarkers(text, this.validPasteIds());
|
|
230
|
+
segment(text, mode) {
|
|
231
|
+
return segmentWithMarkers(text, mode === "word" ? wordSegmenter : graphemeSegmenter, this.validPasteIds());
|
|
230
232
|
}
|
|
231
233
|
getPaddingX() {
|
|
232
234
|
return this.paddingX;
|
|
@@ -381,7 +383,7 @@ export class Editor {
|
|
|
381
383
|
if (after.length > 0) {
|
|
382
384
|
// Cursor is on a character (grapheme) - replace it with highlighted version
|
|
383
385
|
// Get the first grapheme from 'after'
|
|
384
|
-
const afterGraphemes = [...this.segment(after)];
|
|
386
|
+
const afterGraphemes = [...this.segment(after, "grapheme")];
|
|
385
387
|
const firstGrapheme = afterGraphemes[0]?.segment || "";
|
|
386
388
|
const restAfter = after.slice(firstGrapheme.length);
|
|
387
389
|
const cursor = `\x1b[7m${firstGrapheme}\x1b[0m`;
|
|
@@ -717,7 +719,7 @@ export class Editor {
|
|
|
717
719
|
}
|
|
718
720
|
else {
|
|
719
721
|
// Line needs wrapping - use word-aware wrapping
|
|
720
|
-
const chunks = wordWrapLine(line, contentWidth, [...this.segment(line)]);
|
|
722
|
+
const chunks = wordWrapLine(line, contentWidth, [...this.segment(line, "grapheme")]);
|
|
721
723
|
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
|
|
722
724
|
const chunk = chunks[chunkIndex];
|
|
723
725
|
if (!chunk)
|
|
@@ -1030,7 +1032,7 @@ export class Editor {
|
|
|
1030
1032
|
const line = this.state.lines[this.state.cursorLine] || "";
|
|
1031
1033
|
const beforeCursor = line.slice(0, this.state.cursorCol);
|
|
1032
1034
|
// Find the last grapheme in the text before cursor
|
|
1033
|
-
const graphemes = [...this.segment(beforeCursor)];
|
|
1035
|
+
const graphemes = [...this.segment(beforeCursor, "grapheme")];
|
|
1034
1036
|
const lastGrapheme = graphemes[graphemes.length - 1];
|
|
1035
1037
|
const graphemeLength = lastGrapheme ? lastGrapheme.segment.length : 1;
|
|
1036
1038
|
const before = line.slice(0, this.state.cursorCol - graphemeLength);
|
|
@@ -1114,7 +1116,7 @@ export class Editor {
|
|
|
1114
1116
|
// Snap cursor to atomic segment boundary (e.g. paste markers)
|
|
1115
1117
|
// so the cursor never lands in the middle of a multi-grapheme unit.
|
|
1116
1118
|
// Single-grapheme segments don't need snapping.
|
|
1117
|
-
const segments = [...this.segment(logicalLine)];
|
|
1119
|
+
const segments = [...this.segment(logicalLine, "grapheme")];
|
|
1118
1120
|
for (const seg of segments) {
|
|
1119
1121
|
if (seg.index > this.state.cursorCol)
|
|
1120
1122
|
break;
|
|
@@ -1334,7 +1336,7 @@ export class Editor {
|
|
|
1334
1336
|
// Delete grapheme at cursor position (handles emojis, combining characters, etc.)
|
|
1335
1337
|
const afterCursor = currentLine.slice(this.state.cursorCol);
|
|
1336
1338
|
// Find the first grapheme at cursor
|
|
1337
|
-
const graphemes = [...this.segment(afterCursor)];
|
|
1339
|
+
const graphemes = [...this.segment(afterCursor, "grapheme")];
|
|
1338
1340
|
const firstGrapheme = graphemes[0];
|
|
1339
1341
|
const graphemeLength = firstGrapheme ? firstGrapheme.segment.length : 1;
|
|
1340
1342
|
const before = currentLine.slice(0, this.state.cursorCol);
|
|
@@ -1389,7 +1391,7 @@ export class Editor {
|
|
|
1389
1391
|
}
|
|
1390
1392
|
else {
|
|
1391
1393
|
// Line needs wrapping - use word-aware wrapping
|
|
1392
|
-
const chunks = wordWrapLine(line, width, [...this.segment(line)]);
|
|
1394
|
+
const chunks = wordWrapLine(line, width, [...this.segment(line, "grapheme")]);
|
|
1393
1395
|
for (const chunk of chunks) {
|
|
1394
1396
|
visualLines.push({
|
|
1395
1397
|
logicalLine: i,
|
|
@@ -1441,7 +1443,7 @@ export class Editor {
|
|
|
1441
1443
|
// Moving right - move by one grapheme (handles emojis, combining characters, etc.)
|
|
1442
1444
|
if (this.state.cursorCol < currentLine.length) {
|
|
1443
1445
|
const afterCursor = currentLine.slice(this.state.cursorCol);
|
|
1444
|
-
const graphemes = [...this.segment(afterCursor)];
|
|
1446
|
+
const graphemes = [...this.segment(afterCursor, "grapheme")];
|
|
1445
1447
|
const firstGrapheme = graphemes[0];
|
|
1446
1448
|
this.setCursorCol(this.state.cursorCol + (firstGrapheme ? firstGrapheme.segment.length : 1));
|
|
1447
1449
|
}
|
|
@@ -1462,7 +1464,7 @@ export class Editor {
|
|
|
1462
1464
|
// Moving left - move by one grapheme (handles emojis, combining characters, etc.)
|
|
1463
1465
|
if (this.state.cursorCol > 0) {
|
|
1464
1466
|
const beforeCursor = currentLine.slice(0, this.state.cursorCol);
|
|
1465
|
-
const graphemes = [...this.segment(beforeCursor)];
|
|
1467
|
+
const graphemes = [...this.segment(beforeCursor, "grapheme")];
|
|
1466
1468
|
const lastGrapheme = graphemes[graphemes.length - 1];
|
|
1467
1469
|
this.setCursorCol(this.state.cursorCol - (lastGrapheme ? lastGrapheme.segment.length : 1));
|
|
1468
1470
|
}
|
|
@@ -1500,40 +1502,10 @@ export class Editor {
|
|
|
1500
1502
|
}
|
|
1501
1503
|
return;
|
|
1502
1504
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
while (graphemes.length > 0 &&
|
|
1508
|
-
!isPasteMarker(graphemes[graphemes.length - 1]?.segment || "") &&
|
|
1509
|
-
isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || "")) {
|
|
1510
|
-
newCol -= graphemes.pop()?.segment.length || 0;
|
|
1511
|
-
}
|
|
1512
|
-
if (graphemes.length > 0) {
|
|
1513
|
-
const lastGrapheme = graphemes[graphemes.length - 1]?.segment || "";
|
|
1514
|
-
if (isPasteMarker(lastGrapheme)) {
|
|
1515
|
-
// Paste marker is a single atomic word
|
|
1516
|
-
newCol -= graphemes.pop()?.segment.length || 0;
|
|
1517
|
-
}
|
|
1518
|
-
else if (isPunctuationChar(lastGrapheme)) {
|
|
1519
|
-
// Skip punctuation run
|
|
1520
|
-
while (graphemes.length > 0 &&
|
|
1521
|
-
isPunctuationChar(graphemes[graphemes.length - 1]?.segment || "") &&
|
|
1522
|
-
!isPasteMarker(graphemes[graphemes.length - 1]?.segment || "")) {
|
|
1523
|
-
newCol -= graphemes.pop()?.segment.length || 0;
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
else {
|
|
1527
|
-
// Skip word run
|
|
1528
|
-
while (graphemes.length > 0 &&
|
|
1529
|
-
!isWhitespaceChar(graphemes[graphemes.length - 1]?.segment || "") &&
|
|
1530
|
-
!isPunctuationChar(graphemes[graphemes.length - 1]?.segment || "") &&
|
|
1531
|
-
!isPasteMarker(graphemes[graphemes.length - 1]?.segment || "")) {
|
|
1532
|
-
newCol -= graphemes.pop()?.segment.length || 0;
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
this.setCursorCol(newCol);
|
|
1505
|
+
this.setCursorCol(findWordBackward(currentLine, this.state.cursorCol, {
|
|
1506
|
+
segment: (text) => this.segment(text, "word"),
|
|
1507
|
+
isAtomicSegment: isPasteMarker,
|
|
1508
|
+
}));
|
|
1537
1509
|
}
|
|
1538
1510
|
/**
|
|
1539
1511
|
* Yank (paste) the most recent kill ring entry at cursor position.
|
|
@@ -1690,41 +1662,10 @@ export class Editor {
|
|
|
1690
1662
|
}
|
|
1691
1663
|
return;
|
|
1692
1664
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
let newCol = this.state.cursorCol;
|
|
1698
|
-
// Skip leading whitespace
|
|
1699
|
-
while (!next.done && !isPasteMarker(next.value.segment) && isWhitespaceChar(next.value.segment)) {
|
|
1700
|
-
newCol += next.value.segment.length;
|
|
1701
|
-
next = iterator.next();
|
|
1702
|
-
}
|
|
1703
|
-
if (!next.done) {
|
|
1704
|
-
const firstGrapheme = next.value.segment;
|
|
1705
|
-
if (isPasteMarker(firstGrapheme)) {
|
|
1706
|
-
// Paste marker is a single atomic word
|
|
1707
|
-
newCol += firstGrapheme.length;
|
|
1708
|
-
}
|
|
1709
|
-
else if (isPunctuationChar(firstGrapheme)) {
|
|
1710
|
-
// Skip punctuation run
|
|
1711
|
-
while (!next.done && isPunctuationChar(next.value.segment) && !isPasteMarker(next.value.segment)) {
|
|
1712
|
-
newCol += next.value.segment.length;
|
|
1713
|
-
next = iterator.next();
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
else {
|
|
1717
|
-
// Skip word run
|
|
1718
|
-
while (!next.done &&
|
|
1719
|
-
!isWhitespaceChar(next.value.segment) &&
|
|
1720
|
-
!isPunctuationChar(next.value.segment) &&
|
|
1721
|
-
!isPasteMarker(next.value.segment)) {
|
|
1722
|
-
newCol += next.value.segment.length;
|
|
1723
|
-
next = iterator.next();
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
this.setCursorCol(newCol);
|
|
1665
|
+
this.setCursorCol(findWordForward(currentLine, this.state.cursorCol, {
|
|
1666
|
+
segment: (text) => this.segment(text, "word"),
|
|
1667
|
+
isAtomicSegment: isPasteMarker,
|
|
1668
|
+
}));
|
|
1728
1669
|
}
|
|
1729
1670
|
// Slash menu only allowed on the first line of the editor
|
|
1730
1671
|
isSlashMenuAllowed() {
|