@austinthesing/magic-shell 0.1.1 → 0.1.3

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.
Files changed (4) hide show
  1. package/LICENSE +1 -1
  2. package/dist/cli.js +1582 -186
  3. package/dist/index.js +1580 -180
  4. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -831,6 +831,13 @@ function t(strings, ...values) {
831
831
  }
832
832
  return new StyledText(chunks);
833
833
  }
834
+
835
+ class LinearScrollAccel {
836
+ tick(_now) {
837
+ return 1;
838
+ }
839
+ reset() {}
840
+ }
834
841
  function isCompleteSequence(data) {
835
842
  if (!data.startsWith(ESC)) {
836
843
  return "not-escape";
@@ -16650,7 +16657,7 @@ function canonicalize(obj, stack, replacementStack, replacer, key) {
16650
16657
  }
16651
16658
  return canonicalizedObj;
16652
16659
  }
16653
- var EditBuffer, engine, BoxRenderable, TextBufferRenderable, BrandedTextNodeRenderable, TextNodeRenderable, RootTextNodeRenderable, CharacterDiff, characterDiff, extendedWordChars = "a-zA-Z0-9_\\u{C0}-\\u{FF}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}", tokenizeIncludingWhitespace, WordDiff, wordDiff, WordsWithSpaceDiff, wordsWithSpaceDiff, LineDiff, lineDiff, SentenceDiff, sentenceDiff, CssDiff, cssDiff, JsonDiff, jsonDiff, ArrayDiff, arrayDiff, TextRenderable, defaultInputKeybindings, InputRenderableEvents, InputRenderable, defaultThumbBackgroundColor, defaultTrackBackgroundColor, defaultSelectKeybindings, SelectRenderableEvents, SelectRenderable, TabSelectRenderableEvents, EditBufferRenderable;
16660
+ var EditBuffer, engine, BoxRenderable, TextBufferRenderable, BrandedTextNodeRenderable, TextNodeRenderable, RootTextNodeRenderable, CharacterDiff, characterDiff, extendedWordChars = "a-zA-Z0-9_\\u{C0}-\\u{FF}\\u{D8}-\\u{F6}\\u{F8}-\\u{2C6}\\u{2C8}-\\u{2D7}\\u{2DE}-\\u{2FF}\\u{1E00}-\\u{1EFF}", tokenizeIncludingWhitespace, WordDiff, wordDiff, WordsWithSpaceDiff, wordsWithSpaceDiff, LineDiff, lineDiff, SentenceDiff, sentenceDiff, CssDiff, cssDiff, JsonDiff, jsonDiff, ArrayDiff, arrayDiff, TextRenderable, defaultInputKeybindings, InputRenderableEvents, InputRenderable, defaultThumbBackgroundColor, defaultTrackBackgroundColor, SliderRenderable, ScrollBarRenderable, ArrowRenderable, ContentRenderable, ScrollBoxRenderable, defaultSelectKeybindings, SelectRenderableEvents, SelectRenderable, TabSelectRenderableEvents, EditBufferRenderable;
16654
16661
  var init_core = __esm(async () => {
16655
16662
  await init_index_93qf6w1k();
16656
16663
  EditBuffer = class EditBuffer extends EventEmitter10 {
@@ -18369,6 +18376,1146 @@ var init_core = __esm(async () => {
18369
18376
  };
18370
18377
  defaultThumbBackgroundColor = RGBA.fromHex("#9a9ea3");
18371
18378
  defaultTrackBackgroundColor = RGBA.fromHex("#252527");
18379
+ SliderRenderable = class SliderRenderable extends Renderable {
18380
+ orientation;
18381
+ _value;
18382
+ _min;
18383
+ _max;
18384
+ _viewPortSize;
18385
+ _backgroundColor;
18386
+ _foregroundColor;
18387
+ _onChange;
18388
+ constructor(ctx, options) {
18389
+ super(ctx, { flexShrink: 0, ...options });
18390
+ this.orientation = options.orientation;
18391
+ this._min = options.min ?? 0;
18392
+ this._max = options.max ?? 100;
18393
+ this._value = options.value ?? this._min;
18394
+ this._viewPortSize = options.viewPortSize ?? Math.max(1, (this._max - this._min) * 0.1);
18395
+ this._onChange = options.onChange;
18396
+ this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : defaultTrackBackgroundColor;
18397
+ this._foregroundColor = options.foregroundColor ? parseColor(options.foregroundColor) : defaultThumbBackgroundColor;
18398
+ this.setupMouseHandling();
18399
+ }
18400
+ get value() {
18401
+ return this._value;
18402
+ }
18403
+ set value(newValue) {
18404
+ const clamped = Math.max(this._min, Math.min(this._max, newValue));
18405
+ if (clamped !== this._value) {
18406
+ this._value = clamped;
18407
+ this._onChange?.(clamped);
18408
+ this.emit("change", { value: clamped });
18409
+ this.requestRender();
18410
+ }
18411
+ }
18412
+ get min() {
18413
+ return this._min;
18414
+ }
18415
+ set min(newMin) {
18416
+ if (newMin !== this._min) {
18417
+ this._min = newMin;
18418
+ if (this._value < newMin) {
18419
+ this.value = newMin;
18420
+ }
18421
+ this.requestRender();
18422
+ }
18423
+ }
18424
+ get max() {
18425
+ return this._max;
18426
+ }
18427
+ set max(newMax) {
18428
+ if (newMax !== this._max) {
18429
+ this._max = newMax;
18430
+ if (this._value > newMax) {
18431
+ this.value = newMax;
18432
+ }
18433
+ this.requestRender();
18434
+ }
18435
+ }
18436
+ set viewPortSize(size) {
18437
+ const clampedSize = Math.max(0.01, Math.min(size, this._max - this._min));
18438
+ if (clampedSize !== this._viewPortSize) {
18439
+ this._viewPortSize = clampedSize;
18440
+ this.requestRender();
18441
+ }
18442
+ }
18443
+ get viewPortSize() {
18444
+ return this._viewPortSize;
18445
+ }
18446
+ get backgroundColor() {
18447
+ return this._backgroundColor;
18448
+ }
18449
+ set backgroundColor(value) {
18450
+ this._backgroundColor = parseColor(value);
18451
+ this.requestRender();
18452
+ }
18453
+ get foregroundColor() {
18454
+ return this._foregroundColor;
18455
+ }
18456
+ set foregroundColor(value) {
18457
+ this._foregroundColor = parseColor(value);
18458
+ this.requestRender();
18459
+ }
18460
+ calculateDragOffsetVirtual(event) {
18461
+ const trackStart = this.orientation === "vertical" ? this.y : this.x;
18462
+ const mousePos = (this.orientation === "vertical" ? event.y : event.x) - trackStart;
18463
+ const virtualMousePos = Math.max(0, Math.min((this.orientation === "vertical" ? this.height : this.width) * 2, mousePos * 2));
18464
+ const virtualThumbStart = this.getVirtualThumbStart();
18465
+ const virtualThumbSize = this.getVirtualThumbSize();
18466
+ return Math.max(0, Math.min(virtualThumbSize, virtualMousePos - virtualThumbStart));
18467
+ }
18468
+ setupMouseHandling() {
18469
+ let isDragging = false;
18470
+ let dragOffsetVirtual = 0;
18471
+ this.onMouseDown = (event) => {
18472
+ event.stopPropagation();
18473
+ event.preventDefault();
18474
+ const thumb = this.getThumbRect();
18475
+ const inThumb = event.x >= thumb.x && event.x < thumb.x + thumb.width && event.y >= thumb.y && event.y < thumb.y + thumb.height;
18476
+ if (inThumb) {
18477
+ isDragging = true;
18478
+ dragOffsetVirtual = this.calculateDragOffsetVirtual(event);
18479
+ } else {
18480
+ this.updateValueFromMouseDirect(event);
18481
+ isDragging = true;
18482
+ dragOffsetVirtual = this.calculateDragOffsetVirtual(event);
18483
+ }
18484
+ };
18485
+ this.onMouseDrag = (event) => {
18486
+ if (!isDragging)
18487
+ return;
18488
+ event.stopPropagation();
18489
+ this.updateValueFromMouseWithOffset(event, dragOffsetVirtual);
18490
+ };
18491
+ this.onMouseUp = (event) => {
18492
+ if (isDragging) {
18493
+ this.updateValueFromMouseWithOffset(event, dragOffsetVirtual);
18494
+ }
18495
+ isDragging = false;
18496
+ };
18497
+ }
18498
+ updateValueFromMouseDirect(event) {
18499
+ const trackStart = this.orientation === "vertical" ? this.y : this.x;
18500
+ const trackSize = this.orientation === "vertical" ? this.height : this.width;
18501
+ const mousePos = this.orientation === "vertical" ? event.y : event.x;
18502
+ const relativeMousePos = mousePos - trackStart;
18503
+ const clampedMousePos = Math.max(0, Math.min(trackSize, relativeMousePos));
18504
+ const ratio = trackSize === 0 ? 0 : clampedMousePos / trackSize;
18505
+ const range = this._max - this._min;
18506
+ const newValue = this._min + ratio * range;
18507
+ this.value = newValue;
18508
+ }
18509
+ updateValueFromMouseWithOffset(event, offsetVirtual) {
18510
+ const trackStart = this.orientation === "vertical" ? this.y : this.x;
18511
+ const trackSize = this.orientation === "vertical" ? this.height : this.width;
18512
+ const mousePos = this.orientation === "vertical" ? event.y : event.x;
18513
+ const virtualTrackSize = trackSize * 2;
18514
+ const relativeMousePos = mousePos - trackStart;
18515
+ const clampedMousePos = Math.max(0, Math.min(trackSize, relativeMousePos));
18516
+ const virtualMousePos = clampedMousePos * 2;
18517
+ const virtualThumbSize = this.getVirtualThumbSize();
18518
+ const maxThumbStart = Math.max(0, virtualTrackSize - virtualThumbSize);
18519
+ let desiredThumbStart = virtualMousePos - offsetVirtual;
18520
+ desiredThumbStart = Math.max(0, Math.min(maxThumbStart, desiredThumbStart));
18521
+ const ratio = maxThumbStart === 0 ? 0 : desiredThumbStart / maxThumbStart;
18522
+ const range = this._max - this._min;
18523
+ const newValue = this._min + ratio * range;
18524
+ this.value = newValue;
18525
+ }
18526
+ getThumbRect() {
18527
+ const virtualThumbSize = this.getVirtualThumbSize();
18528
+ const virtualThumbStart = this.getVirtualThumbStart();
18529
+ const realThumbStart = Math.floor(virtualThumbStart / 2);
18530
+ const realThumbSize = Math.ceil((virtualThumbStart + virtualThumbSize) / 2) - realThumbStart;
18531
+ if (this.orientation === "vertical") {
18532
+ return {
18533
+ x: this.x,
18534
+ y: this.y + realThumbStart,
18535
+ width: this.width,
18536
+ height: Math.max(1, realThumbSize)
18537
+ };
18538
+ } else {
18539
+ return {
18540
+ x: this.x + realThumbStart,
18541
+ y: this.y,
18542
+ width: Math.max(1, realThumbSize),
18543
+ height: this.height
18544
+ };
18545
+ }
18546
+ }
18547
+ renderSelf(buffer) {
18548
+ if (this.orientation === "horizontal") {
18549
+ this.renderHorizontal(buffer);
18550
+ } else {
18551
+ this.renderVertical(buffer);
18552
+ }
18553
+ }
18554
+ renderHorizontal(buffer) {
18555
+ const virtualThumbSize = this.getVirtualThumbSize();
18556
+ const virtualThumbStart = this.getVirtualThumbStart();
18557
+ const virtualThumbEnd = virtualThumbStart + virtualThumbSize;
18558
+ buffer.fillRect(this.x, this.y, this.width, this.height, this._backgroundColor);
18559
+ const realStartCell = Math.floor(virtualThumbStart / 2);
18560
+ const realEndCell = Math.ceil(virtualThumbEnd / 2) - 1;
18561
+ const startX = Math.max(0, realStartCell);
18562
+ const endX = Math.min(this.width - 1, realEndCell);
18563
+ for (let realX = startX;realX <= endX; realX++) {
18564
+ const virtualCellStart = realX * 2;
18565
+ const virtualCellEnd = virtualCellStart + 2;
18566
+ const thumbStartInCell = Math.max(virtualThumbStart, virtualCellStart);
18567
+ const thumbEndInCell = Math.min(virtualThumbEnd, virtualCellEnd);
18568
+ const coverage = thumbEndInCell - thumbStartInCell;
18569
+ let char = " ";
18570
+ if (coverage >= 2) {
18571
+ char = "█";
18572
+ } else {
18573
+ const isLeftHalf = thumbStartInCell === virtualCellStart;
18574
+ if (isLeftHalf) {
18575
+ char = "▌";
18576
+ } else {
18577
+ char = "▐";
18578
+ }
18579
+ }
18580
+ for (let y = 0;y < this.height; y++) {
18581
+ buffer.setCellWithAlphaBlending(this.x + realX, this.y + y, char, this._foregroundColor, this._backgroundColor);
18582
+ }
18583
+ }
18584
+ }
18585
+ renderVertical(buffer) {
18586
+ const virtualThumbSize = this.getVirtualThumbSize();
18587
+ const virtualThumbStart = this.getVirtualThumbStart();
18588
+ const virtualThumbEnd = virtualThumbStart + virtualThumbSize;
18589
+ buffer.fillRect(this.x, this.y, this.width, this.height, this._backgroundColor);
18590
+ const realStartCell = Math.floor(virtualThumbStart / 2);
18591
+ const realEndCell = Math.ceil(virtualThumbEnd / 2) - 1;
18592
+ const startY = Math.max(0, realStartCell);
18593
+ const endY = Math.min(this.height - 1, realEndCell);
18594
+ for (let realY = startY;realY <= endY; realY++) {
18595
+ const virtualCellStart = realY * 2;
18596
+ const virtualCellEnd = virtualCellStart + 2;
18597
+ const thumbStartInCell = Math.max(virtualThumbStart, virtualCellStart);
18598
+ const thumbEndInCell = Math.min(virtualThumbEnd, virtualCellEnd);
18599
+ const coverage = thumbEndInCell - thumbStartInCell;
18600
+ let char = " ";
18601
+ if (coverage >= 2) {
18602
+ char = "█";
18603
+ } else if (coverage > 0) {
18604
+ const virtualPositionInCell = thumbStartInCell - virtualCellStart;
18605
+ if (virtualPositionInCell === 0) {
18606
+ char = "▀";
18607
+ } else {
18608
+ char = "▄";
18609
+ }
18610
+ }
18611
+ for (let x = 0;x < this.width; x++) {
18612
+ buffer.setCellWithAlphaBlending(this.x + x, this.y + realY, char, this._foregroundColor, this._backgroundColor);
18613
+ }
18614
+ }
18615
+ }
18616
+ getVirtualThumbSize() {
18617
+ const virtualTrackSize = this.orientation === "vertical" ? this.height * 2 : this.width * 2;
18618
+ const range = this._max - this._min;
18619
+ if (range === 0)
18620
+ return virtualTrackSize;
18621
+ const viewportSize = Math.max(1, this._viewPortSize);
18622
+ const contentSize = range + viewportSize;
18623
+ if (contentSize <= viewportSize)
18624
+ return virtualTrackSize;
18625
+ const thumbRatio = viewportSize / contentSize;
18626
+ const calculatedSize = Math.floor(virtualTrackSize * thumbRatio);
18627
+ return Math.max(1, Math.min(calculatedSize, virtualTrackSize));
18628
+ }
18629
+ getVirtualThumbStart() {
18630
+ const virtualTrackSize = this.orientation === "vertical" ? this.height * 2 : this.width * 2;
18631
+ const range = this._max - this._min;
18632
+ if (range === 0)
18633
+ return 0;
18634
+ const valueRatio = (this._value - this._min) / range;
18635
+ const virtualThumbSize = this.getVirtualThumbSize();
18636
+ return Math.round(valueRatio * (virtualTrackSize - virtualThumbSize));
18637
+ }
18638
+ };
18639
+ ScrollBarRenderable = class ScrollBarRenderable extends Renderable {
18640
+ slider;
18641
+ startArrow;
18642
+ endArrow;
18643
+ orientation;
18644
+ _focusable = true;
18645
+ _scrollSize = 0;
18646
+ _scrollPosition = 0;
18647
+ _viewportSize = 0;
18648
+ _showArrows = false;
18649
+ _manualVisibility = false;
18650
+ _onChange;
18651
+ scrollStep = null;
18652
+ get visible() {
18653
+ return super.visible;
18654
+ }
18655
+ set visible(value) {
18656
+ this._manualVisibility = true;
18657
+ super.visible = value;
18658
+ }
18659
+ resetVisibilityControl() {
18660
+ this._manualVisibility = false;
18661
+ this.recalculateVisibility();
18662
+ }
18663
+ get scrollSize() {
18664
+ return this._scrollSize;
18665
+ }
18666
+ get scrollPosition() {
18667
+ return this._scrollPosition;
18668
+ }
18669
+ get viewportSize() {
18670
+ return this._viewportSize;
18671
+ }
18672
+ set scrollSize(value) {
18673
+ if (value === this.scrollSize)
18674
+ return;
18675
+ this._scrollSize = value;
18676
+ this.recalculateVisibility();
18677
+ this.updateSliderFromScrollState();
18678
+ this.scrollPosition = this.scrollPosition;
18679
+ }
18680
+ set scrollPosition(value) {
18681
+ const newPosition = Math.round(Math.min(Math.max(0, value), this.scrollSize - this.viewportSize));
18682
+ if (newPosition !== this._scrollPosition) {
18683
+ this._scrollPosition = newPosition;
18684
+ this.updateSliderFromScrollState();
18685
+ }
18686
+ }
18687
+ set viewportSize(value) {
18688
+ if (value === this.viewportSize)
18689
+ return;
18690
+ this._viewportSize = value;
18691
+ this.slider.viewPortSize = Math.max(1, this._viewportSize);
18692
+ this.recalculateVisibility();
18693
+ this.updateSliderFromScrollState();
18694
+ this.scrollPosition = this.scrollPosition;
18695
+ }
18696
+ get showArrows() {
18697
+ return this._showArrows;
18698
+ }
18699
+ set showArrows(value) {
18700
+ if (value === this._showArrows)
18701
+ return;
18702
+ this._showArrows = value;
18703
+ this.startArrow.visible = value;
18704
+ this.endArrow.visible = value;
18705
+ }
18706
+ constructor(ctx, { trackOptions, arrowOptions, orientation, showArrows = false, ...options }) {
18707
+ super(ctx, {
18708
+ flexDirection: orientation === "vertical" ? "column" : "row",
18709
+ alignSelf: "stretch",
18710
+ alignItems: "stretch",
18711
+ ...options
18712
+ });
18713
+ this._onChange = options.onChange;
18714
+ this.orientation = orientation;
18715
+ this._showArrows = showArrows;
18716
+ const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
18717
+ const defaultStepSize = Math.max(1, this._viewportSize);
18718
+ const stepSize = trackOptions?.viewPortSize ?? defaultStepSize;
18719
+ this.slider = new SliderRenderable(ctx, {
18720
+ orientation,
18721
+ min: 0,
18722
+ max: scrollRange,
18723
+ value: this._scrollPosition,
18724
+ viewPortSize: stepSize,
18725
+ onChange: (value) => {
18726
+ this._scrollPosition = Math.round(value);
18727
+ this._onChange?.(this._scrollPosition);
18728
+ this.emit("change", { position: this._scrollPosition });
18729
+ },
18730
+ ...orientation === "vertical" ? {
18731
+ width: Math.max(1, Math.min(2, this.width)),
18732
+ height: "100%",
18733
+ marginLeft: "auto"
18734
+ } : {
18735
+ width: "100%",
18736
+ height: 1,
18737
+ marginTop: "auto"
18738
+ },
18739
+ flexGrow: 1,
18740
+ flexShrink: 1,
18741
+ ...trackOptions
18742
+ });
18743
+ this.updateSliderFromScrollState();
18744
+ const arrowOpts = arrowOptions ? {
18745
+ foregroundColor: arrowOptions.backgroundColor,
18746
+ backgroundColor: arrowOptions.backgroundColor,
18747
+ attributes: arrowOptions.attributes,
18748
+ ...arrowOptions
18749
+ } : {};
18750
+ this.startArrow = new ArrowRenderable(ctx, {
18751
+ alignSelf: "center",
18752
+ visible: this.showArrows,
18753
+ direction: this.orientation === "vertical" ? "up" : "left",
18754
+ height: this.orientation === "vertical" ? 1 : 1,
18755
+ ...arrowOpts
18756
+ });
18757
+ this.endArrow = new ArrowRenderable(ctx, {
18758
+ alignSelf: "center",
18759
+ visible: this.showArrows,
18760
+ direction: this.orientation === "vertical" ? "down" : "right",
18761
+ height: this.orientation === "vertical" ? 1 : 1,
18762
+ ...arrowOpts
18763
+ });
18764
+ this.add(this.startArrow);
18765
+ this.add(this.slider);
18766
+ this.add(this.endArrow);
18767
+ let startArrowMouseTimeout = undefined;
18768
+ let endArrowMouseTimeout = undefined;
18769
+ this.startArrow.onMouseDown = (event) => {
18770
+ event.stopPropagation();
18771
+ event.preventDefault();
18772
+ this.scrollBy(-0.5, "viewport");
18773
+ startArrowMouseTimeout = setTimeout(() => {
18774
+ this.scrollBy(-0.5, "viewport");
18775
+ startArrowMouseTimeout = setInterval(() => {
18776
+ this.scrollBy(-0.2, "viewport");
18777
+ }, 200);
18778
+ }, 500);
18779
+ };
18780
+ this.startArrow.onMouseUp = (event) => {
18781
+ event.stopPropagation();
18782
+ clearInterval(startArrowMouseTimeout);
18783
+ };
18784
+ this.endArrow.onMouseDown = (event) => {
18785
+ event.stopPropagation();
18786
+ event.preventDefault();
18787
+ this.scrollBy(0.5, "viewport");
18788
+ endArrowMouseTimeout = setTimeout(() => {
18789
+ this.scrollBy(0.5, "viewport");
18790
+ endArrowMouseTimeout = setInterval(() => {
18791
+ this.scrollBy(0.2, "viewport");
18792
+ }, 200);
18793
+ }, 500);
18794
+ };
18795
+ this.endArrow.onMouseUp = (event) => {
18796
+ event.stopPropagation();
18797
+ clearInterval(endArrowMouseTimeout);
18798
+ };
18799
+ }
18800
+ set arrowOptions(options) {
18801
+ Object.assign(this.startArrow, options);
18802
+ Object.assign(this.endArrow, options);
18803
+ this.requestRender();
18804
+ }
18805
+ set trackOptions(options) {
18806
+ Object.assign(this.slider, options);
18807
+ this.requestRender();
18808
+ }
18809
+ updateSliderFromScrollState() {
18810
+ const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
18811
+ this.slider.min = 0;
18812
+ this.slider.max = scrollRange;
18813
+ this.slider.value = Math.min(this._scrollPosition, scrollRange);
18814
+ }
18815
+ scrollBy(delta, unit = "absolute") {
18816
+ const multiplier = unit === "viewport" ? this.viewportSize : unit === "content" ? this.scrollSize : unit === "step" ? this.scrollStep ?? 1 : 1;
18817
+ const resolvedDelta = multiplier * delta;
18818
+ this.scrollPosition += resolvedDelta;
18819
+ }
18820
+ recalculateVisibility() {
18821
+ if (!this._manualVisibility) {
18822
+ const sizeRatio = this.scrollSize <= this.viewportSize ? 1 : this.viewportSize / this.scrollSize;
18823
+ super.visible = sizeRatio < 1;
18824
+ }
18825
+ }
18826
+ handleKeyPress(key) {
18827
+ switch (key.name) {
18828
+ case "left":
18829
+ case "h":
18830
+ if (this.orientation !== "horizontal")
18831
+ return false;
18832
+ this.scrollBy(-1 / 5, "viewport");
18833
+ return true;
18834
+ case "right":
18835
+ case "l":
18836
+ if (this.orientation !== "horizontal")
18837
+ return false;
18838
+ this.scrollBy(1 / 5, "viewport");
18839
+ return true;
18840
+ case "up":
18841
+ case "k":
18842
+ if (this.orientation !== "vertical")
18843
+ return false;
18844
+ this.scrollBy(-1 / 5, "viewport");
18845
+ return true;
18846
+ case "down":
18847
+ case "j":
18848
+ if (this.orientation !== "vertical")
18849
+ return false;
18850
+ this.scrollBy(1 / 5, "viewport");
18851
+ return true;
18852
+ case "pageup":
18853
+ this.scrollBy(-1 / 2, "viewport");
18854
+ return true;
18855
+ case "pagedown":
18856
+ this.scrollBy(1 / 2, "viewport");
18857
+ return true;
18858
+ case "home":
18859
+ this.scrollBy(-1, "content");
18860
+ return true;
18861
+ case "end":
18862
+ this.scrollBy(1, "content");
18863
+ return true;
18864
+ }
18865
+ return false;
18866
+ }
18867
+ };
18868
+ ArrowRenderable = class ArrowRenderable extends Renderable {
18869
+ _direction;
18870
+ _foregroundColor;
18871
+ _backgroundColor;
18872
+ _attributes;
18873
+ _arrowChars;
18874
+ constructor(ctx, options) {
18875
+ super(ctx, options);
18876
+ this._direction = options.direction;
18877
+ this._foregroundColor = options.foregroundColor ? parseColor(options.foregroundColor) : RGBA.fromValues(1, 1, 1, 1);
18878
+ this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : RGBA.fromValues(0, 0, 0, 0);
18879
+ this._attributes = options.attributes ?? 0;
18880
+ this._arrowChars = {
18881
+ up: "▲",
18882
+ down: "▼",
18883
+ left: "◀",
18884
+ right: "▶",
18885
+ ...options.arrowChars
18886
+ };
18887
+ if (!options.width) {
18888
+ this.width = Bun.stringWidth(this.getArrowChar());
18889
+ }
18890
+ }
18891
+ get direction() {
18892
+ return this._direction;
18893
+ }
18894
+ set direction(value) {
18895
+ if (this._direction !== value) {
18896
+ this._direction = value;
18897
+ this.requestRender();
18898
+ }
18899
+ }
18900
+ get foregroundColor() {
18901
+ return this._foregroundColor;
18902
+ }
18903
+ set foregroundColor(value) {
18904
+ if (this._foregroundColor !== value) {
18905
+ this._foregroundColor = parseColor(value);
18906
+ this.requestRender();
18907
+ }
18908
+ }
18909
+ get backgroundColor() {
18910
+ return this._backgroundColor;
18911
+ }
18912
+ set backgroundColor(value) {
18913
+ if (this._backgroundColor !== value) {
18914
+ this._backgroundColor = parseColor(value);
18915
+ this.requestRender();
18916
+ }
18917
+ }
18918
+ get attributes() {
18919
+ return this._attributes;
18920
+ }
18921
+ set attributes(value) {
18922
+ if (this._attributes !== value) {
18923
+ this._attributes = value;
18924
+ this.requestRender();
18925
+ }
18926
+ }
18927
+ set arrowChars(value) {
18928
+ this._arrowChars = {
18929
+ ...this._arrowChars,
18930
+ ...value
18931
+ };
18932
+ this.requestRender();
18933
+ }
18934
+ renderSelf(buffer) {
18935
+ const char = this.getArrowChar();
18936
+ buffer.drawText(char, this.x, this.y, this._foregroundColor, this._backgroundColor, this._attributes);
18937
+ }
18938
+ getArrowChar() {
18939
+ switch (this._direction) {
18940
+ case "up":
18941
+ return this._arrowChars.up;
18942
+ case "down":
18943
+ return this._arrowChars.down;
18944
+ case "left":
18945
+ return this._arrowChars.left;
18946
+ case "right":
18947
+ return this._arrowChars.right;
18948
+ default:
18949
+ return "?";
18950
+ }
18951
+ }
18952
+ };
18953
+ ContentRenderable = class ContentRenderable extends BoxRenderable {
18954
+ viewport;
18955
+ _viewportCulling;
18956
+ constructor(ctx, viewport, viewportCulling, options) {
18957
+ super(ctx, options);
18958
+ this.viewport = viewport;
18959
+ this._viewportCulling = viewportCulling;
18960
+ }
18961
+ get viewportCulling() {
18962
+ return this._viewportCulling;
18963
+ }
18964
+ set viewportCulling(value) {
18965
+ this._viewportCulling = value;
18966
+ }
18967
+ _getVisibleChildren() {
18968
+ if (this._viewportCulling) {
18969
+ return getObjectsInViewport(this.viewport, this.getChildrenSortedByPrimaryAxis(), this.primaryAxis, 0).map((child) => child.num);
18970
+ }
18971
+ return this.getChildrenSortedByPrimaryAxis().map((child) => child.num);
18972
+ }
18973
+ };
18974
+ ScrollBoxRenderable = class ScrollBoxRenderable extends BoxRenderable {
18975
+ static idCounter = 0;
18976
+ internalId = 0;
18977
+ wrapper;
18978
+ viewport;
18979
+ content;
18980
+ horizontalScrollBar;
18981
+ verticalScrollBar;
18982
+ _focusable = true;
18983
+ selectionListener;
18984
+ autoScrollMouseX = 0;
18985
+ autoScrollMouseY = 0;
18986
+ autoScrollThresholdVertical = 3;
18987
+ autoScrollThresholdHorizontal = 3;
18988
+ autoScrollSpeedSlow = 6;
18989
+ autoScrollSpeedMedium = 36;
18990
+ autoScrollSpeedFast = 72;
18991
+ isAutoScrolling = false;
18992
+ cachedAutoScrollSpeed = 3;
18993
+ autoScrollAccumulatorX = 0;
18994
+ autoScrollAccumulatorY = 0;
18995
+ scrollAccumulatorX = 0;
18996
+ scrollAccumulatorY = 0;
18997
+ _stickyScroll;
18998
+ _stickyScrollTop = false;
18999
+ _stickyScrollBottom = false;
19000
+ _stickyScrollLeft = false;
19001
+ _stickyScrollRight = false;
19002
+ _stickyStart;
19003
+ _hasManualScroll = false;
19004
+ _isApplyingStickyScroll = false;
19005
+ scrollAccel;
19006
+ get stickyScroll() {
19007
+ return this._stickyScroll;
19008
+ }
19009
+ set stickyScroll(value) {
19010
+ this._stickyScroll = value;
19011
+ this.updateStickyState();
19012
+ }
19013
+ get stickyStart() {
19014
+ return this._stickyStart;
19015
+ }
19016
+ set stickyStart(value) {
19017
+ this._stickyStart = value;
19018
+ this.updateStickyState();
19019
+ }
19020
+ get scrollTop() {
19021
+ return this.verticalScrollBar.scrollPosition;
19022
+ }
19023
+ set scrollTop(value) {
19024
+ this.verticalScrollBar.scrollPosition = value;
19025
+ if (!this._isApplyingStickyScroll) {
19026
+ const maxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
19027
+ if (!this.isAtStickyPosition() && maxScrollTop > 1) {
19028
+ this._hasManualScroll = true;
19029
+ }
19030
+ }
19031
+ this.updateStickyState();
19032
+ }
19033
+ get scrollLeft() {
19034
+ return this.horizontalScrollBar.scrollPosition;
19035
+ }
19036
+ set scrollLeft(value) {
19037
+ this.horizontalScrollBar.scrollPosition = value;
19038
+ if (!this._isApplyingStickyScroll) {
19039
+ const maxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
19040
+ if (!this.isAtStickyPosition() && maxScrollLeft > 1) {
19041
+ this._hasManualScroll = true;
19042
+ }
19043
+ }
19044
+ this.updateStickyState();
19045
+ }
19046
+ get scrollWidth() {
19047
+ return this.horizontalScrollBar.scrollSize;
19048
+ }
19049
+ get scrollHeight() {
19050
+ return this.verticalScrollBar.scrollSize;
19051
+ }
19052
+ updateStickyState() {
19053
+ if (!this._stickyScroll)
19054
+ return;
19055
+ const maxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
19056
+ const maxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
19057
+ if (this.scrollTop <= 0) {
19058
+ this._stickyScrollTop = true;
19059
+ this._stickyScrollBottom = false;
19060
+ } else if (this.scrollTop >= maxScrollTop) {
19061
+ this._stickyScrollTop = false;
19062
+ this._stickyScrollBottom = true;
19063
+ } else {
19064
+ this._stickyScrollTop = false;
19065
+ this._stickyScrollBottom = false;
19066
+ }
19067
+ if (this.scrollLeft <= 0) {
19068
+ this._stickyScrollLeft = true;
19069
+ this._stickyScrollRight = false;
19070
+ } else if (this.scrollLeft >= maxScrollLeft) {
19071
+ this._stickyScrollLeft = false;
19072
+ this._stickyScrollRight = true;
19073
+ } else {
19074
+ this._stickyScrollLeft = false;
19075
+ this._stickyScrollRight = false;
19076
+ }
19077
+ }
19078
+ applyStickyStart(stickyStart) {
19079
+ this._isApplyingStickyScroll = true;
19080
+ switch (stickyStart) {
19081
+ case "top":
19082
+ this._stickyScrollTop = true;
19083
+ this._stickyScrollBottom = false;
19084
+ this.verticalScrollBar.scrollPosition = 0;
19085
+ break;
19086
+ case "bottom":
19087
+ this._stickyScrollTop = false;
19088
+ this._stickyScrollBottom = true;
19089
+ this.verticalScrollBar.scrollPosition = Math.max(0, this.scrollHeight - this.viewport.height);
19090
+ break;
19091
+ case "left":
19092
+ this._stickyScrollLeft = true;
19093
+ this._stickyScrollRight = false;
19094
+ this.horizontalScrollBar.scrollPosition = 0;
19095
+ break;
19096
+ case "right":
19097
+ this._stickyScrollLeft = false;
19098
+ this._stickyScrollRight = true;
19099
+ this.horizontalScrollBar.scrollPosition = Math.max(0, this.scrollWidth - this.viewport.width);
19100
+ break;
19101
+ }
19102
+ this._isApplyingStickyScroll = false;
19103
+ }
19104
+ constructor(ctx, {
19105
+ wrapperOptions,
19106
+ viewportOptions,
19107
+ contentOptions,
19108
+ rootOptions,
19109
+ scrollbarOptions,
19110
+ verticalScrollbarOptions,
19111
+ horizontalScrollbarOptions,
19112
+ stickyScroll = false,
19113
+ stickyStart,
19114
+ scrollX = false,
19115
+ scrollY = true,
19116
+ scrollAcceleration,
19117
+ viewportCulling = true,
19118
+ ...options
19119
+ }) {
19120
+ super(ctx, {
19121
+ flexDirection: "row",
19122
+ alignItems: "stretch",
19123
+ ...options,
19124
+ ...rootOptions
19125
+ });
19126
+ this.internalId = ScrollBoxRenderable.idCounter++;
19127
+ this._stickyScroll = stickyScroll;
19128
+ this._stickyStart = stickyStart;
19129
+ this.scrollAccel = scrollAcceleration ?? new LinearScrollAccel;
19130
+ this.wrapper = new BoxRenderable(ctx, {
19131
+ flexDirection: "column",
19132
+ flexGrow: 1,
19133
+ ...wrapperOptions,
19134
+ id: `scroll-box-wrapper-${this.internalId}`
19135
+ });
19136
+ super.add(this.wrapper);
19137
+ this.viewport = new BoxRenderable(ctx, {
19138
+ flexDirection: "column",
19139
+ flexGrow: 1,
19140
+ overflow: "hidden",
19141
+ onSizeChange: () => {
19142
+ this.recalculateBarProps();
19143
+ },
19144
+ ...viewportOptions,
19145
+ id: `scroll-box-viewport-${this.internalId}`
19146
+ });
19147
+ this.wrapper.add(this.viewport);
19148
+ this.content = new ContentRenderable(ctx, this.viewport, viewportCulling, {
19149
+ alignSelf: "flex-start",
19150
+ flexShrink: 0,
19151
+ ...scrollX ? { minWidth: "100%" } : { minWidth: "100%", maxWidth: "100%" },
19152
+ ...scrollY ? { minHeight: "100%" } : { minHeight: "100%", maxHeight: "100%" },
19153
+ onSizeChange: () => {
19154
+ this.recalculateBarProps();
19155
+ },
19156
+ ...contentOptions,
19157
+ id: `scroll-box-content-${this.internalId}`
19158
+ });
19159
+ this.viewport.add(this.content);
19160
+ this.verticalScrollBar = new ScrollBarRenderable(ctx, {
19161
+ ...scrollbarOptions,
19162
+ ...verticalScrollbarOptions,
19163
+ arrowOptions: {
19164
+ ...scrollbarOptions?.arrowOptions,
19165
+ ...verticalScrollbarOptions?.arrowOptions
19166
+ },
19167
+ id: `scroll-box-vertical-scrollbar-${this.internalId}`,
19168
+ orientation: "vertical",
19169
+ onChange: (position) => {
19170
+ this.content.translateY = -position;
19171
+ if (!this._isApplyingStickyScroll) {
19172
+ const maxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
19173
+ if (!this.isAtStickyPosition() && maxScrollTop > 1) {
19174
+ this._hasManualScroll = true;
19175
+ }
19176
+ }
19177
+ this.updateStickyState();
19178
+ }
19179
+ });
19180
+ super.add(this.verticalScrollBar);
19181
+ this.horizontalScrollBar = new ScrollBarRenderable(ctx, {
19182
+ ...scrollbarOptions,
19183
+ ...horizontalScrollbarOptions,
19184
+ arrowOptions: {
19185
+ ...scrollbarOptions?.arrowOptions,
19186
+ ...horizontalScrollbarOptions?.arrowOptions
19187
+ },
19188
+ id: `scroll-box-horizontal-scrollbar-${this.internalId}`,
19189
+ orientation: "horizontal",
19190
+ onChange: (position) => {
19191
+ this.content.translateX = -position;
19192
+ if (!this._isApplyingStickyScroll) {
19193
+ const maxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
19194
+ if (!this.isAtStickyPosition() && maxScrollLeft > 1) {
19195
+ this._hasManualScroll = true;
19196
+ }
19197
+ }
19198
+ this.updateStickyState();
19199
+ }
19200
+ });
19201
+ this.wrapper.add(this.horizontalScrollBar);
19202
+ this.recalculateBarProps();
19203
+ if (stickyStart && stickyScroll) {
19204
+ this.applyStickyStart(stickyStart);
19205
+ }
19206
+ this.selectionListener = () => {
19207
+ const selection = this._ctx.getSelection();
19208
+ if (!selection || !selection.isSelecting) {
19209
+ this.stopAutoScroll();
19210
+ }
19211
+ };
19212
+ this._ctx.on("selection", this.selectionListener);
19213
+ }
19214
+ onUpdate(deltaTime) {
19215
+ this.handleAutoScroll(deltaTime);
19216
+ }
19217
+ scrollBy(delta, unit = "absolute") {
19218
+ if (typeof delta === "number") {
19219
+ this.verticalScrollBar.scrollBy(delta, unit);
19220
+ } else {
19221
+ this.verticalScrollBar.scrollBy(delta.y, unit);
19222
+ this.horizontalScrollBar.scrollBy(delta.x, unit);
19223
+ }
19224
+ }
19225
+ scrollTo(position) {
19226
+ if (typeof position === "number") {
19227
+ this.scrollTop = position;
19228
+ } else {
19229
+ this.scrollTop = position.y;
19230
+ this.scrollLeft = position.x;
19231
+ }
19232
+ }
19233
+ isAtStickyPosition() {
19234
+ if (!this._stickyScroll || !this._stickyStart) {
19235
+ return false;
19236
+ }
19237
+ const maxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
19238
+ const maxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
19239
+ switch (this._stickyStart) {
19240
+ case "top":
19241
+ return this.scrollTop === 0;
19242
+ case "bottom":
19243
+ return this.scrollTop >= maxScrollTop;
19244
+ case "left":
19245
+ return this.scrollLeft === 0;
19246
+ case "right":
19247
+ return this.scrollLeft >= maxScrollLeft;
19248
+ default:
19249
+ return false;
19250
+ }
19251
+ }
19252
+ add(obj, index) {
19253
+ return this.content.add(obj, index);
19254
+ }
19255
+ insertBefore(obj, anchor) {
19256
+ return this.content.insertBefore(obj, anchor);
19257
+ }
19258
+ remove(id) {
19259
+ this.content.remove(id);
19260
+ }
19261
+ getChildren() {
19262
+ return this.content.getChildren();
19263
+ }
19264
+ onMouseEvent(event) {
19265
+ if (event.type === "scroll") {
19266
+ let dir = event.scroll?.direction;
19267
+ if (event.modifiers.shift)
19268
+ dir = dir === "up" ? "left" : dir === "down" ? "right" : dir === "right" ? "down" : "up";
19269
+ const baseDelta = event.scroll?.delta ?? 0;
19270
+ const now = Date.now();
19271
+ const multiplier = this.scrollAccel.tick(now);
19272
+ const scrollAmount = baseDelta * multiplier;
19273
+ if (dir === "up") {
19274
+ this.scrollAccumulatorY -= scrollAmount;
19275
+ const integerScroll = Math.trunc(this.scrollAccumulatorY);
19276
+ if (integerScroll !== 0) {
19277
+ this.scrollTop += integerScroll;
19278
+ this.scrollAccumulatorY -= integerScroll;
19279
+ }
19280
+ } else if (dir === "down") {
19281
+ this.scrollAccumulatorY += scrollAmount;
19282
+ const integerScroll = Math.trunc(this.scrollAccumulatorY);
19283
+ if (integerScroll !== 0) {
19284
+ this.scrollTop += integerScroll;
19285
+ this.scrollAccumulatorY -= integerScroll;
19286
+ }
19287
+ } else if (dir === "left") {
19288
+ this.scrollAccumulatorX -= scrollAmount;
19289
+ const integerScroll = Math.trunc(this.scrollAccumulatorX);
19290
+ if (integerScroll !== 0) {
19291
+ this.scrollLeft += integerScroll;
19292
+ this.scrollAccumulatorX -= integerScroll;
19293
+ }
19294
+ } else if (dir === "right") {
19295
+ this.scrollAccumulatorX += scrollAmount;
19296
+ const integerScroll = Math.trunc(this.scrollAccumulatorX);
19297
+ if (integerScroll !== 0) {
19298
+ this.scrollLeft += integerScroll;
19299
+ this.scrollAccumulatorX -= integerScroll;
19300
+ }
19301
+ }
19302
+ const maxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
19303
+ const maxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
19304
+ if (maxScrollTop > 1 || maxScrollLeft > 1) {
19305
+ this._hasManualScroll = true;
19306
+ }
19307
+ }
19308
+ if (event.type === "drag" && event.isSelecting) {
19309
+ this.updateAutoScroll(event.x, event.y);
19310
+ } else if (event.type === "up") {
19311
+ this.stopAutoScroll();
19312
+ }
19313
+ }
19314
+ handleKeyPress(key) {
19315
+ if (this.verticalScrollBar.handleKeyPress(key)) {
19316
+ this._hasManualScroll = true;
19317
+ this.scrollAccel.reset();
19318
+ this.resetScrollAccumulators();
19319
+ return true;
19320
+ }
19321
+ if (this.horizontalScrollBar.handleKeyPress(key)) {
19322
+ this._hasManualScroll = true;
19323
+ this.scrollAccel.reset();
19324
+ this.resetScrollAccumulators();
19325
+ return true;
19326
+ }
19327
+ return false;
19328
+ }
19329
+ resetScrollAccumulators() {
19330
+ this.scrollAccumulatorX = 0;
19331
+ this.scrollAccumulatorY = 0;
19332
+ }
19333
+ startAutoScroll(mouseX, mouseY) {
19334
+ this.stopAutoScroll();
19335
+ this.autoScrollMouseX = mouseX;
19336
+ this.autoScrollMouseY = mouseY;
19337
+ this.cachedAutoScrollSpeed = this.getAutoScrollSpeed(mouseX, mouseY);
19338
+ this.isAutoScrolling = true;
19339
+ if (!this.live) {
19340
+ this.live = true;
19341
+ }
19342
+ }
19343
+ updateAutoScroll(mouseX, mouseY) {
19344
+ this.autoScrollMouseX = mouseX;
19345
+ this.autoScrollMouseY = mouseY;
19346
+ this.cachedAutoScrollSpeed = this.getAutoScrollSpeed(mouseX, mouseY);
19347
+ const scrollX = this.getAutoScrollDirectionX(mouseX);
19348
+ const scrollY = this.getAutoScrollDirectionY(mouseY);
19349
+ if (scrollX === 0 && scrollY === 0) {
19350
+ this.stopAutoScroll();
19351
+ } else if (!this.isAutoScrolling) {
19352
+ this.startAutoScroll(mouseX, mouseY);
19353
+ }
19354
+ }
19355
+ stopAutoScroll() {
19356
+ const wasAutoScrolling = this.isAutoScrolling;
19357
+ this.isAutoScrolling = false;
19358
+ this.autoScrollAccumulatorX = 0;
19359
+ this.autoScrollAccumulatorY = 0;
19360
+ if (wasAutoScrolling && !this.hasOtherLiveReasons()) {
19361
+ this.live = false;
19362
+ }
19363
+ }
19364
+ hasOtherLiveReasons() {
19365
+ return false;
19366
+ }
19367
+ handleAutoScroll(deltaTime) {
19368
+ if (!this.isAutoScrolling)
19369
+ return;
19370
+ const scrollX = this.getAutoScrollDirectionX(this.autoScrollMouseX);
19371
+ const scrollY = this.getAutoScrollDirectionY(this.autoScrollMouseY);
19372
+ const scrollAmount = this.cachedAutoScrollSpeed * (deltaTime / 1000);
19373
+ let scrolled = false;
19374
+ if (scrollX !== 0) {
19375
+ this.autoScrollAccumulatorX += scrollX * scrollAmount;
19376
+ const integerScrollX = Math.trunc(this.autoScrollAccumulatorX);
19377
+ if (integerScrollX !== 0) {
19378
+ this.scrollLeft += integerScrollX;
19379
+ this.autoScrollAccumulatorX -= integerScrollX;
19380
+ scrolled = true;
19381
+ }
19382
+ }
19383
+ if (scrollY !== 0) {
19384
+ this.autoScrollAccumulatorY += scrollY * scrollAmount;
19385
+ const integerScrollY = Math.trunc(this.autoScrollAccumulatorY);
19386
+ if (integerScrollY !== 0) {
19387
+ this.scrollTop += integerScrollY;
19388
+ this.autoScrollAccumulatorY -= integerScrollY;
19389
+ scrolled = true;
19390
+ }
19391
+ }
19392
+ if (scrolled) {
19393
+ this._ctx.requestSelectionUpdate();
19394
+ }
19395
+ if (scrollX === 0 && scrollY === 0) {
19396
+ this.stopAutoScroll();
19397
+ }
19398
+ }
19399
+ getAutoScrollDirectionX(mouseX) {
19400
+ const relativeX = mouseX - this.x;
19401
+ const distToLeft = relativeX;
19402
+ const distToRight = this.width - relativeX;
19403
+ if (distToLeft <= this.autoScrollThresholdHorizontal) {
19404
+ return this.scrollLeft > 0 ? -1 : 0;
19405
+ } else if (distToRight <= this.autoScrollThresholdHorizontal) {
19406
+ const maxScrollLeft = this.scrollWidth - this.viewport.width;
19407
+ return this.scrollLeft < maxScrollLeft ? 1 : 0;
19408
+ }
19409
+ return 0;
19410
+ }
19411
+ getAutoScrollDirectionY(mouseY) {
19412
+ const relativeY = mouseY - this.y;
19413
+ const distToTop = relativeY;
19414
+ const distToBottom = this.height - relativeY;
19415
+ if (distToTop <= this.autoScrollThresholdVertical) {
19416
+ return this.scrollTop > 0 ? -1 : 0;
19417
+ } else if (distToBottom <= this.autoScrollThresholdVertical) {
19418
+ const maxScrollTop = this.scrollHeight - this.viewport.height;
19419
+ return this.scrollTop < maxScrollTop ? 1 : 0;
19420
+ }
19421
+ return 0;
19422
+ }
19423
+ getAutoScrollSpeed(mouseX, mouseY) {
19424
+ const relativeX = mouseX - this.x;
19425
+ const relativeY = mouseY - this.y;
19426
+ const distToLeft = relativeX;
19427
+ const distToRight = this.width - relativeX;
19428
+ const distToTop = relativeY;
19429
+ const distToBottom = this.height - relativeY;
19430
+ const minDistance = Math.min(distToLeft, distToRight, distToTop, distToBottom);
19431
+ if (minDistance <= 1) {
19432
+ return this.autoScrollSpeedFast;
19433
+ } else if (minDistance <= 2) {
19434
+ return this.autoScrollSpeedMedium;
19435
+ } else {
19436
+ return this.autoScrollSpeedSlow;
19437
+ }
19438
+ }
19439
+ recalculateBarProps() {
19440
+ const wasApplyingStickyScroll = this._isApplyingStickyScroll;
19441
+ this._isApplyingStickyScroll = true;
19442
+ this.verticalScrollBar.scrollSize = this.content.height;
19443
+ this.verticalScrollBar.viewportSize = this.viewport.height;
19444
+ this.horizontalScrollBar.scrollSize = this.content.width;
19445
+ this.horizontalScrollBar.viewportSize = this.viewport.width;
19446
+ if (this._stickyScroll) {
19447
+ const newMaxScrollTop = Math.max(0, this.scrollHeight - this.viewport.height);
19448
+ const newMaxScrollLeft = Math.max(0, this.scrollWidth - this.viewport.width);
19449
+ if (this._stickyStart && !this._hasManualScroll) {
19450
+ this.applyStickyStart(this._stickyStart);
19451
+ } else {
19452
+ if (this._stickyScrollTop) {
19453
+ this.scrollTop = 0;
19454
+ } else if (this._stickyScrollBottom && newMaxScrollTop > 0) {
19455
+ this.scrollTop = newMaxScrollTop;
19456
+ }
19457
+ if (this._stickyScrollLeft) {
19458
+ this.scrollLeft = 0;
19459
+ } else if (this._stickyScrollRight && newMaxScrollLeft > 0) {
19460
+ this.scrollLeft = newMaxScrollLeft;
19461
+ }
19462
+ }
19463
+ }
19464
+ this._isApplyingStickyScroll = wasApplyingStickyScroll;
19465
+ process.nextTick(() => {
19466
+ this.requestRender();
19467
+ });
19468
+ }
19469
+ set rootOptions(options) {
19470
+ Object.assign(this, options);
19471
+ this.requestRender();
19472
+ }
19473
+ set wrapperOptions(options) {
19474
+ Object.assign(this.wrapper, options);
19475
+ this.requestRender();
19476
+ }
19477
+ set viewportOptions(options) {
19478
+ Object.assign(this.viewport, options);
19479
+ this.requestRender();
19480
+ }
19481
+ set contentOptions(options) {
19482
+ Object.assign(this.content, options);
19483
+ this.requestRender();
19484
+ }
19485
+ set scrollbarOptions(options) {
19486
+ Object.assign(this.verticalScrollBar, options);
19487
+ Object.assign(this.horizontalScrollBar, options);
19488
+ this.requestRender();
19489
+ }
19490
+ set verticalScrollbarOptions(options) {
19491
+ Object.assign(this.verticalScrollBar, options);
19492
+ this.requestRender();
19493
+ }
19494
+ set horizontalScrollbarOptions(options) {
19495
+ Object.assign(this.horizontalScrollBar, options);
19496
+ this.requestRender();
19497
+ }
19498
+ get scrollAcceleration() {
19499
+ return this.scrollAccel;
19500
+ }
19501
+ set scrollAcceleration(value) {
19502
+ this.scrollAccel = value;
19503
+ }
19504
+ get viewportCulling() {
19505
+ return this.content.viewportCulling;
19506
+ }
19507
+ set viewportCulling(value) {
19508
+ this.content.viewportCulling = value;
19509
+ this.requestRender();
19510
+ }
19511
+ destroySelf() {
19512
+ if (this.selectionListener) {
19513
+ this._ctx.off("selection", this.selectionListener);
19514
+ this.selectionListener = undefined;
19515
+ }
19516
+ super.destroySelf();
19517
+ }
19518
+ };
18372
19519
  defaultSelectKeybindings = [
18373
19520
  { name: "up", action: "move-up" },
18374
19521
  { name: "k", action: "move-up" },
@@ -20877,21 +22024,21 @@ var config;
20877
22024
  var history = [];
20878
22025
  var currentCwd = getCwd();
20879
22026
  var dryRunMode = false;
22027
+ var chatMessages = [];
22028
+ var messageIdCounter = 0;
20880
22029
  var mainContainer;
20881
22030
  var headerText;
20882
- var cwdText;
20883
- var modelText;
22031
+ var statusBarText;
22032
+ var chatScrollBox;
20884
22033
  var inputField;
20885
- var outputContainer;
20886
- var outputText;
20887
- var statusText;
20888
- var commandPreview;
20889
- var safetyWarning;
20890
- var confirmPrompt;
22034
+ var helpBarText;
20891
22035
  var modelSelector = null;
20892
22036
  var providerSelector = null;
20893
- var pendingCommand = null;
22037
+ var pendingMessageId = null;
20894
22038
  var awaitingConfirmation = false;
22039
+ function generateMessageId() {
22040
+ return `msg-${++messageIdCounter}`;
22041
+ }
20895
22042
  async function main2() {
20896
22043
  config = loadConfig();
20897
22044
  history = loadHistory();
@@ -21069,44 +22216,65 @@ function createMainUI() {
21069
22216
  id: "header-row",
21070
22217
  flexDirection: "row",
21071
22218
  width: "100%",
22219
+ alignItems: "center",
21072
22220
  marginBottom: 1
21073
22221
  });
21074
22222
  mainContainer.add(headerRow);
21075
22223
  headerText = new TextRenderable(renderer, {
21076
22224
  id: "header-text",
21077
- content: t`${bold(fg(theme.colors.primary)("magic-shell"))} ${fg(theme.colors.textMuted)("- natural language to terminal commands")}`,
22225
+ content: t`${bold(fg(theme.colors.primary)("magic-shell"))}`,
21078
22226
  flexGrow: 1
21079
22227
  });
21080
22228
  headerRow.add(headerText);
21081
- const statusRow = new BoxRenderable(renderer, {
21082
- id: "status-row",
21083
- flexDirection: "row",
21084
- width: "100%",
21085
- marginBottom: 1
22229
+ const modelBadge = new TextRenderable(renderer, {
22230
+ id: "model-badge",
22231
+ content: getModelDisplay()
21086
22232
  });
21087
- mainContainer.add(statusRow);
21088
- cwdText = new TextRenderable(renderer, {
21089
- id: "cwd-text",
21090
- content: t`${fg(theme.colors.textMuted)("cwd:")} ${fg(theme.colors.success)(currentCwd)}`,
21091
- flexGrow: 1
22233
+ headerRow.add(modelBadge);
22234
+ statusBarText = new TextRenderable(renderer, {
22235
+ id: "status-bar-text",
22236
+ content: getStatusBarContent(),
22237
+ marginBottom: 1
21092
22238
  });
21093
- statusRow.add(cwdText);
21094
- modelText = new TextRenderable(renderer, {
21095
- id: "model-text",
21096
- content: getModelDisplay()
22239
+ mainContainer.add(statusBarText);
22240
+ chatScrollBox = new ScrollBoxRenderable(renderer, {
22241
+ id: "chat-scroll-box",
22242
+ flexGrow: 1,
22243
+ width: "100%",
22244
+ scrollY: true,
22245
+ scrollX: false,
22246
+ stickyScroll: true,
22247
+ stickyStart: "bottom",
22248
+ rootOptions: {
22249
+ border: true,
22250
+ borderColor: theme.colors.border,
22251
+ borderStyle: "single"
22252
+ },
22253
+ viewportOptions: {
22254
+ backgroundColor: theme.colors.background,
22255
+ paddingLeft: 1,
22256
+ paddingRight: 1,
22257
+ paddingTop: 1
22258
+ },
22259
+ contentOptions: {
22260
+ flexDirection: "column",
22261
+ gap: 1
22262
+ }
21097
22263
  });
21098
- statusRow.add(modelText);
22264
+ mainContainer.add(chatScrollBox);
22265
+ addSystemMessage(getWelcomeMessage());
21099
22266
  const inputRow = new BoxRenderable(renderer, {
21100
22267
  id: "input-row",
21101
22268
  flexDirection: "row",
21102
22269
  width: "100%",
21103
- marginBottom: 1
22270
+ marginTop: 1,
22271
+ alignItems: "center"
21104
22272
  });
21105
22273
  mainContainer.add(inputRow);
21106
22274
  const promptText = new TextRenderable(renderer, {
21107
22275
  id: "prompt-text",
21108
- content: t`${fg(theme.colors.success)(">")} `,
21109
- width: 2
22276
+ content: t`${fg(theme.colors.primary)("~>")} `,
22277
+ width: 3
21110
22278
  });
21111
22279
  inputRow.add(promptText);
21112
22280
  inputField = new InputRenderable(renderer, {
@@ -21124,58 +22292,268 @@ function createMainUI() {
21124
22292
  }
21125
22293
  });
21126
22294
  inputRow.add(inputField);
21127
- commandPreview = new TextRenderable(renderer, {
21128
- id: "command-preview",
21129
- content: "",
21130
- marginBottom: 1
21131
- });
21132
- mainContainer.add(commandPreview);
21133
- safetyWarning = new TextRenderable(renderer, {
21134
- id: "safety-warning",
21135
- content: ""
22295
+ helpBarText = new TextRenderable(renderer, {
22296
+ id: "help-bar-text",
22297
+ content: getHelpBarContent(),
22298
+ marginTop: 1
21136
22299
  });
21137
- mainContainer.add(safetyWarning);
21138
- confirmPrompt = new BoxRenderable(renderer, {
21139
- id: "confirm-prompt",
22300
+ mainContainer.add(helpBarText);
22301
+ inputField.on(InputRenderableEvents.ENTER, handleInput);
22302
+ renderer.keyInput.on("keypress", handleKeypress);
22303
+ inputField.focus();
22304
+ }
22305
+ function getStatusBarContent() {
22306
+ const theme = getTheme();
22307
+ const providerName = config.provider === "opencode-zen" ? "OpenCode Zen" : "OpenRouter";
22308
+ const safeModeIndicator = dryRunMode ? fg(theme.colors.warning)("[DRY RUN]") : fg(theme.colors.success)("Safe");
22309
+ return t`${fg(theme.colors.textMuted)("Provider:")} ${fg(theme.colors.text)(providerName)} ${fg(theme.colors.textMuted)("Model:")} ${fg(theme.colors.text)(currentModel.name)} ${safeModeIndicator}`;
22310
+ }
22311
+ function getHelpBarContent() {
22312
+ const theme = getTheme();
22313
+ if (awaitingConfirmation) {
22314
+ return t`${fg(theme.colors.warning)("[Enter] Run")} ${fg(theme.colors.textMuted)("|")} ${fg(theme.colors.error)("[Esc] Cancel")} ${fg(theme.colors.textMuted)("|")} ${fg(theme.colors.primary)("[e] Edit")}`;
22315
+ }
22316
+ return t`${fg(theme.colors.textMuted)("Ctrl+X")} ${fg(theme.colors.primary)("P")}${fg(theme.colors.textMuted)(" Palette")} ${fg(theme.colors.primary)("M")}${fg(theme.colors.textMuted)(" Model")} ${fg(theme.colors.primary)("T")}${fg(theme.colors.textMuted)(" Theme")} ${fg(theme.colors.primary)("D")}${fg(theme.colors.textMuted)(" Dry-run")} ${fg(theme.colors.primary)("?")}${fg(theme.colors.textMuted)(" Help")}`;
22317
+ }
22318
+ function getWelcomeMessage() {
22319
+ const providerName = config.provider === "opencode-zen" ? "OpenCode Zen" : "OpenRouter";
22320
+ const freeNote = config.provider === "opencode-zen" ? `
22321
+ Free models: grok-code, glm-4.7-free` : "";
22322
+ return `Ready. Using ${providerName}.${freeNote}
22323
+ Type what you want to do, or press Ctrl+X P for command palette.`;
22324
+ }
22325
+ function addSystemMessage(content) {
22326
+ const msg = {
22327
+ id: generateMessageId(),
22328
+ type: "system",
22329
+ content,
22330
+ timestamp: Date.now()
22331
+ };
22332
+ chatMessages.push(msg);
22333
+ renderMessage(msg);
22334
+ return msg;
22335
+ }
22336
+ function addUserMessage(content) {
22337
+ const msg = {
22338
+ id: generateMessageId(),
22339
+ type: "user",
22340
+ content,
22341
+ timestamp: Date.now()
22342
+ };
22343
+ chatMessages.push(msg);
22344
+ renderMessage(msg);
22345
+ return msg;
22346
+ }
22347
+ function addAssistantMessage(content, command, safety) {
22348
+ const msg = {
22349
+ id: generateMessageId(),
22350
+ type: "assistant",
22351
+ content,
22352
+ command,
22353
+ safety,
22354
+ timestamp: Date.now(),
22355
+ executed: false
22356
+ };
22357
+ chatMessages.push(msg);
22358
+ renderMessage(msg);
22359
+ return msg;
22360
+ }
22361
+ function addResultMessage(content, exitCode) {
22362
+ const msg = {
22363
+ id: generateMessageId(),
22364
+ type: "result",
22365
+ content,
22366
+ timestamp: Date.now(),
22367
+ exitCode
22368
+ };
22369
+ chatMessages.push(msg);
22370
+ renderMessage(msg);
22371
+ return msg;
22372
+ }
22373
+ function renderMessage(msg) {
22374
+ const theme = getTheme();
22375
+ const msgBox = createMessageRenderable(msg, theme);
22376
+ chatScrollBox.add(msgBox);
22377
+ }
22378
+ function createMessageRenderable(msg, theme) {
22379
+ switch (msg.type) {
22380
+ case "user":
22381
+ return createUserMessageRenderable(msg, theme);
22382
+ case "assistant":
22383
+ return createAssistantMessageRenderable(msg, theme);
22384
+ case "result":
22385
+ return createResultMessageRenderable(msg, theme);
22386
+ case "system":
22387
+ default:
22388
+ return createSystemMessageRenderable(msg, theme);
22389
+ }
22390
+ }
22391
+ function createUserMessageRenderable(msg, theme) {
22392
+ const box = new BoxRenderable(renderer, {
22393
+ id: `msg-${msg.id}`,
21140
22394
  flexDirection: "row",
21141
- visible: false,
21142
- marginBottom: 1
22395
+ width: "100%"
21143
22396
  });
21144
- mainContainer.add(confirmPrompt);
21145
- const confirmText = new TextRenderable(renderer, {
21146
- id: "confirm-text",
21147
- content: t`${fg(theme.colors.warning)("[Enter] Execute")} ${fg(theme.colors.textMuted)("|")} ${fg(theme.colors.error)("[Esc] Cancel")} ${fg(theme.colors.textMuted)("|")} ${fg(theme.colors.primary)("[e] Edit")}`
22397
+ const text = new TextRenderable(renderer, {
22398
+ id: `msg-${msg.id}-text`,
22399
+ content: t`${fg(theme.colors.success)(">")} ${fg(theme.colors.text)(msg.content)}`
21148
22400
  });
21149
- confirmPrompt.add(confirmText);
21150
- outputContainer = new BoxRenderable(renderer, {
21151
- id: "output-container",
21152
- flexGrow: 1,
22401
+ box.add(text);
22402
+ return box;
22403
+ }
22404
+ function createAssistantMessageRenderable(msg, theme) {
22405
+ const isSelected = pendingMessageId === msg.id;
22406
+ const card = new BoxRenderable(renderer, {
22407
+ id: `msg-${msg.id}`,
22408
+ flexDirection: "column",
22409
+ width: "100%",
21153
22410
  border: true,
21154
- borderColor: theme.colors.border,
22411
+ borderColor: isSelected ? theme.colors.primary : theme.colors.border,
21155
22412
  borderStyle: "single",
21156
- title: "Output",
21157
- padding: 1
22413
+ paddingLeft: 1,
22414
+ paddingRight: 1,
22415
+ paddingTop: 0,
22416
+ paddingBottom: 0,
22417
+ backgroundColor: theme.colors.backgroundPanel
21158
22418
  });
21159
- mainContainer.add(outputContainer);
21160
- const providerName = config.provider === "opencode-zen" ? "OpenCode Zen" : "OpenRouter";
21161
- const freeModelsNote = config.provider === "opencode-zen" ? `
21162
- ${fg(theme.colors.success)("Free models available!")} Try: grok-code, glm-4.7-free` : "";
21163
- outputText = new TextRenderable(renderer, {
21164
- id: "output-text",
21165
- content: t`${fg(theme.colors.textMuted)(`Ready. Using ${providerName}.`)}${freeModelsNote}
21166
-
21167
- ${fg(theme.colors.textMuted)("Type what you want to do, or press")} ${fg(theme.colors.primary)("Ctrl+X P")} ${fg(theme.colors.textMuted)("for command palette.")}`
22419
+ const commandText = new TextRenderable(renderer, {
22420
+ id: `msg-${msg.id}-cmd`,
22421
+ content: t`${fg(theme.colors.textMuted)("Command:")} ${fg(theme.colors.text)(msg.command || "")}`
21168
22422
  });
21169
- outputContainer.add(outputText);
21170
- statusText = new TextRenderable(renderer, {
21171
- id: "status-text",
21172
- content: getDryRunStatus(),
21173
- marginTop: 1
22423
+ card.add(commandText);
22424
+ if (msg.safety) {
22425
+ const severityColor = getSeverityColor(msg.safety.severity);
22426
+ const severityText = msg.safety.isDangerous ? `${msg.safety.severity.toUpperCase()} risk${msg.safety.reason ? ` - ${msg.safety.reason}` : ""}` : "Low risk";
22427
+ const safetyText = new TextRenderable(renderer, {
22428
+ id: `msg-${msg.id}-safety`,
22429
+ content: t`${fg(severityColor)("●")} ${fg(theme.colors.textMuted)(severityText)}`
22430
+ });
22431
+ card.add(safetyText);
22432
+ }
22433
+ if (isSelected && !msg.executed) {
22434
+ const actionsText = new TextRenderable(renderer, {
22435
+ id: `msg-${msg.id}-actions`,
22436
+ content: t`${fg(theme.colors.warning)("[Enter]")} ${fg(theme.colors.textMuted)("Run")} ${fg(theme.colors.primary)("[c]")} ${fg(theme.colors.textMuted)("Copy")} ${fg(theme.colors.primary)("[e]")} ${fg(theme.colors.textMuted)("Edit")}`
22437
+ });
22438
+ card.add(actionsText);
22439
+ }
22440
+ if (msg.executed) {
22441
+ const execText = new TextRenderable(renderer, {
22442
+ id: `msg-${msg.id}-exec`,
22443
+ content: t`${fg(theme.colors.success)("Executed")}`
22444
+ });
22445
+ card.add(execText);
22446
+ }
22447
+ return card;
22448
+ }
22449
+ function createResultMessageRenderable(msg, theme) {
22450
+ const isSuccess = msg.exitCode === undefined || msg.exitCode === 0;
22451
+ const isExpanded = msg.expanded ?? false;
22452
+ const hasOutput = msg.content && msg.content.trim().length > 0;
22453
+ const outputLines = hasOutput ? msg.content.trim().split(`
22454
+ `) : [];
22455
+ const isLongOutput = outputLines.length > 5;
22456
+ const PREVIEW_LINES = 3;
22457
+ const card = new BoxRenderable(renderer, {
22458
+ id: `msg-${msg.id}`,
22459
+ flexDirection: "column",
22460
+ width: "100%",
22461
+ border: true,
22462
+ borderColor: isSuccess ? theme.colors.success : theme.colors.error,
22463
+ borderStyle: "single",
22464
+ paddingLeft: 1,
22465
+ paddingRight: 1,
22466
+ backgroundColor: theme.colors.backgroundPanel,
22467
+ onMouseDown: isLongOutput ? () => {
22468
+ toggleResultExpand(msg.id);
22469
+ } : undefined
21174
22470
  });
21175
- mainContainer.add(statusText);
21176
- inputField.on(InputRenderableEvents.ENTER, handleInput);
21177
- renderer.keyInput.on("keypress", handleKeypress);
21178
- inputField.focus();
22471
+ const statusIcon = isSuccess ? "✓" : "✗";
22472
+ const statusColor = isSuccess ? theme.colors.success : theme.colors.error;
22473
+ const statusLabel = isSuccess ? "Executed successfully" : `Exit code: ${msg.exitCode}`;
22474
+ const expandIcon = isLongOutput ? isExpanded ? "▼" : "▶" : "";
22475
+ const lineCount = isLongOutput ? ` (${outputLines.length} lines)` : "";
22476
+ const statusText = new TextRenderable(renderer, {
22477
+ id: `msg-${msg.id}-status`,
22478
+ content: t`${fg(statusColor)(statusIcon)} ${fg(theme.colors.text)(statusLabel)}${fg(theme.colors.textMuted)(lineCount)} ${fg(theme.colors.primary)(expandIcon)}`
22479
+ });
22480
+ card.add(statusText);
22481
+ if (hasOutput) {
22482
+ let displayContent;
22483
+ if (isExpanded || !isLongOutput) {
22484
+ displayContent = msg.content.trim();
22485
+ } else {
22486
+ const previewLines = outputLines.slice(0, PREVIEW_LINES);
22487
+ displayContent = previewLines.join(`
22488
+ `) + `
22489
+ ... ${outputLines.length - PREVIEW_LINES} more lines`;
22490
+ }
22491
+ const outputText = new TextRenderable(renderer, {
22492
+ id: `msg-${msg.id}-output`,
22493
+ content: t`${fg(theme.colors.textMuted)(displayContent)}`
22494
+ });
22495
+ card.add(outputText);
22496
+ if (isLongOutput) {
22497
+ const hintText = new TextRenderable(renderer, {
22498
+ id: `msg-${msg.id}-hint`,
22499
+ content: t`${fg(theme.colors.primary)("[o]")} ${fg(theme.colors.textMuted)(isExpanded ? "Collapse" : "Expand output")}`
22500
+ });
22501
+ card.add(hintText);
22502
+ }
22503
+ }
22504
+ return card;
22505
+ }
22506
+ function createSystemMessageRenderable(msg, theme) {
22507
+ const box = new BoxRenderable(renderer, {
22508
+ id: `msg-${msg.id}`,
22509
+ flexDirection: "column",
22510
+ width: "100%"
22511
+ });
22512
+ const text = new TextRenderable(renderer, {
22513
+ id: `msg-${msg.id}-text`,
22514
+ content: t`${fg(theme.colors.textMuted)(msg.content)}`
22515
+ });
22516
+ box.add(text);
22517
+ return box;
22518
+ }
22519
+ function updateAssistantMessage(msgId, updates) {
22520
+ const msgIndex = chatMessages.findIndex((m) => m.id === msgId);
22521
+ if (msgIndex === -1)
22522
+ return;
22523
+ const msg = chatMessages[msgIndex];
22524
+ Object.assign(msg, updates);
22525
+ chatScrollBox.remove(`msg-${msgId}`);
22526
+ const theme = getTheme();
22527
+ const newBox = createMessageRenderable(msg, theme);
22528
+ chatScrollBox.add(newBox);
22529
+ }
22530
+ function updateResultMessage(msgId, updates) {
22531
+ const msgIndex = chatMessages.findIndex((m) => m.id === msgId);
22532
+ if (msgIndex === -1)
22533
+ return;
22534
+ const msg = chatMessages[msgIndex];
22535
+ Object.assign(msg, updates);
22536
+ chatScrollBox.remove(`msg-${msgId}`);
22537
+ const theme = getTheme();
22538
+ const newBox = createMessageRenderable(msg, theme);
22539
+ chatScrollBox.add(newBox);
22540
+ }
22541
+ function toggleResultExpand(msgId) {
22542
+ const msg = chatMessages.find((m) => m.id === msgId);
22543
+ if (!msg || msg.type !== "result")
22544
+ return;
22545
+ const outputLines = msg.content?.trim().split(`
22546
+ `) || [];
22547
+ if (outputLines.length <= 5)
22548
+ return;
22549
+ updateResultMessage(msgId, { expanded: !msg.expanded });
22550
+ }
22551
+ function toggleLastResultExpand() {
22552
+ const resultMessages = chatMessages.filter((m) => m.type === "result");
22553
+ if (resultMessages.length === 0)
22554
+ return;
22555
+ const lastResult = resultMessages[resultMessages.length - 1];
22556
+ toggleResultExpand(lastResult.id);
21179
22557
  }
21180
22558
  function getModelDisplay() {
21181
22559
  const theme = getTheme();
@@ -21184,30 +22562,22 @@ function getModelDisplay() {
21184
22562
  const freeBadge = currentModel.free ? fg(theme.colors.success)(" FREE") : "";
21185
22563
  return t`${providerBadge} ${fg(categoryColor)(currentModel.name)}${freeBadge}`;
21186
22564
  }
21187
- function getDryRunStatus() {
21188
- const theme = getTheme();
21189
- if (dryRunMode) {
21190
- return t`${fg(theme.colors.warning)("[DRY RUN]")} ${fg(theme.colors.textMuted)("Ctrl+X P palette | Ctrl+X M model | Ctrl+X D dry-run")}`;
21191
- }
21192
- return t`${fg(theme.colors.textMuted)("Ctrl+X P palette | Ctrl+X M model | Ctrl+X ? help")}`;
21193
- }
21194
22565
  function refreshThemeColors() {
21195
22566
  const theme = getTheme();
21196
22567
  renderer.setBackgroundColor(theme.colors.background);
21197
22568
  if (headerText) {
21198
- headerText.content = t`${bold(fg(theme.colors.primary)("magic-shell"))} ${fg(theme.colors.textMuted)("- natural language to terminal commands")}`;
22569
+ headerText.content = t`${bold(fg(theme.colors.primary)("magic-shell"))}`;
21199
22570
  }
21200
- if (cwdText) {
21201
- cwdText.content = t`${fg(theme.colors.textMuted)("cwd:")} ${fg(theme.colors.success)(currentCwd)}`;
22571
+ if (statusBarText) {
22572
+ statusBarText.content = getStatusBarContent();
21202
22573
  }
21203
- if (modelText) {
21204
- modelText.content = getModelDisplay();
22574
+ if (helpBarText) {
22575
+ helpBarText.content = getHelpBarContent();
21205
22576
  }
21206
- if (statusText) {
21207
- statusText.content = getDryRunStatus();
21208
- }
21209
- if (outputContainer) {
21210
- outputContainer.borderColor = theme.colors.border;
22577
+ if (chatScrollBox) {
22578
+ chatScrollBox.rootOptions = {
22579
+ borderColor: theme.colors.border
22580
+ };
21211
22581
  }
21212
22582
  if (inputField) {
21213
22583
  inputField.focusedBackgroundColor = theme.colors.backgroundPanel;
@@ -21215,14 +22585,6 @@ function refreshThemeColors() {
21215
22585
  inputField.placeholderColor = theme.colors.textMuted;
21216
22586
  inputField.cursorColor = theme.colors.primary;
21217
22587
  }
21218
- const providerName = config.provider === "opencode-zen" ? "OpenCode Zen" : "OpenRouter";
21219
- const freeModelsNote = config.provider === "opencode-zen" ? `
21220
- ${fg(theme.colors.success)("Free models available!")} Try: grok-code, glm-4.7-free` : "";
21221
- if (outputText) {
21222
- outputText.content = t`${fg(theme.colors.textMuted)(`Ready. Using ${providerName}.`)}${freeModelsNote}
21223
-
21224
- ${fg(theme.colors.textMuted)("Type what you want to do, or press")} ${fg(theme.colors.primary)("Ctrl+X P")} ${fg(theme.colors.textMuted)("for command palette.")}`;
21225
- }
21226
22588
  }
21227
22589
  async function handleInput(value) {
21228
22590
  const input = value.trim();
@@ -21233,8 +22595,9 @@ async function handleInput(value) {
21233
22595
  await handleSpecialCommand(input);
21234
22596
  return;
21235
22597
  }
22598
+ addUserMessage(input);
21236
22599
  if (isDirectCommand(input)) {
21237
- await processCommand(input, input);
22600
+ await processDirectCommand(input, input);
21238
22601
  return;
21239
22602
  }
21240
22603
  await translateAndProcess(input);
@@ -21268,38 +22631,50 @@ function isDirectCommand(input) {
21268
22631
  async function translateAndProcess(input) {
21269
22632
  const apiKey = await getApiKey(config.provider);
21270
22633
  if (!apiKey) {
21271
- setOutput(t`${fg("#ef4444")("Error: No API key configured. Run !provider to set up.")}`);
22634
+ addSystemMessage("Error: No API key configured. Run !provider to set up.");
21272
22635
  return;
21273
22636
  }
21274
- setOutput(t`${fg("#64748b")("Translating...")}`);
22637
+ const loadingMsg = addSystemMessage("Translating...");
21275
22638
  try {
21276
22639
  const command = await translateToCommand(apiKey, currentModel, input, currentCwd, history);
21277
- commandPreview.content = t`${fg("#64748b")("Command:")} ${fg("#f8fafc")(command)}`;
22640
+ chatScrollBox.remove(`msg-${loadingMsg.id}`);
22641
+ chatMessages = chatMessages.filter((m) => m.id !== loadingMsg.id);
21278
22642
  const safety = analyzeCommand(command, config);
22643
+ const assistantMsg = addAssistantMessage(input, command, safety);
21279
22644
  if (safety.isDangerous) {
21280
- safetyWarning.content = t`${fg(getSeverityColor(safety.severity))(`[${safety.severity.toUpperCase()}] ${safety.reason}`)}`;
21281
- pendingCommand = command;
22645
+ pendingMessageId = assistantMsg.id;
21282
22646
  awaitingConfirmation = true;
21283
- confirmPrompt.visible = true;
21284
- setOutput(t`${fg("#fbbf24")("Command requires confirmation. Press Enter to execute or Esc to cancel.")}`);
22647
+ helpBarText.content = getHelpBarContent();
21285
22648
  } else {
21286
- safetyWarning.content = "";
21287
- await processCommand(input, command);
22649
+ await executeAndShowResult(input, command, assistantMsg.id);
21288
22650
  }
21289
22651
  } catch (error) {
22652
+ chatScrollBox.remove(`msg-${loadingMsg.id}`);
22653
+ chatMessages = chatMessages.filter((m) => m.id !== loadingMsg.id);
21290
22654
  const message = error instanceof Error ? error.message : String(error);
21291
- setOutput(t`${fg("#ef4444")(`Error: ${message}`)}`);
22655
+ addSystemMessage(`Error: ${message}`);
21292
22656
  }
21293
22657
  }
21294
- async function processCommand(input, command) {
22658
+ async function processDirectCommand(input, command) {
22659
+ const safety = analyzeCommand(command, config);
22660
+ const assistantMsg = addAssistantMessage(input, command, safety);
22661
+ if (safety.isDangerous) {
22662
+ pendingMessageId = assistantMsg.id;
22663
+ awaitingConfirmation = true;
22664
+ helpBarText.content = getHelpBarContent();
22665
+ } else {
22666
+ await executeAndShowResult(input, command, assistantMsg.id);
22667
+ }
22668
+ }
22669
+ async function executeAndShowResult(input, command, assistantMsgId) {
21295
22670
  if (command.startsWith("cd ")) {
21296
22671
  const path2 = command.slice(3).trim().replace(/^["']|["']$/g, "");
21297
22672
  try {
21298
22673
  const expandedPath = path2.startsWith("~") ? path2.replace("~", process.env.HOME || "") : path2;
21299
22674
  process.chdir(expandedPath);
21300
22675
  currentCwd = getCwd();
21301
- cwdText.content = t`${fg("#64748b")("cwd:")} ${fg("#22c55e")(currentCwd)}`;
21302
- setOutput(t`${fg("#22c55e")(`Changed directory to ${currentCwd}`)}`);
22676
+ statusBarText.content = getStatusBarContent();
22677
+ addResultMessage(`Changed directory to ${currentCwd}`, 0);
21303
22678
  addToHistory({
21304
22679
  input,
21305
22680
  command,
@@ -21307,35 +22682,37 @@ async function processCommand(input, command) {
21307
22682
  timestamp: Date.now()
21308
22683
  });
21309
22684
  history = loadHistory();
22685
+ updateAssistantMessage(assistantMsgId, { executed: true });
21310
22686
  } catch (err) {
21311
- setOutput(t`${fg("#ef4444")(`cd: ${err instanceof Error ? err.message : String(err)}`)}`);
22687
+ addResultMessage(`cd: ${err instanceof Error ? err.message : String(err)}`, 1);
21312
22688
  }
21313
22689
  clearCommandState();
21314
22690
  return;
21315
22691
  }
21316
22692
  if (dryRunMode) {
21317
- setOutput(t`${fg("#fbbf24")("[DRY RUN]")} Would execute: ${fg("#f8fafc")(command)}`);
22693
+ addResultMessage(`[DRY RUN] Would execute: ${command}`, 0);
22694
+ updateAssistantMessage(assistantMsgId, { executed: true });
21318
22695
  clearCommandState();
21319
22696
  return;
21320
22697
  }
21321
- setOutput(t`${fg("#64748b")("Executing...")}`);
21322
22698
  try {
21323
- const result = await executeCommand(command);
21324
- setOutput(result || t`${fg("#22c55e")("Command completed successfully")}`);
22699
+ const { output, exitCode } = await executeCommandWithCode(command);
22700
+ addResultMessage(output || "Command completed successfully", exitCode);
21325
22701
  addToHistory({
21326
22702
  input,
21327
22703
  command,
21328
- output: result.slice(0, 500),
22704
+ output: output.slice(0, 500),
21329
22705
  timestamp: Date.now()
21330
22706
  });
21331
22707
  history = loadHistory();
22708
+ updateAssistantMessage(assistantMsgId, { executed: true });
21332
22709
  } catch (error) {
21333
22710
  const message = error instanceof Error ? error.message : String(error);
21334
- setOutput(t`${fg("#ef4444")(`Error: ${message}`)}`);
22711
+ addResultMessage(`Error: ${message}`, 1);
21335
22712
  }
21336
22713
  clearCommandState();
21337
22714
  }
21338
- function executeCommand(command) {
22715
+ function executeCommandWithCode(command) {
21339
22716
  return new Promise((resolve3, reject) => {
21340
22717
  const child = spawn(command, {
21341
22718
  shell: true,
@@ -21354,23 +22731,16 @@ function executeCommand(command) {
21354
22731
  reject(error);
21355
22732
  });
21356
22733
  child.on("close", (code) => {
21357
- if (code === 0) {
21358
- resolve3(stdout || stderr);
21359
- } else {
21360
- resolve3(stderr || stdout || `Command exited with code ${code}`);
21361
- }
22734
+ const exitCode = code ?? 0;
22735
+ const output = stdout || stderr || (exitCode === 0 ? "" : `Command exited with code ${exitCode}`);
22736
+ resolve3({ output, exitCode });
21362
22737
  });
21363
22738
  });
21364
22739
  }
21365
22740
  function clearCommandState() {
21366
- pendingCommand = null;
22741
+ pendingMessageId = null;
21367
22742
  awaitingConfirmation = false;
21368
- confirmPrompt.visible = false;
21369
- commandPreview.content = "";
21370
- safetyWarning.content = "";
21371
- }
21372
- function setOutput(content) {
21373
- outputText.content = content;
22743
+ helpBarText.content = getHelpBarContent();
21374
22744
  }
21375
22745
  async function handleSpecialCommand(input) {
21376
22746
  const cmd = input.slice(1).toLowerCase().trim();
@@ -21386,8 +22756,8 @@ async function handleSpecialCommand(input) {
21386
22756
  break;
21387
22757
  case "dry":
21388
22758
  dryRunMode = !dryRunMode;
21389
- statusText.content = getDryRunStatus();
21390
- setOutput(t`${fg("#22c55e")(`Dry-run mode: ${dryRunMode ? "ON" : "OFF"}`)}`);
22759
+ statusBarText.content = getStatusBarContent();
22760
+ addSystemMessage(`Dry-run mode: ${dryRunMode ? "ON" : "OFF"}`);
21391
22761
  break;
21392
22762
  case "config":
21393
22763
  await showConfig();
@@ -21396,65 +22766,72 @@ async function handleSpecialCommand(input) {
21396
22766
  showHistory();
21397
22767
  break;
21398
22768
  case "clear":
21399
- setOutput("");
22769
+ clearChat();
21400
22770
  break;
21401
22771
  default:
21402
22772
  if (cmd) {
21403
- await processCommand(input, cmd);
22773
+ addUserMessage(cmd);
22774
+ await processDirectCommand(input, cmd);
21404
22775
  }
21405
22776
  }
21406
22777
  }
22778
+ function clearChat() {
22779
+ for (const msg of chatMessages) {
22780
+ chatScrollBox.remove(`msg-${msg.id}`);
22781
+ }
22782
+ chatMessages = [];
22783
+ addSystemMessage(getWelcomeMessage());
22784
+ }
21407
22785
  function showHelp() {
21408
- const theme = getTheme();
21409
- setOutput(t`${bold(fg(theme.colors.primary)("Magic Shell"))}
21410
-
21411
- ${bold(fg(theme.colors.textMuted)("Keyboard Shortcuts (Ctrl+X then...):"))}
21412
- ${fg(theme.colors.primary)("P")} ${fg(theme.colors.textMuted)("Command palette")} ${fg(theme.colors.primary)("M")} ${fg(theme.colors.textMuted)("Change model")}
21413
- ${fg(theme.colors.primary)("S")} ${fg(theme.colors.textMuted)("Switch provider")} ${fg(theme.colors.primary)("D")} ${fg(theme.colors.textMuted)("Toggle dry-run")}
21414
- ${fg(theme.colors.primary)("T")} ${fg(theme.colors.textMuted)("Change theme")} ${fg(theme.colors.primary)("H")} ${fg(theme.colors.textMuted)("Show history")}
21415
- ${fg(theme.colors.primary)("C")} ${fg(theme.colors.textMuted)("Show config")} ${fg(theme.colors.primary)("L")} ${fg(theme.colors.textMuted)("Clear output")}
21416
- ${fg(theme.colors.primary)("?")} ${fg(theme.colors.textMuted)("This help")} ${fg(theme.colors.primary)("Q")} ${fg(theme.colors.textMuted)("Exit")}
22786
+ const helpText = `Keyboard Shortcuts (Ctrl+X then...):
22787
+ P Command palette M Change model
22788
+ S Switch provider D Toggle dry-run
22789
+ T Change theme H Show history
22790
+ C Show config L Clear chat
22791
+ ? This help Q Exit
21417
22792
 
21418
- ${bold(fg(theme.colors.textMuted)("Other:"))}
21419
- ${fg(theme.colors.primary)("Ctrl+C")} ${fg(theme.colors.textMuted)("Exit / Cancel")} ${fg(theme.colors.primary)("Esc")} ${fg(theme.colors.textMuted)("Close palette")}
22793
+ Other:
22794
+ Ctrl+C Exit / Cancel Esc Close palette
21420
22795
 
21421
- ${bold(fg(theme.colors.textMuted)("Tips:"))}
22796
+ Tips:
21422
22797
  - Type naturally: "list all files" -> ls -la
21423
22798
  - Reference history: "do that again", "undo"
21424
- - ${fg(theme.colors.success)("Free models:")} gpt-5-nano, grok-code, glm-4.7-free`);
22799
+ - Free models: gpt-5-nano, grok-code, glm-4.7-free`;
22800
+ addSystemMessage(helpText);
21425
22801
  }
21426
22802
  async function showConfig() {
21427
22803
  const theme = getTheme();
21428
22804
  const providerName = config.provider === "opencode-zen" ? "OpenCode Zen" : "OpenRouter";
21429
22805
  const apiKey = await getApiKey(config.provider);
21430
- const apiKeyStatus = apiKey ? fg(theme.colors.success)("configured") : fg(theme.colors.error)("not set");
21431
- const freeBadge = currentModel.free ? fg(theme.colors.success)(" (FREE)") : "";
22806
+ const apiKeyStatus = apiKey ? "configured" : "not set";
22807
+ const freeBadge = currentModel.free ? " (FREE)" : "";
21432
22808
  const shellInfo = getShellInfo();
21433
- setOutput(t`${bold(fg(theme.colors.primary)("Current Configuration"))}
22809
+ const configText = `Current Configuration
21434
22810
 
21435
- ${fg(theme.colors.textMuted)("Provider:")} ${fg(theme.colors.text)(providerName)}
21436
- ${fg(theme.colors.textMuted)("Model:")} ${fg(theme.colors.text)(currentModel.name)}${freeBadge}
21437
- ${fg(theme.colors.textMuted)("Model ID:")} ${fg(theme.colors.textMuted)(currentModel.id)}
21438
- ${fg(theme.colors.textMuted)("Category:")} ${fg(theme.colors.text)(currentModel.category)}
21439
- ${fg(theme.colors.textMuted)("Theme:")} ${fg(theme.colors.text)(theme.name)}
21440
- ${fg(theme.colors.textMuted)("Shell:")} ${fg(theme.colors.text)(shellInfo.shell)} ${fg(theme.colors.textMuted)(`(${shellInfo.shellPath})`)}
21441
- ${fg(theme.colors.textMuted)("Platform:")} ${fg(theme.colors.text)(shellInfo.platform)}${shellInfo.isWSL ? fg(theme.colors.textMuted)(" (WSL)") : ""}
21442
- ${fg(theme.colors.textMuted)("Safety:")} ${fg(theme.colors.text)(config.safetyLevel)}
21443
- ${fg(theme.colors.textMuted)("Dry-run:")} ${fg(theme.colors.text)(dryRunMode ? "ON" : "OFF")}
21444
- ${fg(theme.colors.textMuted)("API Key:")} ${apiKeyStatus}
21445
- ${fg(theme.colors.textMuted)("History:")} ${fg(theme.colors.text)(`${history.length} commands`)}`);
22811
+ Provider: ${providerName}
22812
+ Model: ${currentModel.name}${freeBadge}
22813
+ Model ID: ${currentModel.id}
22814
+ Category: ${currentModel.category}
22815
+ Theme: ${theme.name}
22816
+ Shell: ${shellInfo.shell} (${shellInfo.shellPath})
22817
+ Platform: ${shellInfo.platform}${shellInfo.isWSL ? " (WSL)" : ""}
22818
+ Safety: ${config.safetyLevel}
22819
+ Dry-run: ${dryRunMode ? "ON" : "OFF"}
22820
+ API Key: ${apiKeyStatus}
22821
+ History: ${history.length} commands`;
22822
+ addSystemMessage(configText);
21446
22823
  }
21447
22824
  function showHistory() {
21448
22825
  if (history.length === 0) {
21449
- setOutput(t`${fg("#64748b")("No command history yet.")}`);
22826
+ addSystemMessage("No command history yet.");
21450
22827
  return;
21451
22828
  }
21452
22829
  const recent = history.slice(-10);
21453
22830
  const lines = recent.map((entry, i) => {
21454
22831
  const date = new Date(entry.timestamp).toLocaleTimeString();
21455
- return t`${fg("#64748b")(`${i + 1}.`)} ${fg("#94a3b8")(`[${date}]`)} ${fg("#f8fafc")(entry.command)}`;
22832
+ return `${i + 1}. [${date}] ${entry.command}`;
21456
22833
  });
21457
- setOutput(t`${bold(fg("#60a5fa")("Recent Command History"))}
22834
+ addSystemMessage(`Recent Command History
21458
22835
 
21459
22836
  ${lines.join(`
21460
22837
  `)}`);
@@ -21520,10 +22897,10 @@ async function switchProvider() {
21520
22897
  currentModel = models.find((m) => m.id === config.defaultModel) || models[0];
21521
22898
  config.defaultModel = currentModel.id;
21522
22899
  saveConfig(config);
21523
- modelText.content = getModelDisplay();
22900
+ statusBarText.content = getStatusBarContent();
21524
22901
  closeSelector();
21525
22902
  const providerName = newProvider === "opencode-zen" ? "OpenCode Zen" : "OpenRouter";
21526
- setOutput(t`${fg("#22c55e")(`Switched to ${providerName}. Model: ${currentModel.name}`)}`);
22903
+ addSystemMessage(`Switched to ${providerName}. Model: ${currentModel.name}`);
21527
22904
  } else {
21528
22905
  closeSelector();
21529
22906
  renderer.root.remove("main-container");
@@ -21589,12 +22966,12 @@ function showModelSelector() {
21589
22966
  currentModel = option.value;
21590
22967
  config.defaultModel = currentModel.id;
21591
22968
  saveConfig(config);
21592
- modelText.content = getModelDisplay();
22969
+ statusBarText.content = getStatusBarContent();
21593
22970
  renderer.root.remove("model-selector-container");
21594
22971
  modelSelector = null;
21595
22972
  inputField.focus();
21596
22973
  const freeBadge = currentModel.free ? " (FREE)" : "";
21597
- setOutput(t`${fg("#22c55e")(`Model changed to ${currentModel.name}${freeBadge}`)}`);
22974
+ addSystemMessage(`Model changed to ${currentModel.name}${freeBadge}`);
21598
22975
  });
21599
22976
  modelSelector.focus();
21600
22977
  }
@@ -21652,8 +23029,7 @@ function showThemeSelector() {
21652
23029
  renderer.root.remove("theme-selector-container");
21653
23030
  themeSelector = null;
21654
23031
  refreshThemeColors();
21655
- const newTheme = getTheme();
21656
- setOutput(t`${fg(newTheme.colors.success)(`Theme changed to ${themeName}`)}`);
23032
+ addSystemMessage(`Theme changed to ${themeName}`);
21657
23033
  inputField.focus();
21658
23034
  });
21659
23035
  const escHandler = (key) => {
@@ -21699,8 +23075,8 @@ function getCommandPaletteOptions() {
21699
23075
  chord: "d",
21700
23076
  action: () => {
21701
23077
  dryRunMode = !dryRunMode;
21702
- statusText.content = getDryRunStatus();
21703
- setOutput(t`${fg("#22c55e")(`Dry-run mode: ${dryRunMode ? "ON" : "OFF"}`)}`);
23078
+ statusBarText.content = getStatusBarContent();
23079
+ addSystemMessage(`Dry-run mode: ${dryRunMode ? "ON" : "OFF"}`);
21704
23080
  }
21705
23081
  },
21706
23082
  {
@@ -21725,11 +23101,11 @@ function getCommandPaletteOptions() {
21725
23101
  action: () => showThemeSelector()
21726
23102
  },
21727
23103
  {
21728
- name: "Clear Output",
21729
- description: "Clear the output area",
23104
+ name: "Clear Chat",
23105
+ description: "Clear the chat history",
21730
23106
  key: "l",
21731
23107
  chord: "l",
21732
- action: () => setOutput("")
23108
+ action: () => clearChat()
21733
23109
  },
21734
23110
  {
21735
23111
  name: "Show Help",
@@ -21870,21 +23246,41 @@ function handleKeypress(key) {
21870
23246
  inputField.focus();
21871
23247
  return;
21872
23248
  }
21873
- if (awaitingConfirmation) {
23249
+ if (awaitingConfirmation && pendingMessageId) {
21874
23250
  clearCommandState();
21875
- setOutput(t`${fg("#64748b")("Command cancelled.")}`);
23251
+ addSystemMessage("Command cancelled.");
21876
23252
  inputField.focus();
21877
23253
  }
21878
23254
  }
21879
- if (key.name === "return" && awaitingConfirmation && pendingCommand) {
21880
- const cmd = pendingCommand;
21881
- clearCommandState();
21882
- processCommand("", cmd);
23255
+ if (key.name === "return" && awaitingConfirmation && pendingMessageId) {
23256
+ const msg = chatMessages.find((m) => m.id === pendingMessageId);
23257
+ if (msg && msg.command) {
23258
+ const command = msg.command;
23259
+ const msgId = pendingMessageId;
23260
+ clearCommandState();
23261
+ executeAndShowResult(msg.content, command, msgId);
23262
+ }
21883
23263
  }
21884
- if (key.name === "e" && awaitingConfirmation && pendingCommand) {
21885
- inputField.value = pendingCommand;
21886
- clearCommandState();
21887
- inputField.focus();
23264
+ if (key.name === "e" && awaitingConfirmation && pendingMessageId) {
23265
+ const msg = chatMessages.find((m) => m.id === pendingMessageId);
23266
+ if (msg && msg.command) {
23267
+ inputField.value = msg.command;
23268
+ clearCommandState();
23269
+ inputField.focus();
23270
+ }
23271
+ }
23272
+ if (key.name === "c" && awaitingConfirmation && pendingMessageId) {
23273
+ const msg = chatMessages.find((m) => m.id === pendingMessageId);
23274
+ if (msg && msg.command) {
23275
+ const copyCmd = process.platform === "darwin" ? "pbcopy" : "xclip -selection clipboard";
23276
+ const child = spawn(copyCmd, { shell: true });
23277
+ child.stdin?.write(msg.command);
23278
+ child.stdin?.end();
23279
+ addSystemMessage(`Copied to clipboard: ${msg.command}`);
23280
+ }
23281
+ }
23282
+ if (key.name === "o" && !awaitingConfirmation && !commandPalette && !modelSelector) {
23283
+ toggleLastResultExpand();
21888
23284
  }
21889
23285
  }
21890
23286
  if (__require.main == __require.module) {