@earendil-works/pi-tui 0.84.4 → 0.85.1
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/README.md +53 -4
- package/dist/alt-screen-search.d.ts +21 -1
- package/dist/alt-screen-search.d.ts.map +1 -1
- package/dist/alt-screen-search.js +202 -42
- package/dist/alt-screen-search.js.map +1 -1
- package/dist/components/box.d.ts +3 -1
- package/dist/components/box.d.ts.map +1 -1
- package/dist/components/box.js +29 -0
- package/dist/components/box.js.map +1 -1
- package/dist/components/editor.d.ts +6 -1
- package/dist/components/editor.d.ts.map +1 -1
- package/dist/components/editor.js +98 -18
- package/dist/components/editor.js.map +1 -1
- package/dist/components/input.d.ts +12 -1
- package/dist/components/input.d.ts.map +1 -1
- package/dist/components/input.js +44 -5
- package/dist/components/input.js.map +1 -1
- package/dist/components/loader.d.ts +2 -0
- package/dist/components/loader.d.ts.map +1 -1
- package/dist/components/loader.js +10 -3
- package/dist/components/loader.js.map +1 -1
- package/dist/components/mouse-region.d.ts +12 -0
- package/dist/components/mouse-region.d.ts.map +1 -0
- package/dist/components/mouse-region.js +21 -0
- package/dist/components/mouse-region.js.map +1 -0
- package/dist/components/scroll-view.d.ts +6 -3
- package/dist/components/scroll-view.d.ts.map +1 -1
- package/dist/components/scroll-view.js +8 -2
- package/dist/components/scroll-view.js.map +1 -1
- package/dist/components/select-list.d.ts +4 -1
- package/dist/components/select-list.d.ts.map +1 -1
- package/dist/components/select-list.js +49 -2
- package/dist/components/select-list.js.map +1 -1
- package/dist/components/settings-list.d.ts +5 -1
- package/dist/components/settings-list.d.ts.map +1 -1
- package/dist/components/settings-list.js +55 -5
- package/dist/components/settings-list.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/latex.d.ts.map +1 -1
- package/dist/latex.js +14 -0
- package/dist/latex.js.map +1 -1
- package/dist/layout.d.ts +3 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +32 -9
- package/dist/layout.js.map +1 -1
- package/dist/terminal-image.d.ts.map +1 -1
- package/dist/terminal-image.js +1 -4
- package/dist/terminal-image.js.map +1 -1
- package/dist/terminal.d.ts +6 -0
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +17 -4
- package/dist/terminal.js.map +1 -1
- package/dist/tui-alt-screen.d.ts +31 -1
- package/dist/tui-alt-screen.d.ts.map +1 -1
- package/dist/tui-alt-screen.js +342 -41
- package/dist/tui-alt-screen.js.map +1 -1
- package/dist/tui-main-screen.d.ts.map +1 -1
- package/dist/tui-main-screen.js +6 -5
- package/dist/tui-main-screen.js.map +1 -1
- package/dist/tui.d.ts +84 -6
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +123 -9
- package/dist/tui.js.map +1 -1
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +9 -0
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getKeybindings } from "../keybindings.js";
|
|
2
2
|
import { decodePrintableKey, matchesKey } from "../keys.js";
|
|
3
3
|
import { KillRing } from "../kill-ring.js";
|
|
4
|
-
import { CURSOR_MARKER } from "../tui.js";
|
|
4
|
+
import { CURSOR_MARKER, } from "../tui.js";
|
|
5
5
|
import { UndoStack } from "../undo-stack.js";
|
|
6
6
|
import { cjkBreakRegex, getGraphemeSegmenter, getWordSegmenter, isWhitespaceChar, sliceByColumn, visibleWidth, } from "../utils.js";
|
|
7
7
|
import { findWordBackward, findWordForward } from "../word-navigation.js";
|
|
@@ -180,6 +180,12 @@ function buildDebouncePattern(triggerCharacters) {
|
|
|
180
180
|
}
|
|
181
181
|
function createScrollBorder(direction, hiddenLineCount, width) {
|
|
182
182
|
const availableWidth = Math.max(0, width);
|
|
183
|
+
const label = ` ${direction} ${hiddenLineCount} more `;
|
|
184
|
+
const labelWidth = visibleWidth(label);
|
|
185
|
+
if (labelWidth + 2 <= availableWidth) {
|
|
186
|
+
const leftWidth = Math.floor((availableWidth - labelWidth) / 2);
|
|
187
|
+
return "─".repeat(leftWidth) + label + "─".repeat(availableWidth - leftWidth - labelWidth);
|
|
188
|
+
}
|
|
183
189
|
const indicator = `─── ${direction} ${hiddenLineCount} more `;
|
|
184
190
|
const remaining = availableWidth - visibleWidth(indicator);
|
|
185
191
|
if (remaining >= 0)
|
|
@@ -199,8 +205,10 @@ export class Editor {
|
|
|
199
205
|
tui;
|
|
200
206
|
theme;
|
|
201
207
|
paddingX = 0;
|
|
202
|
-
// Store last render
|
|
208
|
+
// Store last render geometry for cursor navigation and mouse hit-testing.
|
|
203
209
|
lastWidth = 80;
|
|
210
|
+
renderedVisibleLineCount = 1;
|
|
211
|
+
renderedAutocompleteHeight = 0;
|
|
204
212
|
// Vertical scrolling support
|
|
205
213
|
scrollOffset = 0;
|
|
206
214
|
// Border color (can be changed dynamically)
|
|
@@ -370,6 +378,14 @@ export class Editor {
|
|
|
370
378
|
invalidate() {
|
|
371
379
|
// No cached state to invalidate currently
|
|
372
380
|
}
|
|
381
|
+
renderTopBorder(width, hiddenLineCount) {
|
|
382
|
+
const border = hiddenLineCount > 0 ? createScrollBorder("↑", hiddenLineCount, width) : "─".repeat(width);
|
|
383
|
+
return this.borderColor(border);
|
|
384
|
+
}
|
|
385
|
+
renderBottomBorder(width, hiddenLineCount) {
|
|
386
|
+
const border = hiddenLineCount > 0 ? createScrollBorder("↓", hiddenLineCount, width) : "─".repeat(width);
|
|
387
|
+
return this.borderColor(border);
|
|
388
|
+
}
|
|
373
389
|
render(width) {
|
|
374
390
|
const maxPadding = Math.max(0, Math.floor((width - 1) / 2));
|
|
375
391
|
const paddingX = Math.min(this.paddingX, maxPadding);
|
|
@@ -379,7 +395,6 @@ export class Editor {
|
|
|
379
395
|
const layoutWidth = Math.max(1, contentWidth - (paddingX ? 0 : 1));
|
|
380
396
|
// Store for cursor navigation (must match wrapping width)
|
|
381
397
|
this.lastWidth = layoutWidth;
|
|
382
|
-
const horizontal = this.borderColor("─");
|
|
383
398
|
// Layout the text
|
|
384
399
|
const layoutLines = this.layoutText(layoutWidth);
|
|
385
400
|
// Calculate max visible lines: 30% of terminal height, minimum 5 lines
|
|
@@ -401,17 +416,12 @@ export class Editor {
|
|
|
401
416
|
this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxScrollOffset));
|
|
402
417
|
// Get visible lines slice
|
|
403
418
|
const visibleLines = layoutLines.slice(this.scrollOffset, this.scrollOffset + maxVisibleLines);
|
|
419
|
+
this.renderedVisibleLineCount = visibleLines.length;
|
|
404
420
|
const result = [];
|
|
405
421
|
const leftPadding = " ".repeat(paddingX);
|
|
406
422
|
const rightPadding = leftPadding;
|
|
407
423
|
// Render top border (with scroll indicator if scrolled down)
|
|
408
|
-
|
|
409
|
-
const border = createScrollBorder("↑", this.scrollOffset, width);
|
|
410
|
-
result.push(this.borderColor(border));
|
|
411
|
-
}
|
|
412
|
-
else {
|
|
413
|
-
result.push(horizontal.repeat(width));
|
|
414
|
-
}
|
|
424
|
+
result.push(this.renderTopBorder(width, this.scrollOffset));
|
|
415
425
|
// Render each visible layout line
|
|
416
426
|
// Emit hardware cursor marker when focused so TUI can position the
|
|
417
427
|
// hardware cursor for IME candidate-window placement even while
|
|
@@ -456,16 +466,12 @@ export class Editor {
|
|
|
456
466
|
}
|
|
457
467
|
// Render bottom border (with scroll indicator if more content below)
|
|
458
468
|
const linesBelow = layoutLines.length - (this.scrollOffset + visibleLines.length);
|
|
459
|
-
|
|
460
|
-
const border = createScrollBorder("↓", linesBelow, width);
|
|
461
|
-
result.push(this.borderColor(border));
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
result.push(horizontal.repeat(width));
|
|
465
|
-
}
|
|
469
|
+
result.push(this.renderBottomBorder(width, linesBelow));
|
|
466
470
|
// Add autocomplete list if active
|
|
471
|
+
this.renderedAutocompleteHeight = 0;
|
|
467
472
|
if (this.autocompleteState && this.autocompleteList) {
|
|
468
473
|
const autocompleteResult = this.autocompleteList.render(contentWidth);
|
|
474
|
+
this.renderedAutocompleteHeight = autocompleteResult.length;
|
|
469
475
|
for (const line of autocompleteResult) {
|
|
470
476
|
const lineWidth = visibleWidth(line);
|
|
471
477
|
const linePadding = " ".repeat(Math.max(0, contentWidth - lineWidth));
|
|
@@ -474,6 +480,67 @@ export class Editor {
|
|
|
474
480
|
}
|
|
475
481
|
return result;
|
|
476
482
|
}
|
|
483
|
+
handleMouse(event) {
|
|
484
|
+
const autocompleteStartRow = this.renderedVisibleLineCount + 2;
|
|
485
|
+
if (this.autocompleteState &&
|
|
486
|
+
this.autocompleteList &&
|
|
487
|
+
event.y >= autocompleteStartRow &&
|
|
488
|
+
event.y < autocompleteStartRow + this.renderedAutocompleteHeight) {
|
|
489
|
+
const maxPadding = Math.max(0, Math.floor((event.width - 1) / 2));
|
|
490
|
+
const paddingX = Math.min(this.paddingX, maxPadding);
|
|
491
|
+
const contentWidth = Math.max(1, event.width - paddingX * 2);
|
|
492
|
+
const result = this.autocompleteList.handleMouse?.({
|
|
493
|
+
...event,
|
|
494
|
+
x: event.x - paddingX,
|
|
495
|
+
y: event.y - autocompleteStartRow,
|
|
496
|
+
width: contentWidth,
|
|
497
|
+
height: this.renderedAutocompleteHeight,
|
|
498
|
+
});
|
|
499
|
+
return result ? { ...result, focus: true } : undefined;
|
|
500
|
+
}
|
|
501
|
+
// Leave press/drag/release unhandled so the renderer's screen-level text
|
|
502
|
+
// selection can run over the editor rows (drag to select, release to copy).
|
|
503
|
+
// The renderer synthesizes a click when press and release land on the same
|
|
504
|
+
// cell without movement, which is the gesture that positions the cursor.
|
|
505
|
+
if (event.type !== "click" || event.button !== "left")
|
|
506
|
+
return undefined;
|
|
507
|
+
if (event.y <= 0 || event.y > this.renderedVisibleLineCount)
|
|
508
|
+
return { handled: true, focus: true };
|
|
509
|
+
const visualLines = this.buildVisualLineMap(this.lastWidth);
|
|
510
|
+
const visualLineIndex = this.scrollOffset + event.y - 1;
|
|
511
|
+
const visualLine = visualLines[visualLineIndex];
|
|
512
|
+
if (!visualLine)
|
|
513
|
+
return { handled: true, focus: true };
|
|
514
|
+
const logicalLine = this.state.lines[visualLine.logicalLine] ?? "";
|
|
515
|
+
const chunkEnd = visualLine.startCol + visualLine.length;
|
|
516
|
+
const chunk = logicalLine.slice(visualLine.startCol, chunkEnd);
|
|
517
|
+
const maxPadding = Math.max(0, Math.floor((event.width - 1) / 2));
|
|
518
|
+
const paddingX = Math.min(this.paddingX, maxPadding);
|
|
519
|
+
const targetColumn = Math.max(0, event.x - paddingX);
|
|
520
|
+
let visibleColumn = 0;
|
|
521
|
+
let targetIndex = chunk.length;
|
|
522
|
+
let lastGraphemeIndex = 0;
|
|
523
|
+
for (const grapheme of this.segment(chunk, "grapheme")) {
|
|
524
|
+
const nextColumn = visibleColumn + visibleWidth(grapheme.segment);
|
|
525
|
+
lastGraphemeIndex = grapheme.index;
|
|
526
|
+
if (targetColumn < nextColumn) {
|
|
527
|
+
targetIndex = grapheme.index;
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
530
|
+
visibleColumn = nextColumn;
|
|
531
|
+
}
|
|
532
|
+
const isLastSegment = visualLineIndex === visualLines.length - 1 ||
|
|
533
|
+
visualLines[visualLineIndex + 1]?.logicalLine !== visualLine.logicalLine;
|
|
534
|
+
if (!isLastSegment && targetIndex === chunk.length && chunk.length > 0)
|
|
535
|
+
targetIndex = lastGraphemeIndex;
|
|
536
|
+
this.state.cursorLine = visualLine.logicalLine;
|
|
537
|
+
this.setCursorCol(visualLine.startCol + targetIndex);
|
|
538
|
+
this.lastAction = null;
|
|
539
|
+
this.exitHistoryBrowsing();
|
|
540
|
+
if (this.autocompleteState)
|
|
541
|
+
this.updateAutocomplete();
|
|
542
|
+
return { handled: true, focus: true };
|
|
543
|
+
}
|
|
477
544
|
handleInput(data) {
|
|
478
545
|
const kb = getKeybindings();
|
|
479
546
|
// Handle character jump mode (awaiting next character to jump to)
|
|
@@ -1804,7 +1871,20 @@ export class Editor {
|
|
|
1804
1871
|
}
|
|
1805
1872
|
createAutocompleteList(prefix, items) {
|
|
1806
1873
|
const layout = prefix.startsWith("/") ? SLASH_COMMAND_SELECT_LIST_LAYOUT : undefined;
|
|
1807
|
-
|
|
1874
|
+
const list = new SelectList(items, this.autocompleteMaxVisible, this.theme.selectList, layout);
|
|
1875
|
+
list.onSelect = (selected) => {
|
|
1876
|
+
if (!this.autocompleteProvider)
|
|
1877
|
+
return;
|
|
1878
|
+
this.pushUndoSnapshot();
|
|
1879
|
+
this.lastAction = null;
|
|
1880
|
+
const result = this.autocompleteProvider.applyCompletion(this.state.lines, this.state.cursorLine, this.state.cursorCol, selected, this.autocompletePrefix);
|
|
1881
|
+
this.state.lines = result.lines;
|
|
1882
|
+
this.state.cursorLine = result.cursorLine;
|
|
1883
|
+
this.setCursorCol(result.cursorCol);
|
|
1884
|
+
this.cancelAutocomplete();
|
|
1885
|
+
this.onChange?.(this.getText());
|
|
1886
|
+
};
|
|
1887
|
+
return list;
|
|
1808
1888
|
}
|
|
1809
1889
|
tryTriggerAutocomplete(explicitTab = false) {
|
|
1810
1890
|
this.requestAutocomplete({ force: false, explicitTab });
|