@ashx-j/lunr-tui 0.2.7 → 0.2.8

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.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 { extractAnsiCode, extractSegments, normalizeTerminalOutput, sliceByColumn, sliceWithWidth, visibleWidth, } from "./utils.js";
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);
@@ -118,7 +118,7 @@ export class TUI extends Container {
118
118
  inputListeners = new Set();
119
119
  /** Global callback for debug key (Shift+Ctrl+D). Called before input is forwarded to focused component. */
120
120
  onDebug;
121
- /** Called with selected user/assistant text after an in-app drag-release. */
121
+ /** Kept for callers; left-drag no longer selects. Shift+drag stays native terminal selection. */
122
122
  onTextSelected;
123
123
  renderRequested = false;
124
124
  renderTimer;
@@ -155,9 +155,8 @@ export class TUI extends Container {
155
155
  alternateScreenActive = false;
156
156
  mouseTrackingActive = false;
157
157
  static WHEEL_LINES = 3;
158
- chatHitRegions = [];
159
158
  chatScrollbar;
160
- selection;
159
+ scrollbarDrag;
161
160
  constructor(terminal, showHardwareCursor) {
162
161
  super();
163
162
  this.terminal = terminal;
@@ -405,13 +404,9 @@ export class TUI extends Container {
405
404
  }
406
405
  return;
407
406
  }
408
- const start = lines.length;
409
407
  for (const line of child.render(width)) {
410
408
  lines.push(line);
411
409
  }
412
- if (child.selectable && lines.length > start) {
413
- this.chatHitRegions.push({ y0: start, y1: lines.length - 1, lines: lines.slice(start) });
414
- }
415
410
  }
416
411
  snapStartToKittyImageHeader(lines, start) {
417
412
  if (start <= 0 || start >= lines.length)
@@ -434,8 +429,8 @@ export class TUI extends Container {
434
429
  render(width) {
435
430
  const pinIndex = this.getPinIndex();
436
431
  if (pinIndex < 0) {
437
- this.chatHitRegions = [];
438
432
  this.chatScrollbar = undefined;
433
+ this.scrollbarDrag = undefined;
439
434
  this.chatLayoutCache = undefined;
440
435
  this.chatUsesGutter = false;
441
436
  this.chatLaidOut = false;
@@ -457,18 +452,15 @@ export class TUI extends Container {
457
452
  const cache = this.chatLayoutCache;
458
453
  if (cache && cache.epoch === this.contentEpoch && cache.chatWidth === chatWidth) {
459
454
  scrollLines = cache.scrollLines;
460
- this.chatHitRegions = this.copyHitRegions(cache.hitRegionsUnshifted);
461
455
  fromCache = true;
462
456
  }
463
457
  else {
464
- this.chatHitRegions = [];
465
458
  scrollLines = this.collectChildLines(0, pinIndex, chatWidth);
466
459
  }
467
460
  if (this.chatUsesGutter) {
468
461
  if (scrollLines.length <= viewH) {
469
462
  this.chatUsesGutter = false;
470
463
  chatWidth = width;
471
- this.chatHitRegions = [];
472
464
  scrollLines = this.collectChildLines(0, pinIndex, chatWidth);
473
465
  fromCache = false;
474
466
  }
@@ -476,7 +468,6 @@ export class TUI extends Container {
476
468
  else if (scrollLines.length > viewH) {
477
469
  this.chatUsesGutter = true;
478
470
  chatWidth = Math.max(1, width - 1);
479
- this.chatHitRegions = [];
480
471
  scrollLines = this.collectChildLines(0, pinIndex, chatWidth);
481
472
  fromCache = false;
482
473
  }
@@ -485,7 +476,6 @@ export class TUI extends Container {
485
476
  epoch: this.contentEpoch,
486
477
  chatWidth,
487
478
  scrollLines,
488
- hitRegionsUnshifted: this.copyHitRegions(this.chatHitRegions),
489
479
  };
490
480
  }
491
481
  let chatSlice;
@@ -495,6 +485,7 @@ export class TUI extends Container {
495
485
  this.chatScrollOffset = 0;
496
486
  this.lastChatScrollMax = 0;
497
487
  this.chatScrollbar = undefined;
488
+ this.scrollbarDrag = undefined;
498
489
  chatSlice = scrollLines.slice();
499
490
  }
500
491
  else {
@@ -507,42 +498,31 @@ export class TUI extends Container {
507
498
  while (chatSlice.length < viewH) {
508
499
  chatSlice.push("");
509
500
  }
510
- this.chatScrollbar = { x: width, y0: 1, y1: viewH, viewH, scrollMax: this.lastChatScrollMax };
511
501
  const thumbH = Math.max(1, Math.round((viewH * viewH) / scrollLines.length));
512
502
  const travel = Math.max(0, viewH - thumbH);
513
503
  const thumbTop = this.lastChatScrollMax === 0
514
504
  ? viewH - thumbH
515
505
  : Math.round(travel * (1 - this.chatScrollOffset / this.lastChatScrollMax));
516
- const track = "\x1b[2m│\x1b[22m";
517
- const thumb = "\x1b[1m█\x1b[22m";
506
+ this.chatScrollbar = {
507
+ x: width,
508
+ y0: 1,
509
+ y1: viewH,
510
+ viewH,
511
+ scrollMax: this.lastChatScrollMax,
512
+ thumbTop,
513
+ thumbH,
514
+ };
515
+ // Reset SGR so error/thinking colors cannot bleed into the gutter.
516
+ const RESET = "\x1b[0m";
517
+ const track = `${RESET}\x1b[2m│\x1b[0m`;
518
+ const thumb = `${RESET}\x1b[1m█\x1b[0m`;
518
519
  for (let i = 0; i < chatSlice.length; i++) {
519
520
  const glyph = i >= thumbTop && i < thumbTop + thumbH ? thumb : track;
520
521
  chatSlice[i] = `${padToWidth(chatSlice[i] ?? "", chatWidth)}${glyph}`;
521
522
  }
522
523
  }
523
- this.shiftHitRegions(-start);
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
- }
535
524
  return [...chatSlice, ...dockLines];
536
525
  }
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
526
  /**
547
527
  * Show an overlay component with configurable positioning and sizing.
548
528
  * Returns a handle to control the overlay's visibility.
@@ -860,61 +840,39 @@ export class TUI extends Container {
860
840
  // Shift+drag stays with the terminal's native selection.
861
841
  if (mouse.shift)
862
842
  return;
863
- if (mouse.kind === "button" && !mouse.release && this.chatScrollbar && mouse.x === this.chatScrollbar.x) {
864
- const viewH = this.chatScrollbar.viewH;
865
- const y = Math.max(this.chatScrollbar.y0, Math.min(mouse.y, this.chatScrollbar.y1));
866
- const rel = (y - this.chatScrollbar.y0) / Math.max(1, viewH - 1);
867
- this.setChatScroll(Math.round((1 - rel) * this.chatScrollbar.scrollMax));
868
- this.selection = undefined;
869
- return;
870
- }
871
- if (mouse.kind === "button" && !mouse.release) {
872
- const region = this.hitSelectable(mouse.y);
873
- if (!region) {
874
- this.selection = undefined;
843
+ if (this.scrollbarDrag) {
844
+ if (mouse.kind === "move") {
845
+ this.dragScrollbarTo(mouse.y);
875
846
  return;
876
847
  }
877
- this.selection = { anchorY: mouse.y, focusY: mouse.y };
878
- this.requestRender();
848
+ if (mouse.kind === "button" && mouse.release) {
849
+ this.scrollbarDrag = undefined;
850
+ }
879
851
  return;
880
852
  }
881
- if (!this.selection)
882
- return;
883
- if (mouse.kind === "move") {
884
- this.selection.focusY = mouse.y;
885
- this.requestRender();
853
+ if (mouse.kind === "button" && !mouse.release && this.chatScrollbar && mouse.x >= this.chatScrollbar.x) {
854
+ const sb = this.chatScrollbar;
855
+ const y = Math.max(sb.y0, Math.min(mouse.y, sb.y1));
856
+ const row = y - sb.y0;
857
+ const onThumb = row >= sb.thumbTop && row < sb.thumbTop + sb.thumbH;
858
+ if (!onThumb) {
859
+ const rel = (y - sb.y0) / Math.max(1, sb.viewH - 1);
860
+ this.setChatScroll(Math.round((1 - rel) * sb.scrollMax));
861
+ }
862
+ this.scrollbarDrag = { grabY: y, startOffset: this.chatScrollOffset };
886
863
  return;
887
864
  }
888
- if (mouse.kind === "button" && mouse.release) {
889
- this.selection.focusY = mouse.y;
890
- const text = this.selectedText();
891
- this.selection = undefined;
892
- this.requestRender();
893
- if (text)
894
- this.onTextSelected?.(text);
895
- }
896
865
  }
897
- hitSelectable(y) {
898
- return this.chatHitRegions.find((region) => y >= region.y0 + 1 && y <= region.y1 + 1);
899
- }
900
- selectedText() {
901
- if (!this.selection)
902
- return undefined;
903
- const lo = Math.min(this.selection.anchorY, this.selection.focusY);
904
- const hi = Math.max(this.selection.anchorY, this.selection.focusY);
905
- if (lo === hi)
906
- return undefined;
907
- const lines = [];
908
- for (const region of this.chatHitRegions) {
909
- for (let i = 0; i < region.lines.length; i++) {
910
- const y = region.y0 + i + 1;
911
- if (y < lo || y > hi)
912
- continue;
913
- lines.push(stripAnsiForCopy(region.lines[i] ?? ""));
914
- }
915
- }
916
- const text = lines.join("\n").trim();
917
- return text.length > 0 ? text : undefined;
866
+ dragScrollbarTo(mouseY) {
867
+ if (!this.scrollbarDrag || !this.chatScrollbar)
868
+ return;
869
+ const { viewH, thumbH, scrollMax, y0, y1 } = this.chatScrollbar;
870
+ const travel = viewH - thumbH;
871
+ if (travel <= 0 || scrollMax <= 0)
872
+ return;
873
+ const y = Math.max(y0, Math.min(mouseY, y1));
874
+ const deltaY = y - this.scrollbarDrag.grabY;
875
+ this.setChatScroll(this.scrollbarDrag.startOffset - Math.round((deltaY * scrollMax) / travel));
918
876
  }
919
877
  handleInput(data) {
920
878
  if (this.consumeOsc11BackgroundResponse(data)) {
@@ -1791,22 +1749,4 @@ function padToWidth(line, width) {
1791
1749
  return line;
1792
1750
  return line + " ".repeat(width - vis);
1793
1751
  }
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
1752
  //# sourceMappingURL=tui.js.map