@austinthesing/magic-shell 0.1.2 → 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.
- package/dist/cli.js +1582 -186
- package/dist/index.js +1580 -180
- 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
|
|
20883
|
-
var
|
|
22031
|
+
var statusBarText;
|
|
22032
|
+
var chatScrollBox;
|
|
20884
22033
|
var inputField;
|
|
20885
|
-
var
|
|
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
|
|
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"))}
|
|
22225
|
+
content: t`${bold(fg(theme.colors.primary)("magic-shell"))}`,
|
|
21078
22226
|
flexGrow: 1
|
|
21079
22227
|
});
|
|
21080
22228
|
headerRow.add(headerText);
|
|
21081
|
-
const
|
|
21082
|
-
id: "
|
|
21083
|
-
|
|
21084
|
-
width: "100%",
|
|
21085
|
-
marginBottom: 1
|
|
22229
|
+
const modelBadge = new TextRenderable(renderer, {
|
|
22230
|
+
id: "model-badge",
|
|
22231
|
+
content: getModelDisplay()
|
|
21086
22232
|
});
|
|
21087
|
-
|
|
21088
|
-
|
|
21089
|
-
id: "
|
|
21090
|
-
content:
|
|
21091
|
-
|
|
22233
|
+
headerRow.add(modelBadge);
|
|
22234
|
+
statusBarText = new TextRenderable(renderer, {
|
|
22235
|
+
id: "status-bar-text",
|
|
22236
|
+
content: getStatusBarContent(),
|
|
22237
|
+
marginBottom: 1
|
|
21092
22238
|
});
|
|
21093
|
-
|
|
21094
|
-
|
|
21095
|
-
id: "
|
|
21096
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
21109
|
-
width:
|
|
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
|
-
|
|
21128
|
-
id: "
|
|
21129
|
-
content:
|
|
21130
|
-
|
|
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(
|
|
21138
|
-
|
|
21139
|
-
|
|
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
|
-
|
|
21142
|
-
marginBottom: 1
|
|
22395
|
+
width: "100%"
|
|
21143
22396
|
});
|
|
21144
|
-
|
|
21145
|
-
|
|
21146
|
-
|
|
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
|
-
|
|
21150
|
-
|
|
21151
|
-
|
|
21152
|
-
|
|
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
|
-
|
|
21157
|
-
|
|
22413
|
+
paddingLeft: 1,
|
|
22414
|
+
paddingRight: 1,
|
|
22415
|
+
paddingTop: 0,
|
|
22416
|
+
paddingBottom: 0,
|
|
22417
|
+
backgroundColor: theme.colors.backgroundPanel
|
|
21158
22418
|
});
|
|
21159
|
-
|
|
21160
|
-
|
|
21161
|
-
|
|
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
|
-
|
|
21170
|
-
|
|
21171
|
-
|
|
21172
|
-
|
|
21173
|
-
|
|
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
|
-
|
|
21176
|
-
|
|
21177
|
-
|
|
21178
|
-
|
|
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"))}
|
|
22569
|
+
headerText.content = t`${bold(fg(theme.colors.primary)("magic-shell"))}`;
|
|
21199
22570
|
}
|
|
21200
|
-
if (
|
|
21201
|
-
|
|
22571
|
+
if (statusBarText) {
|
|
22572
|
+
statusBarText.content = getStatusBarContent();
|
|
21202
22573
|
}
|
|
21203
|
-
if (
|
|
21204
|
-
|
|
22574
|
+
if (helpBarText) {
|
|
22575
|
+
helpBarText.content = getHelpBarContent();
|
|
21205
22576
|
}
|
|
21206
|
-
if (
|
|
21207
|
-
|
|
21208
|
-
|
|
21209
|
-
|
|
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
|
|
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
|
-
|
|
22634
|
+
addSystemMessage("Error: No API key configured. Run !provider to set up.");
|
|
21272
22635
|
return;
|
|
21273
22636
|
}
|
|
21274
|
-
|
|
22637
|
+
const loadingMsg = addSystemMessage("Translating...");
|
|
21275
22638
|
try {
|
|
21276
22639
|
const command = await translateToCommand(apiKey, currentModel, input, currentCwd, history);
|
|
21277
|
-
|
|
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
|
-
|
|
21281
|
-
pendingCommand = command;
|
|
22645
|
+
pendingMessageId = assistantMsg.id;
|
|
21282
22646
|
awaitingConfirmation = true;
|
|
21283
|
-
|
|
21284
|
-
setOutput(t`${fg("#fbbf24")("Command requires confirmation. Press Enter to execute or Esc to cancel.")}`);
|
|
22647
|
+
helpBarText.content = getHelpBarContent();
|
|
21285
22648
|
} else {
|
|
21286
|
-
|
|
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
|
-
|
|
22655
|
+
addSystemMessage(`Error: ${message}`);
|
|
21292
22656
|
}
|
|
21293
22657
|
}
|
|
21294
|
-
async function
|
|
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
|
-
|
|
21302
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
21324
|
-
|
|
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:
|
|
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
|
-
|
|
22711
|
+
addResultMessage(`Error: ${message}`, 1);
|
|
21335
22712
|
}
|
|
21336
22713
|
clearCommandState();
|
|
21337
22714
|
}
|
|
21338
|
-
function
|
|
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
|
-
|
|
21358
|
-
|
|
21359
|
-
|
|
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
|
-
|
|
22741
|
+
pendingMessageId = null;
|
|
21367
22742
|
awaitingConfirmation = false;
|
|
21368
|
-
|
|
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
|
-
|
|
21390
|
-
|
|
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
|
-
|
|
22769
|
+
clearChat();
|
|
21400
22770
|
break;
|
|
21401
22771
|
default:
|
|
21402
22772
|
if (cmd) {
|
|
21403
|
-
|
|
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
|
|
21409
|
-
|
|
21410
|
-
|
|
21411
|
-
|
|
21412
|
-
|
|
21413
|
-
|
|
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
|
-
|
|
21419
|
-
|
|
22793
|
+
Other:
|
|
22794
|
+
Ctrl+C Exit / Cancel Esc Close palette
|
|
21420
22795
|
|
|
21421
|
-
|
|
22796
|
+
Tips:
|
|
21422
22797
|
- Type naturally: "list all files" -> ls -la
|
|
21423
22798
|
- Reference history: "do that again", "undo"
|
|
21424
|
-
-
|
|
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 ?
|
|
21431
|
-
const freeBadge = currentModel.free ?
|
|
22806
|
+
const apiKeyStatus = apiKey ? "configured" : "not set";
|
|
22807
|
+
const freeBadge = currentModel.free ? " (FREE)" : "";
|
|
21432
22808
|
const shellInfo = getShellInfo();
|
|
21433
|
-
|
|
22809
|
+
const configText = `Current Configuration
|
|
21434
22810
|
|
|
21435
|
-
|
|
21436
|
-
|
|
21437
|
-
|
|
21438
|
-
|
|
21439
|
-
|
|
21440
|
-
|
|
21441
|
-
|
|
21442
|
-
|
|
21443
|
-
|
|
21444
|
-
|
|
21445
|
-
|
|
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
|
-
|
|
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
|
|
22832
|
+
return `${i + 1}. [${date}] ${entry.command}`;
|
|
21456
22833
|
});
|
|
21457
|
-
|
|
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
|
-
|
|
22900
|
+
statusBarText.content = getStatusBarContent();
|
|
21524
22901
|
closeSelector();
|
|
21525
22902
|
const providerName = newProvider === "opencode-zen" ? "OpenCode Zen" : "OpenRouter";
|
|
21526
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21703
|
-
|
|
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
|
|
21729
|
-
description: "Clear the
|
|
23104
|
+
name: "Clear Chat",
|
|
23105
|
+
description: "Clear the chat history",
|
|
21730
23106
|
key: "l",
|
|
21731
23107
|
chord: "l",
|
|
21732
|
-
action: () =>
|
|
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
|
-
|
|
23251
|
+
addSystemMessage("Command cancelled.");
|
|
21876
23252
|
inputField.focus();
|
|
21877
23253
|
}
|
|
21878
23254
|
}
|
|
21879
|
-
if (key.name === "return" && awaitingConfirmation &&
|
|
21880
|
-
const
|
|
21881
|
-
|
|
21882
|
-
|
|
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 &&
|
|
21885
|
-
|
|
21886
|
-
|
|
21887
|
-
|
|
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) {
|