@ashx-j/lunr-tui 0.2.7 → 0.2.9
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/tui.d.ts +15 -9
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +131 -104
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
package/dist/tui.js
CHANGED
|
@@ -9,7 +9,7 @@ import { isKeyRelease, matchesKey } from "./keys.js";
|
|
|
9
9
|
import { MOUSE_TRACKING_DISABLE, MOUSE_TRACKING_ENABLE, parseMouseEvent } from "./mouse.js";
|
|
10
10
|
import { isOsc11BackgroundColorResponse, parseOsc11BackgroundColor, parseTerminalColorSchemeReport, } from "./terminal-colors.js";
|
|
11
11
|
import { deleteKittyImage, getCapabilities, isImageLine, setCellDimensions } from "./terminal-image.js";
|
|
12
|
-
import {
|
|
12
|
+
import { extractSegments, normalizeTerminalOutput, sliceByColumn, sliceWithWidth, visibleWidth } from "./utils.js";
|
|
13
13
|
const KITTY_SEQUENCE_PREFIX = "\x1b_G";
|
|
14
14
|
function parseKittyImageHeader(line) {
|
|
15
15
|
const sequenceStart = line.indexOf(KITTY_SEQUENCE_PREFIX);
|
|
@@ -104,6 +104,20 @@ export class Container {
|
|
|
104
104
|
}
|
|
105
105
|
return lines;
|
|
106
106
|
}
|
|
107
|
+
handleClick(localY, width) {
|
|
108
|
+
let y = 0;
|
|
109
|
+
for (const child of this.children) {
|
|
110
|
+
const h = child.render(width).length;
|
|
111
|
+
if (localY >= y && localY < y + h) {
|
|
112
|
+
if (typeof child.handleClick === "function") {
|
|
113
|
+
return child.handleClick(localY - y, width);
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
y += h;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
107
121
|
}
|
|
108
122
|
/**
|
|
109
123
|
* TUI - Main class for managing terminal UI with differential rendering
|
|
@@ -118,7 +132,7 @@ export class TUI extends Container {
|
|
|
118
132
|
inputListeners = new Set();
|
|
119
133
|
/** Global callback for debug key (Shift+Ctrl+D). Called before input is forwarded to focused component. */
|
|
120
134
|
onDebug;
|
|
121
|
-
/**
|
|
135
|
+
/** Kept for callers; left-drag no longer selects. Shift+drag stays native terminal selection. */
|
|
122
136
|
onTextSelected;
|
|
123
137
|
renderRequested = false;
|
|
124
138
|
renderTimer;
|
|
@@ -155,9 +169,11 @@ export class TUI extends Container {
|
|
|
155
169
|
alternateScreenActive = false;
|
|
156
170
|
mouseTrackingActive = false;
|
|
157
171
|
static WHEEL_LINES = 3;
|
|
158
|
-
chatHitRegions = [];
|
|
159
172
|
chatScrollbar;
|
|
160
|
-
|
|
173
|
+
scrollbarDrag;
|
|
174
|
+
pendingClick;
|
|
175
|
+
lastChatStart = 0;
|
|
176
|
+
lastChatSliceHeight = 0;
|
|
161
177
|
constructor(terminal, showHardwareCursor) {
|
|
162
178
|
super();
|
|
163
179
|
this.terminal = terminal;
|
|
@@ -387,21 +403,21 @@ export class TUI extends Container {
|
|
|
387
403
|
return -1;
|
|
388
404
|
return this.children.indexOf(this.pinComponent);
|
|
389
405
|
}
|
|
390
|
-
collectChildLines(from, to, width) {
|
|
406
|
+
collectChildLines(from, to, width, ranges) {
|
|
391
407
|
const lines = [];
|
|
392
408
|
for (let i = from; i < to; i++) {
|
|
393
409
|
const child = this.children[i];
|
|
394
410
|
if (!child)
|
|
395
411
|
continue;
|
|
396
|
-
this.appendRenderedChild(lines, child, width);
|
|
412
|
+
this.appendRenderedChild(lines, child, width, ranges);
|
|
397
413
|
}
|
|
398
414
|
return lines;
|
|
399
415
|
}
|
|
400
|
-
appendRenderedChild(lines, child, width) {
|
|
416
|
+
appendRenderedChild(lines, child, width, ranges) {
|
|
401
417
|
const nested = "children" in child && Array.isArray(child.children);
|
|
402
418
|
if (nested && child.children.length > 0 && child.render === Container.prototype.render) {
|
|
403
419
|
for (const nestedChild of child.children) {
|
|
404
|
-
this.appendRenderedChild(lines, nestedChild, width);
|
|
420
|
+
this.appendRenderedChild(lines, nestedChild, width, ranges);
|
|
405
421
|
}
|
|
406
422
|
return;
|
|
407
423
|
}
|
|
@@ -409,9 +425,7 @@ export class TUI extends Container {
|
|
|
409
425
|
for (const line of child.render(width)) {
|
|
410
426
|
lines.push(line);
|
|
411
427
|
}
|
|
412
|
-
|
|
413
|
-
this.chatHitRegions.push({ y0: start, y1: lines.length - 1, lines: lines.slice(start) });
|
|
414
|
-
}
|
|
428
|
+
ranges?.push({ component: child, start, end: lines.length });
|
|
415
429
|
}
|
|
416
430
|
snapStartToKittyImageHeader(lines, start) {
|
|
417
431
|
if (start <= 0 || start >= lines.length)
|
|
@@ -434,8 +448,8 @@ export class TUI extends Container {
|
|
|
434
448
|
render(width) {
|
|
435
449
|
const pinIndex = this.getPinIndex();
|
|
436
450
|
if (pinIndex < 0) {
|
|
437
|
-
this.chatHitRegions = [];
|
|
438
451
|
this.chatScrollbar = undefined;
|
|
452
|
+
this.scrollbarDrag = undefined;
|
|
439
453
|
this.chatLayoutCache = undefined;
|
|
440
454
|
this.chatUsesGutter = false;
|
|
441
455
|
this.chatLaidOut = false;
|
|
@@ -453,31 +467,32 @@ export class TUI extends Container {
|
|
|
453
467
|
this.chatLaidOut = true;
|
|
454
468
|
let chatWidth = this.chatUsesGutter ? Math.max(1, width - 1) : width;
|
|
455
469
|
let scrollLines;
|
|
470
|
+
let ranges;
|
|
456
471
|
let fromCache = false;
|
|
457
472
|
const cache = this.chatLayoutCache;
|
|
458
473
|
if (cache && cache.epoch === this.contentEpoch && cache.chatWidth === chatWidth) {
|
|
459
474
|
scrollLines = cache.scrollLines;
|
|
460
|
-
|
|
475
|
+
ranges = cache.ranges;
|
|
461
476
|
fromCache = true;
|
|
462
477
|
}
|
|
463
478
|
else {
|
|
464
|
-
|
|
465
|
-
scrollLines = this.collectChildLines(0, pinIndex, chatWidth);
|
|
479
|
+
ranges = [];
|
|
480
|
+
scrollLines = this.collectChildLines(0, pinIndex, chatWidth, ranges);
|
|
466
481
|
}
|
|
467
482
|
if (this.chatUsesGutter) {
|
|
468
483
|
if (scrollLines.length <= viewH) {
|
|
469
484
|
this.chatUsesGutter = false;
|
|
470
485
|
chatWidth = width;
|
|
471
|
-
|
|
472
|
-
scrollLines = this.collectChildLines(0, pinIndex, chatWidth);
|
|
486
|
+
ranges = [];
|
|
487
|
+
scrollLines = this.collectChildLines(0, pinIndex, chatWidth, ranges);
|
|
473
488
|
fromCache = false;
|
|
474
489
|
}
|
|
475
490
|
}
|
|
476
491
|
else if (scrollLines.length > viewH) {
|
|
477
492
|
this.chatUsesGutter = true;
|
|
478
493
|
chatWidth = Math.max(1, width - 1);
|
|
479
|
-
|
|
480
|
-
scrollLines = this.collectChildLines(0, pinIndex, chatWidth);
|
|
494
|
+
ranges = [];
|
|
495
|
+
scrollLines = this.collectChildLines(0, pinIndex, chatWidth, ranges);
|
|
481
496
|
fromCache = false;
|
|
482
497
|
}
|
|
483
498
|
if (!fromCache) {
|
|
@@ -485,7 +500,7 @@ export class TUI extends Container {
|
|
|
485
500
|
epoch: this.contentEpoch,
|
|
486
501
|
chatWidth,
|
|
487
502
|
scrollLines,
|
|
488
|
-
|
|
503
|
+
ranges,
|
|
489
504
|
};
|
|
490
505
|
}
|
|
491
506
|
let chatSlice;
|
|
@@ -495,7 +510,9 @@ export class TUI extends Container {
|
|
|
495
510
|
this.chatScrollOffset = 0;
|
|
496
511
|
this.lastChatScrollMax = 0;
|
|
497
512
|
this.chatScrollbar = undefined;
|
|
513
|
+
this.scrollbarDrag = undefined;
|
|
498
514
|
chatSlice = scrollLines.slice();
|
|
515
|
+
this.lastChatStart = 0;
|
|
499
516
|
}
|
|
500
517
|
else {
|
|
501
518
|
this.lastChatScrollMax = scrollLines.length - viewH;
|
|
@@ -503,46 +520,37 @@ export class TUI extends Container {
|
|
|
503
520
|
start = scrollLines.length - viewH - this.chatScrollOffset;
|
|
504
521
|
start = this.snapStartToKittyImageHeader(scrollLines, start);
|
|
505
522
|
start = Math.max(0, start);
|
|
523
|
+
this.lastChatStart = start;
|
|
506
524
|
chatSlice = scrollLines.slice(start, start + viewH);
|
|
507
525
|
while (chatSlice.length < viewH) {
|
|
508
526
|
chatSlice.push("");
|
|
509
527
|
}
|
|
510
|
-
this.chatScrollbar = { x: width, y0: 1, y1: viewH, viewH, scrollMax: this.lastChatScrollMax };
|
|
511
528
|
const thumbH = Math.max(1, Math.round((viewH * viewH) / scrollLines.length));
|
|
512
529
|
const travel = Math.max(0, viewH - thumbH);
|
|
513
530
|
const thumbTop = this.lastChatScrollMax === 0
|
|
514
531
|
? viewH - thumbH
|
|
515
532
|
: Math.round(travel * (1 - this.chatScrollOffset / this.lastChatScrollMax));
|
|
516
|
-
|
|
517
|
-
|
|
533
|
+
this.chatScrollbar = {
|
|
534
|
+
x: width,
|
|
535
|
+
y0: 1,
|
|
536
|
+
y1: viewH,
|
|
537
|
+
viewH,
|
|
538
|
+
scrollMax: this.lastChatScrollMax,
|
|
539
|
+
thumbTop,
|
|
540
|
+
thumbH,
|
|
541
|
+
};
|
|
542
|
+
// Reset SGR so error/thinking colors cannot bleed into the gutter.
|
|
543
|
+
const RESET = "\x1b[0m";
|
|
544
|
+
const track = `${RESET}\x1b[2m│\x1b[0m`;
|
|
545
|
+
const thumb = `${RESET}\x1b[1m█\x1b[0m`;
|
|
518
546
|
for (let i = 0; i < chatSlice.length; i++) {
|
|
519
547
|
const glyph = i >= thumbTop && i < thumbTop + thumbH ? thumb : track;
|
|
520
548
|
chatSlice[i] = `${padToWidth(chatSlice[i] ?? "", chatWidth)}${glyph}`;
|
|
521
549
|
}
|
|
522
550
|
}
|
|
523
|
-
this.
|
|
524
|
-
if (this.selection) {
|
|
525
|
-
const lo = Math.min(this.selection.anchorY, this.selection.focusY);
|
|
526
|
-
const hi = Math.max(this.selection.anchorY, this.selection.focusY);
|
|
527
|
-
const highlightWidth = this.chatUsesGutter ? chatWidth : width;
|
|
528
|
-
for (let y = lo; y <= hi; y++) {
|
|
529
|
-
const idx = y - 1;
|
|
530
|
-
if (idx < 0 || idx >= chatSlice.length)
|
|
531
|
-
continue;
|
|
532
|
-
chatSlice[idx] = applySelectionHighlight(chatSlice[idx] ?? "", highlightWidth);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
551
|
+
this.lastChatSliceHeight = chatSlice.length;
|
|
535
552
|
return [...chatSlice, ...dockLines];
|
|
536
553
|
}
|
|
537
|
-
copyHitRegions(regions) {
|
|
538
|
-
return regions.map((region) => ({ y0: region.y0, y1: region.y1, lines: region.lines }));
|
|
539
|
-
}
|
|
540
|
-
shiftHitRegions(delta) {
|
|
541
|
-
for (const region of this.chatHitRegions) {
|
|
542
|
-
region.y0 += delta;
|
|
543
|
-
region.y1 += delta;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
554
|
/**
|
|
547
555
|
* Show an overlay component with configurable positioning and sizing.
|
|
548
556
|
* Returns a handle to control the overlay's visibility.
|
|
@@ -860,61 +868,98 @@ export class TUI extends Container {
|
|
|
860
868
|
// Shift+drag stays with the terminal's native selection.
|
|
861
869
|
if (mouse.shift)
|
|
862
870
|
return;
|
|
863
|
-
if (
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
871
|
+
if (this.scrollbarDrag) {
|
|
872
|
+
if (mouse.kind === "move") {
|
|
873
|
+
this.dragScrollbarTo(mouse.y);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
if (mouse.kind === "button" && mouse.release) {
|
|
877
|
+
this.scrollbarDrag = undefined;
|
|
878
|
+
}
|
|
869
879
|
return;
|
|
870
880
|
}
|
|
871
|
-
if (mouse.kind === "button" && !mouse.release) {
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
881
|
+
if (mouse.kind === "button" && !mouse.release && this.chatScrollbar && mouse.x >= this.chatScrollbar.x) {
|
|
882
|
+
const sb = this.chatScrollbar;
|
|
883
|
+
const y = Math.max(sb.y0, Math.min(mouse.y, sb.y1));
|
|
884
|
+
const row = y - sb.y0;
|
|
885
|
+
const onThumb = row >= sb.thumbTop && row < sb.thumbTop + sb.thumbH;
|
|
886
|
+
if (!onThumb) {
|
|
887
|
+
const rel = (y - sb.y0) / Math.max(1, sb.viewH - 1);
|
|
888
|
+
this.setChatScroll(Math.round((1 - rel) * sb.scrollMax));
|
|
876
889
|
}
|
|
877
|
-
this.
|
|
878
|
-
this.
|
|
890
|
+
this.scrollbarDrag = { grabY: y, startOffset: this.chatScrollOffset };
|
|
891
|
+
this.pendingClick = undefined;
|
|
879
892
|
return;
|
|
880
893
|
}
|
|
881
|
-
if (!
|
|
894
|
+
if (mouse.kind === "button" && mouse.button === 0 && !mouse.release) {
|
|
895
|
+
this.pendingClick = { x: mouse.x, y: mouse.y };
|
|
882
896
|
return;
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
this.
|
|
897
|
+
}
|
|
898
|
+
if (mouse.kind === "move" && this.pendingClick) {
|
|
899
|
+
const dx = Math.abs(mouse.x - this.pendingClick.x);
|
|
900
|
+
const dy = Math.abs(mouse.y - this.pendingClick.y);
|
|
901
|
+
if (dx + dy > 1) {
|
|
902
|
+
this.pendingClick = undefined;
|
|
903
|
+
}
|
|
886
904
|
return;
|
|
887
905
|
}
|
|
888
|
-
if (mouse.kind === "button" && mouse.release) {
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
if (
|
|
894
|
-
this.
|
|
906
|
+
if (mouse.kind === "button" && mouse.button === 0 && mouse.release) {
|
|
907
|
+
const pending = this.pendingClick;
|
|
908
|
+
this.pendingClick = undefined;
|
|
909
|
+
if (!pending)
|
|
910
|
+
return;
|
|
911
|
+
if (this.dispatchChatClick(pending.y) || this.dispatchDockClick(pending.y)) {
|
|
912
|
+
this.requestRender();
|
|
913
|
+
}
|
|
895
914
|
}
|
|
896
915
|
}
|
|
897
|
-
|
|
898
|
-
|
|
916
|
+
dispatchChatClick(mouseY) {
|
|
917
|
+
const cache = this.chatLayoutCache;
|
|
918
|
+
if (!cache || mouseY < 1 || mouseY > this.lastChatSliceHeight)
|
|
919
|
+
return false;
|
|
920
|
+
const lineIndex = this.lastChatStart + (mouseY - 1);
|
|
921
|
+
const hit = cache.ranges.find((range) => lineIndex >= range.start && lineIndex < range.end);
|
|
922
|
+
if (!hit || typeof hit.component.handleClick !== "function")
|
|
923
|
+
return false;
|
|
924
|
+
return hit.component.handleClick(lineIndex - hit.start, cache.chatWidth) === true;
|
|
899
925
|
}
|
|
900
|
-
|
|
901
|
-
if (
|
|
902
|
-
return
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
926
|
+
dispatchDockClick(mouseY) {
|
|
927
|
+
if (mouseY <= this.lastChatSliceHeight)
|
|
928
|
+
return false;
|
|
929
|
+
const pin = this.getPinIndex();
|
|
930
|
+
if (pin < 0)
|
|
931
|
+
return false;
|
|
932
|
+
const width = this.terminal.columns;
|
|
933
|
+
let y = this.lastChatSliceHeight;
|
|
934
|
+
const local = mouseY - 1;
|
|
935
|
+
for (let i = pin; i < this.children.length; i++) {
|
|
936
|
+
const child = this.children[i];
|
|
937
|
+
if (!child)
|
|
938
|
+
continue;
|
|
939
|
+
const h = child.render(width).length;
|
|
940
|
+
if (local >= y && local < y + h) {
|
|
941
|
+
if (typeof child.handleClick === "function") {
|
|
942
|
+
return child.handleClick(local - y, width);
|
|
943
|
+
}
|
|
944
|
+
if (child instanceof Container) {
|
|
945
|
+
return child.handleClick(local - y, width);
|
|
946
|
+
}
|
|
947
|
+
return false;
|
|
914
948
|
}
|
|
949
|
+
y += h;
|
|
915
950
|
}
|
|
916
|
-
|
|
917
|
-
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
dragScrollbarTo(mouseY) {
|
|
954
|
+
if (!this.scrollbarDrag || !this.chatScrollbar)
|
|
955
|
+
return;
|
|
956
|
+
const { viewH, thumbH, scrollMax, y0, y1 } = this.chatScrollbar;
|
|
957
|
+
const travel = viewH - thumbH;
|
|
958
|
+
if (travel <= 0 || scrollMax <= 0)
|
|
959
|
+
return;
|
|
960
|
+
const y = Math.max(y0, Math.min(mouseY, y1));
|
|
961
|
+
const deltaY = y - this.scrollbarDrag.grabY;
|
|
962
|
+
this.setChatScroll(this.scrollbarDrag.startOffset - Math.round((deltaY * scrollMax) / travel));
|
|
918
963
|
}
|
|
919
964
|
handleInput(data) {
|
|
920
965
|
if (this.consumeOsc11BackgroundResponse(data)) {
|
|
@@ -1791,22 +1836,4 @@ function padToWidth(line, width) {
|
|
|
1791
1836
|
return line;
|
|
1792
1837
|
return line + " ".repeat(width - vis);
|
|
1793
1838
|
}
|
|
1794
|
-
function applySelectionHighlight(line, width) {
|
|
1795
|
-
const padded = padToWidth(line, width);
|
|
1796
|
-
return `\x1b[7m${padded}\x1b[27m`;
|
|
1797
|
-
}
|
|
1798
|
-
function stripAnsiForCopy(line) {
|
|
1799
|
-
let out = "";
|
|
1800
|
-
let i = 0;
|
|
1801
|
-
while (i < line.length) {
|
|
1802
|
-
const ansi = extractAnsiCode(line, i);
|
|
1803
|
-
if (ansi) {
|
|
1804
|
-
i += ansi.length;
|
|
1805
|
-
continue;
|
|
1806
|
-
}
|
|
1807
|
-
out += line[i];
|
|
1808
|
-
i++;
|
|
1809
|
-
}
|
|
1810
|
-
return out.replace(/\s+$/, "");
|
|
1811
|
-
}
|
|
1812
1839
|
//# sourceMappingURL=tui.js.map
|