@ashx-j/lunr-tui 0.2.0 → 0.2.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/dist/tui.js CHANGED
@@ -146,6 +146,11 @@ export class TUI extends Container {
146
146
  chatScrollOffset = 0;
147
147
  lastChatViewportHeight = 1;
148
148
  lastChatScrollMax = 0;
149
+ chatLaidOut = false;
150
+ chatUsesGutter = false;
151
+ /** Bumped on non-scroll `requestRender` so offset-only paints can reuse chat layout. */
152
+ contentEpoch = 0;
153
+ chatLayoutCache;
149
154
  alternateScreen = false;
150
155
  alternateScreenActive = false;
151
156
  mouseTrackingActive = false;
@@ -192,6 +197,9 @@ export class TUI extends Container {
192
197
  */
193
198
  pinFrom(component) {
194
199
  this.pinComponent = component;
200
+ this.chatLayoutCache = undefined;
201
+ this.chatUsesGutter = false;
202
+ this.chatLaidOut = false;
195
203
  if (!component) {
196
204
  this.chatScrollOffset = 0;
197
205
  this.lastChatViewportHeight = Math.max(1, this.terminal.rows);
@@ -207,16 +215,20 @@ export class TUI extends Container {
207
215
  /** `0` follows the latest chat. Larger values scroll up into history. */
208
216
  setChatScroll(offset) {
209
217
  const next = Number.isFinite(offset) ? Math.max(0, Math.floor(offset)) : 0;
210
- this.chatScrollOffset = next;
211
- if (this.getPinIndex() >= 0) {
212
- this.render(this.terminal.columns);
213
- }
214
- else {
218
+ if (this.getPinIndex() < 0) {
219
+ const changed = this.chatScrollOffset !== 0 || this.lastChatScrollMax !== 0;
215
220
  this.chatScrollOffset = 0;
216
221
  this.lastChatScrollMax = 0;
222
+ if (changed && this.started)
223
+ this.requestRender();
224
+ return;
217
225
  }
226
+ const clamped = this.chatLaidOut ? Math.min(next, this.lastChatScrollMax) : next;
227
+ if (clamped === this.chatScrollOffset)
228
+ return;
229
+ this.chatScrollOffset = clamped;
218
230
  if (this.started)
219
- this.requestRender();
231
+ this.requestRenderFromScroll();
220
232
  }
221
233
  /** Positive delta scrolls up (older). Negative scrolls toward the latest. */
222
234
  scrollChat(deltaLines) {
@@ -424,6 +436,9 @@ export class TUI extends Container {
424
436
  if (pinIndex < 0) {
425
437
  this.chatHitRegions = [];
426
438
  this.chatScrollbar = undefined;
439
+ this.chatLayoutCache = undefined;
440
+ this.chatUsesGutter = false;
441
+ this.chatLaidOut = false;
427
442
  return super.render(width);
428
443
  }
429
444
  const rows = Math.max(1, this.terminal.rows);
@@ -435,15 +450,44 @@ export class TUI extends Container {
435
450
  }
436
451
  const viewH = Math.max(minView, rows - dockLines.length);
437
452
  this.lastChatViewportHeight = viewH;
438
- this.chatHitRegions = [];
439
- const chatWidthProbe = this.collectChildLines(0, pinIndex, width);
440
- const showScrollbar = chatWidthProbe.length > viewH;
441
- const chatWidth = showScrollbar ? Math.max(1, width - 1) : width;
442
- let scrollLines = chatWidthProbe;
443
- if (showScrollbar) {
453
+ this.chatLaidOut = true;
454
+ let chatWidth = this.chatUsesGutter ? Math.max(1, width - 1) : width;
455
+ let scrollLines;
456
+ let fromCache = false;
457
+ const cache = this.chatLayoutCache;
458
+ if (cache && cache.epoch === this.contentEpoch && cache.chatWidth === chatWidth) {
459
+ scrollLines = cache.scrollLines;
460
+ this.chatHitRegions = this.copyHitRegions(cache.hitRegionsUnshifted);
461
+ fromCache = true;
462
+ }
463
+ else {
444
464
  this.chatHitRegions = [];
445
465
  scrollLines = this.collectChildLines(0, pinIndex, chatWidth);
446
466
  }
467
+ if (this.chatUsesGutter) {
468
+ if (scrollLines.length <= viewH) {
469
+ this.chatUsesGutter = false;
470
+ chatWidth = width;
471
+ this.chatHitRegions = [];
472
+ scrollLines = this.collectChildLines(0, pinIndex, chatWidth);
473
+ fromCache = false;
474
+ }
475
+ }
476
+ else if (scrollLines.length > viewH) {
477
+ this.chatUsesGutter = true;
478
+ chatWidth = Math.max(1, width - 1);
479
+ this.chatHitRegions = [];
480
+ scrollLines = this.collectChildLines(0, pinIndex, chatWidth);
481
+ fromCache = false;
482
+ }
483
+ if (!fromCache) {
484
+ this.chatLayoutCache = {
485
+ epoch: this.contentEpoch,
486
+ chatWidth,
487
+ scrollLines,
488
+ hitRegionsUnshifted: this.copyHitRegions(this.chatHitRegions),
489
+ };
490
+ }
447
491
  let chatSlice;
448
492
  let start = 0;
449
493
  if (scrollLines.length <= viewH) {
@@ -451,7 +495,7 @@ export class TUI extends Container {
451
495
  this.chatScrollOffset = 0;
452
496
  this.lastChatScrollMax = 0;
453
497
  this.chatScrollbar = undefined;
454
- chatSlice = scrollLines;
498
+ chatSlice = scrollLines.slice();
455
499
  }
456
500
  else {
457
501
  this.lastChatScrollMax = scrollLines.length - viewH;
@@ -480,15 +524,19 @@ export class TUI extends Container {
480
524
  if (this.selection) {
481
525
  const lo = Math.min(this.selection.anchorY, this.selection.focusY);
482
526
  const hi = Math.max(this.selection.anchorY, this.selection.focusY);
527
+ const highlightWidth = this.chatUsesGutter ? chatWidth : width;
483
528
  for (let y = lo; y <= hi; y++) {
484
529
  const idx = y - 1;
485
530
  if (idx < 0 || idx >= chatSlice.length)
486
531
  continue;
487
- chatSlice[idx] = applySelectionHighlight(chatSlice[idx] ?? "", showScrollbar ? chatWidth : width);
532
+ chatSlice[idx] = applySelectionHighlight(chatSlice[idx] ?? "", highlightWidth);
488
533
  }
489
534
  }
490
535
  return [...chatSlice, ...dockLines];
491
536
  }
537
+ copyHitRegions(regions) {
538
+ return regions.map((region) => ({ y0: region.y0, y1: region.y1, lines: region.lines }));
539
+ }
492
540
  shiftHitRegions(delta) {
493
541
  for (const region of this.chatHitRegions) {
494
542
  region.y0 += delta;
@@ -723,6 +771,13 @@ export class TUI extends Container {
723
771
  this.terminal.stop();
724
772
  }
725
773
  requestRender(force = false) {
774
+ this.contentEpoch++;
775
+ this.queueRender(force);
776
+ }
777
+ requestRenderFromScroll() {
778
+ this.queueRender(false);
779
+ }
780
+ queueRender(force) {
726
781
  if (force) {
727
782
  this.previousLines = [];
728
783
  this.previousWidth = -1; // -1 triggers widthChanged, forcing a full clear