erosolar-cli 1.7.185 → 1.7.187
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/core/toolPreconditions.d.ts.map +1 -1
- package/dist/core/toolPreconditions.js +24 -10
- package/dist/core/toolPreconditions.js.map +1 -1
- package/dist/mcp/config.d.ts.map +1 -1
- package/dist/mcp/config.js +56 -7
- package/dist/mcp/config.js.map +1 -1
- package/dist/mcp/sseClient.d.ts +34 -0
- package/dist/mcp/sseClient.d.ts.map +1 -0
- package/dist/mcp/sseClient.js +299 -0
- package/dist/mcp/sseClient.js.map +1 -0
- package/dist/mcp/stdioClient.d.ts +2 -2
- package/dist/mcp/stdioClient.d.ts.map +1 -1
- package/dist/mcp/stdioClient.js.map +1 -1
- package/dist/mcp/toolBridge.d.ts +1 -0
- package/dist/mcp/toolBridge.d.ts.map +1 -1
- package/dist/mcp/toolBridge.js +13 -2
- package/dist/mcp/toolBridge.js.map +1 -1
- package/dist/mcp/types.d.ts +13 -5
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +6 -4
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/ui/persistentPrompt.d.ts +18 -20
- package/dist/ui/persistentPrompt.d.ts.map +1 -1
- package/dist/ui/persistentPrompt.js +171 -192
- package/dist/ui/persistentPrompt.js.map +1 -1
- package/package.json +1 -1
|
@@ -317,6 +317,8 @@ export class PinnedChatBox {
|
|
|
317
317
|
enableScrollRegion() {
|
|
318
318
|
if (this.scrollRegionActive || !this.supportsRendering())
|
|
319
319
|
return;
|
|
320
|
+
// Make sure reserved height matches current content/terminal width
|
|
321
|
+
this.updateReservedLinesForContent();
|
|
320
322
|
const rows = this.writeStream.rows || 24;
|
|
321
323
|
const scrollBottom = rows - this.reservedLines;
|
|
322
324
|
// Step 1: Set scroll region (excludes bottom reserved lines)
|
|
@@ -415,8 +417,7 @@ export class PinnedChatBox {
|
|
|
415
417
|
return;
|
|
416
418
|
this.isRendering = true;
|
|
417
419
|
try {
|
|
418
|
-
const rows = this.
|
|
419
|
-
const cols = Math.max(this.writeStream.columns || 80, 40);
|
|
420
|
+
const { rows, cols, maxInputWidth } = this.getRenderDimensions();
|
|
420
421
|
// ANSI codes - keep simple, no theme functions
|
|
421
422
|
const HIDE_CURSOR = '\x1b[?25l';
|
|
422
423
|
const SHOW_CURSOR = '\x1b[?25h';
|
|
@@ -426,38 +427,19 @@ export class PinnedChatBox {
|
|
|
426
427
|
// Hide cursor during render
|
|
427
428
|
this.safeWrite(HIDE_CURSOR);
|
|
428
429
|
this.safeWrite(RESET);
|
|
429
|
-
//
|
|
430
|
-
const
|
|
431
|
-
const totalInputLines =
|
|
430
|
+
// Wrap input into terminal-safe lines so long text doesn't overflow
|
|
431
|
+
const { lines: wrappedLines, cursorLine: cursorLineIndex, cursorCol: cursorColInLine } = this.wrapInputBuffer(maxInputWidth);
|
|
432
|
+
const totalInputLines = Math.max(1, wrappedLines.length);
|
|
432
433
|
const displayLineCount = Math.min(totalInputLines, this.maxDisplayLines);
|
|
434
|
+
// Keep reserved height in sync with what we need to show
|
|
435
|
+
this.updateReservedLinesForContent(maxInputWidth, totalInputLines);
|
|
433
436
|
// Find cursor position: which line and column
|
|
434
|
-
let cursorLineIndex = 0;
|
|
435
|
-
let cursorColInLine = 0;
|
|
436
|
-
let charCount = 0;
|
|
437
|
-
for (let i = 0; i < inputLines.length; i++) {
|
|
438
|
-
const currentLine = inputLines[i] ?? '';
|
|
439
|
-
const lineLen = currentLine.length;
|
|
440
|
-
if (charCount + lineLen >= this.cursorPosition) {
|
|
441
|
-
cursorLineIndex = i;
|
|
442
|
-
cursorColInLine = this.cursorPosition - charCount;
|
|
443
|
-
break;
|
|
444
|
-
}
|
|
445
|
-
charCount += lineLen + 1; // +1 for newline
|
|
446
|
-
if (i === inputLines.length - 1) {
|
|
447
|
-
cursorLineIndex = i;
|
|
448
|
-
cursorColInLine = currentLine.length;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
// Calculate display window to keep cursor visible
|
|
452
437
|
let displayStartLine = 0;
|
|
453
438
|
if (totalInputLines > displayLineCount) {
|
|
454
|
-
|
|
455
|
-
displayStartLine = cursorLineIndex - displayLineCount + 2;
|
|
456
|
-
}
|
|
439
|
+
displayStartLine = Math.max(0, cursorLineIndex - displayLineCount + 1);
|
|
457
440
|
displayStartLine = Math.min(displayStartLine, totalInputLines - displayLineCount);
|
|
458
|
-
displayStartLine = Math.max(0, displayStartLine);
|
|
459
441
|
}
|
|
460
|
-
const linesToShow =
|
|
442
|
+
const linesToShow = wrappedLines.slice(displayStartLine, displayStartLine + displayLineCount);
|
|
461
443
|
// Move to the start of reserved area
|
|
462
444
|
const reservedStart = rows - this.reservedLines + 1;
|
|
463
445
|
this.safeWrite(ANSI.CURSOR_TO_BOTTOM(reservedStart));
|
|
@@ -481,7 +463,6 @@ export class PinnedChatBox {
|
|
|
481
463
|
this.safeWrite(' ' + hints.join(' • '));
|
|
482
464
|
this.safeWrite(RESET);
|
|
483
465
|
}
|
|
484
|
-
const maxInputWidth = cols - 5;
|
|
485
466
|
let finalCursorRow = reservedStart + 1;
|
|
486
467
|
let finalCursorCol = 3;
|
|
487
468
|
// Render each input line
|
|
@@ -497,21 +478,12 @@ export class PinnedChatBox {
|
|
|
497
478
|
this.safeWrite(isFirstLine ? '>' : '│');
|
|
498
479
|
this.safeWrite(RESET);
|
|
499
480
|
this.safeWrite(' ');
|
|
500
|
-
// Handle long lines - scroll horizontally to keep cursor visible
|
|
501
|
-
let displayStart = 0;
|
|
502
|
-
let displayLine = line;
|
|
503
|
-
if (line.length > maxInputWidth) {
|
|
504
|
-
if (isCursorLine && cursorColInLine > maxInputWidth - 5) {
|
|
505
|
-
displayStart = cursorColInLine - maxInputWidth + 5;
|
|
506
|
-
}
|
|
507
|
-
displayLine = line.slice(displayStart, displayStart + maxInputWidth);
|
|
508
|
-
}
|
|
509
481
|
if (isCursorLine) {
|
|
510
482
|
// Render line with cursor
|
|
511
|
-
const displayCursorCol = Math.max(0, cursorColInLine
|
|
512
|
-
const beforeCursor =
|
|
513
|
-
const atCursor =
|
|
514
|
-
const afterCursor =
|
|
483
|
+
const displayCursorCol = Math.max(0, cursorColInLine);
|
|
484
|
+
const beforeCursor = line.slice(0, displayCursorCol);
|
|
485
|
+
const atCursor = line[displayCursorCol] ?? ' ';
|
|
486
|
+
const afterCursor = line.slice(displayCursorCol + 1);
|
|
515
487
|
this.safeWrite(beforeCursor);
|
|
516
488
|
this.safeWrite(REVERSE_VIDEO);
|
|
517
489
|
this.safeWrite(atCursor);
|
|
@@ -523,7 +495,7 @@ export class PinnedChatBox {
|
|
|
523
495
|
}
|
|
524
496
|
else {
|
|
525
497
|
// Render line without cursor
|
|
526
|
-
this.safeWrite(
|
|
498
|
+
this.safeWrite(line);
|
|
527
499
|
}
|
|
528
500
|
}
|
|
529
501
|
// Position terminal cursor and show it
|
|
@@ -594,10 +566,13 @@ export class PinnedChatBox {
|
|
|
594
566
|
return withoutAnsi.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
|
|
595
567
|
}
|
|
596
568
|
/**
|
|
597
|
-
* Sanitize queued command text (trimmed,
|
|
569
|
+
* Sanitize queued command text (trimmed, preserves newlines)
|
|
598
570
|
*/
|
|
599
571
|
sanitizeCommandText(text) {
|
|
600
|
-
|
|
572
|
+
if (!text)
|
|
573
|
+
return '';
|
|
574
|
+
const clean = this.sanitizeMultilineForDisplay(text);
|
|
575
|
+
return clean.slice(0, this.maxInputLength).trim();
|
|
601
576
|
}
|
|
602
577
|
/**
|
|
603
578
|
* Sanitize status message (single line, bounded length)
|
|
@@ -630,6 +605,12 @@ export class PinnedChatBox {
|
|
|
630
605
|
this.updateReservedLinesForContent();
|
|
631
606
|
this.scheduleRender();
|
|
632
607
|
}
|
|
608
|
+
/**
|
|
609
|
+
* Treat spaces, tabs, and newlines as whitespace for navigation helpers.
|
|
610
|
+
*/
|
|
611
|
+
isWhitespace(char) {
|
|
612
|
+
return char === ' ' || char === '\t' || char === '\n';
|
|
613
|
+
}
|
|
633
614
|
/**
|
|
634
615
|
* Update cursor position while keeping it in bounds and re-rendering.
|
|
635
616
|
*/
|
|
@@ -637,51 +618,6 @@ export class PinnedChatBox {
|
|
|
637
618
|
this.cursorPosition = Math.max(0, Math.min(position, this.inputBuffer.length));
|
|
638
619
|
this.scheduleRender();
|
|
639
620
|
}
|
|
640
|
-
/**
|
|
641
|
-
* Convert the current cursor index into line/column info.
|
|
642
|
-
*/
|
|
643
|
-
getCursorLineInfo() {
|
|
644
|
-
const lines = this.inputBuffer.split('\n');
|
|
645
|
-
let remaining = this.cursorPosition;
|
|
646
|
-
for (let i = 0; i < lines.length; i++) {
|
|
647
|
-
const line = lines[i] ?? '';
|
|
648
|
-
if (remaining <= line.length) {
|
|
649
|
-
return { lines, lineIndex: i, column: remaining };
|
|
650
|
-
}
|
|
651
|
-
remaining -= line.length + 1; // account for newline
|
|
652
|
-
}
|
|
653
|
-
// Fallback to last line
|
|
654
|
-
const lastLine = Math.max(0, lines.length - 1);
|
|
655
|
-
return { lines, lineIndex: lastLine, column: (lines[lastLine] ?? '').length };
|
|
656
|
-
}
|
|
657
|
-
/**
|
|
658
|
-
* Convert line/column back to a single cursor index.
|
|
659
|
-
*/
|
|
660
|
-
positionFromLineCol(lines, lineIndex, column) {
|
|
661
|
-
let pos = 0;
|
|
662
|
-
for (let i = 0; i < lineIndex; i++) {
|
|
663
|
-
pos += (lines[i] ?? '').length + 1; // +1 for newline
|
|
664
|
-
}
|
|
665
|
-
return pos + column;
|
|
666
|
-
}
|
|
667
|
-
/**
|
|
668
|
-
* Move cursor vertically within a multi-line buffer.
|
|
669
|
-
* Returns true if a movement occurred.
|
|
670
|
-
*/
|
|
671
|
-
moveCursorVertical(direction) {
|
|
672
|
-
const info = this.getCursorLineInfo();
|
|
673
|
-
if (info.lines.length <= 1) {
|
|
674
|
-
return false;
|
|
675
|
-
}
|
|
676
|
-
const targetLine = info.lineIndex + direction;
|
|
677
|
-
if (targetLine < 0 || targetLine >= info.lines.length) {
|
|
678
|
-
return false;
|
|
679
|
-
}
|
|
680
|
-
const targetCol = Math.min(info.column, (info.lines[targetLine] ?? '').length);
|
|
681
|
-
const newPos = this.positionFromLineCol(info.lines, targetLine, targetCol);
|
|
682
|
-
this.setCursorPosition(newPos);
|
|
683
|
-
return true;
|
|
684
|
-
}
|
|
685
621
|
/**
|
|
686
622
|
* Insert multi-line content directly into the buffer (manual typing).
|
|
687
623
|
*/
|
|
@@ -797,7 +733,8 @@ export class PinnedChatBox {
|
|
|
797
733
|
}
|
|
798
734
|
// Sanitize and truncate command text
|
|
799
735
|
const truncated = sanitizedText.slice(0, this.maxInputLength);
|
|
800
|
-
const
|
|
736
|
+
const previewSource = this.sanitizeInlineText(truncated);
|
|
737
|
+
const preview = previewSource.length > 60 ? `${previewSource.slice(0, 57)}...` : previewSource;
|
|
801
738
|
const cmd = {
|
|
802
739
|
id: `cmd-${++this.commandIdCounter}`,
|
|
803
740
|
text: truncated,
|
|
@@ -853,14 +790,25 @@ export class PinnedChatBox {
|
|
|
853
790
|
* Handle character input with validation
|
|
854
791
|
* Detects multiline paste and stores full content while showing summary
|
|
855
792
|
*/
|
|
856
|
-
handleInput(char) {
|
|
793
|
+
handleInput(char, options = {}) {
|
|
857
794
|
if (!this.isEnabled || this.isDisposed)
|
|
858
795
|
return;
|
|
859
796
|
if (typeof char !== 'string')
|
|
860
797
|
return;
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
798
|
+
const allowNewlines = options.allowNewlines ?? options.isPaste ?? false;
|
|
799
|
+
const hasNewline = char.includes('\n') || char.includes('\r');
|
|
800
|
+
const shouldHandleAsPaste = options.isPaste || (allowNewlines && hasNewline);
|
|
801
|
+
// Detect paste explicitly or any newline-containing input when allowed
|
|
802
|
+
if (shouldHandleAsPaste) {
|
|
803
|
+
if (options.isPaste) {
|
|
804
|
+
this.handleMultilinePaste(char);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
this.insertMultilineText(char);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
// Ignore unexpected newlines unless explicitly allowed
|
|
811
|
+
if (hasNewline) {
|
|
864
812
|
return;
|
|
865
813
|
}
|
|
866
814
|
const sanitized = this.sanitizeInlineText(char);
|
|
@@ -874,13 +822,10 @@ export class PinnedChatBox {
|
|
|
874
822
|
if (!chunk)
|
|
875
823
|
return;
|
|
876
824
|
// Insert character at cursor position
|
|
877
|
-
this.
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
this.cursorPosition = Math.min(this.cursorPosition + chunk.length, this.inputBuffer.length);
|
|
882
|
-
this.state.currentInput = this.inputBuffer;
|
|
883
|
-
this.scheduleRender();
|
|
825
|
+
this.startManualEdit();
|
|
826
|
+
this.applyBufferChange(this.inputBuffer.slice(0, this.cursorPosition) +
|
|
827
|
+
chunk +
|
|
828
|
+
this.inputBuffer.slice(this.cursorPosition), this.cursorPosition + chunk.length);
|
|
884
829
|
}
|
|
885
830
|
/**
|
|
886
831
|
* Handle multiline paste - store full content and display all lines
|
|
@@ -928,37 +873,88 @@ export class PinnedChatBox {
|
|
|
928
873
|
*/
|
|
929
874
|
clearPastedBlockState() {
|
|
930
875
|
this.clearPastedBlock();
|
|
931
|
-
this.
|
|
932
|
-
this.cursorPosition = 0;
|
|
933
|
-
this.state.currentInput = '';
|
|
934
|
-
this.resetReservedLines();
|
|
876
|
+
this.applyBufferChange('', 0);
|
|
935
877
|
}
|
|
936
878
|
/**
|
|
937
|
-
*
|
|
938
|
-
* Ensures multi-line content is fully visible within maxDisplayLines limit.
|
|
879
|
+
* Get terminal dimensions and a safe width for rendering input content.
|
|
939
880
|
*/
|
|
940
|
-
|
|
941
|
-
const
|
|
942
|
-
const
|
|
943
|
-
//
|
|
944
|
-
const
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
881
|
+
getRenderDimensions() {
|
|
882
|
+
const rows = this.writeStream.rows || 24;
|
|
883
|
+
const cols = Math.max(this.writeStream.columns || 80, 40);
|
|
884
|
+
// Leave a small gutter for prefix + padding so we never wrap unexpectedly
|
|
885
|
+
const maxInputWidth = Math.max(10, cols - 5);
|
|
886
|
+
return { rows, cols, maxInputWidth };
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Wrap the current input buffer to the provided width and locate the cursor.
|
|
890
|
+
*/
|
|
891
|
+
wrapInputBuffer(maxInputWidth) {
|
|
892
|
+
const width = Math.max(1, maxInputWidth);
|
|
893
|
+
const rawLines = this.inputBuffer.split('\n');
|
|
894
|
+
const wrappedLines = [];
|
|
895
|
+
const targetCursor = Math.max(0, Math.min(this.cursorPosition, this.inputBuffer.length));
|
|
896
|
+
let cursorLine = 0;
|
|
897
|
+
let cursorCol = 0;
|
|
898
|
+
let absoluteIndex = 0;
|
|
899
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
900
|
+
const raw = rawLines[i] ?? '';
|
|
901
|
+
if (raw.length === 0) {
|
|
902
|
+
// Preserve empty lines so vertical space stays accurate
|
|
903
|
+
wrappedLines.push('');
|
|
904
|
+
if (targetCursor === absoluteIndex) {
|
|
905
|
+
cursorLine = wrappedLines.length - 1;
|
|
906
|
+
cursorCol = 0;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
else {
|
|
910
|
+
for (let start = 0; start < raw.length; start += width) {
|
|
911
|
+
const segment = raw.slice(start, start + width);
|
|
912
|
+
const segmentStart = absoluteIndex + start;
|
|
913
|
+
wrappedLines.push(segment);
|
|
914
|
+
if (targetCursor >= segmentStart && targetCursor <= segmentStart + segment.length) {
|
|
915
|
+
cursorLine = wrappedLines.length - 1;
|
|
916
|
+
cursorCol = targetCursor - segmentStart;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
absoluteIndex += raw.length;
|
|
921
|
+
if (i < rawLines.length - 1) {
|
|
922
|
+
// Account for the newline character between raw lines
|
|
923
|
+
if (targetCursor === absoluteIndex) {
|
|
924
|
+
cursorLine = wrappedLines.length;
|
|
925
|
+
cursorCol = 0;
|
|
926
|
+
}
|
|
927
|
+
absoluteIndex += 1;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (wrappedLines.length === 0) {
|
|
931
|
+
wrappedLines.push('');
|
|
932
|
+
cursorLine = 0;
|
|
933
|
+
cursorCol = 0;
|
|
951
934
|
}
|
|
935
|
+
cursorLine = Math.min(cursorLine, wrappedLines.length - 1);
|
|
936
|
+
cursorCol = Math.min(cursorCol, wrappedLines[cursorLine]?.length ?? 0);
|
|
937
|
+
return { lines: wrappedLines, cursorLine, cursorCol };
|
|
952
938
|
}
|
|
953
939
|
/**
|
|
954
|
-
*
|
|
940
|
+
* Calculate and update reserved lines based on current input content.
|
|
941
|
+
* Ensures multi-line content is fully visible within maxDisplayLines limit.
|
|
955
942
|
*/
|
|
956
|
-
|
|
957
|
-
|
|
943
|
+
updateReservedLinesForContent(maxInputWidth, wrappedLineCount) {
|
|
944
|
+
const { rows, maxInputWidth: derivedWidth } = this.getRenderDimensions();
|
|
945
|
+
const width = Math.max(1, maxInputWidth ?? derivedWidth);
|
|
946
|
+
const totalLines = wrappedLineCount ?? this.wrapInputBuffer(width).lines.length;
|
|
947
|
+
const lineCount = Math.max(1, totalLines);
|
|
948
|
+
// Calculate needed lines: 1 for separator + lineCount for input (clamped to maxDisplayLines)
|
|
949
|
+
const inputLines = Math.min(lineCount, this.maxDisplayLines);
|
|
950
|
+
const calculated = 1 + inputLines; // 1 separator + input lines
|
|
951
|
+
// Ensure we keep at least one row for output scrolling
|
|
952
|
+
const maxAllowed = Math.max(1, rows - 1);
|
|
953
|
+
const desired = Math.max(this.baseReservedLines, calculated);
|
|
954
|
+
this.reservedLines = Math.min(desired, maxAllowed);
|
|
958
955
|
// If we have a scroll region active, update it
|
|
959
956
|
if (this.scrollRegionActive) {
|
|
960
|
-
const
|
|
961
|
-
const scrollBottom = rows - this.reservedLines;
|
|
957
|
+
const scrollBottom = Math.max(1, rows - this.reservedLines);
|
|
962
958
|
this.safeWrite(ANSI.SET_SCROLL_REGION(1, scrollBottom));
|
|
963
959
|
}
|
|
964
960
|
}
|
|
@@ -997,10 +993,9 @@ export class PinnedChatBox {
|
|
|
997
993
|
handleCursorLeft() {
|
|
998
994
|
if (this.isDisposed)
|
|
999
995
|
return;
|
|
1000
|
-
|
|
1001
|
-
if (
|
|
1002
|
-
this.
|
|
1003
|
-
this.scheduleRender();
|
|
996
|
+
const bounded = Math.min(this.cursorPosition, this.inputBuffer.length);
|
|
997
|
+
if (bounded > 0) {
|
|
998
|
+
this.setCursorPosition(bounded - 1);
|
|
1004
999
|
}
|
|
1005
1000
|
}
|
|
1006
1001
|
/**
|
|
@@ -1009,10 +1004,9 @@ export class PinnedChatBox {
|
|
|
1009
1004
|
handleCursorRight() {
|
|
1010
1005
|
if (this.isDisposed)
|
|
1011
1006
|
return;
|
|
1012
|
-
|
|
1013
|
-
if (
|
|
1014
|
-
this.
|
|
1015
|
-
this.scheduleRender();
|
|
1007
|
+
const bounded = Math.min(this.cursorPosition, this.inputBuffer.length);
|
|
1008
|
+
if (bounded < this.inputBuffer.length) {
|
|
1009
|
+
this.setCursorPosition(bounded + 1);
|
|
1016
1010
|
}
|
|
1017
1011
|
}
|
|
1018
1012
|
/**
|
|
@@ -1028,8 +1022,7 @@ export class PinnedChatBox {
|
|
|
1028
1022
|
for (let i = 0; i < lineIndex; i++) {
|
|
1029
1023
|
newPos += (lines[i] ?? '').length + 1;
|
|
1030
1024
|
}
|
|
1031
|
-
this.
|
|
1032
|
-
this.scheduleRender();
|
|
1025
|
+
this.setCursorPosition(newPos);
|
|
1033
1026
|
}
|
|
1034
1027
|
/**
|
|
1035
1028
|
* Handle end key (Ctrl+E) - move to end of current line (not end of buffer)
|
|
@@ -1046,8 +1039,7 @@ export class PinnedChatBox {
|
|
|
1046
1039
|
newPos += (lines[i] ?? '').length + 1;
|
|
1047
1040
|
}
|
|
1048
1041
|
newPos += currentLine.length;
|
|
1049
|
-
this.
|
|
1050
|
-
this.scheduleRender();
|
|
1042
|
+
this.setCursorPosition(newPos);
|
|
1051
1043
|
}
|
|
1052
1044
|
/**
|
|
1053
1045
|
* Handle inserting a newline character at cursor position (Shift+Enter or Option+Enter)
|
|
@@ -1058,16 +1050,10 @@ export class PinnedChatBox {
|
|
|
1058
1050
|
// Respect max input length
|
|
1059
1051
|
if (this.inputBuffer.length >= this.maxInputLength)
|
|
1060
1052
|
return;
|
|
1061
|
-
|
|
1062
|
-
this.inputBuffer
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
this.inputBuffer.slice(this.cursorPosition);
|
|
1066
|
-
this.cursorPosition++;
|
|
1067
|
-
this.state.currentInput = this.inputBuffer;
|
|
1068
|
-
// Update reserved lines for the new content
|
|
1069
|
-
this.updateReservedLinesForContent();
|
|
1070
|
-
this.scheduleRender();
|
|
1053
|
+
this.startManualEdit();
|
|
1054
|
+
this.applyBufferChange(this.inputBuffer.slice(0, this.cursorPosition) +
|
|
1055
|
+
'\n' +
|
|
1056
|
+
this.inputBuffer.slice(this.cursorPosition), this.cursorPosition + 1);
|
|
1071
1057
|
}
|
|
1072
1058
|
/**
|
|
1073
1059
|
* Get cursor line and column position within multi-line content.
|
|
@@ -1111,9 +1097,7 @@ export class PinnedChatBox {
|
|
|
1111
1097
|
newPos += (lines[i] ?? '').length + 1;
|
|
1112
1098
|
}
|
|
1113
1099
|
newPos += newCol;
|
|
1114
|
-
this.
|
|
1115
|
-
this.state.currentInput = this.inputBuffer;
|
|
1116
|
-
this.scheduleRender();
|
|
1100
|
+
this.setCursorPosition(newPos);
|
|
1117
1101
|
return;
|
|
1118
1102
|
}
|
|
1119
1103
|
// On first line - navigate history
|
|
@@ -1126,10 +1110,9 @@ export class PinnedChatBox {
|
|
|
1126
1110
|
// Move up in history
|
|
1127
1111
|
if (this.historyIndex < this.inputHistory.length - 1) {
|
|
1128
1112
|
this.historyIndex++;
|
|
1129
|
-
|
|
1130
|
-
this.
|
|
1131
|
-
this.
|
|
1132
|
-
this.scheduleRender();
|
|
1113
|
+
const nextBuffer = this.inputHistory[this.inputHistory.length - 1 - this.historyIndex] || '';
|
|
1114
|
+
this.startManualEdit();
|
|
1115
|
+
this.applyBufferChange(nextBuffer, nextBuffer.length);
|
|
1133
1116
|
}
|
|
1134
1117
|
}
|
|
1135
1118
|
/**
|
|
@@ -1150,27 +1133,23 @@ export class PinnedChatBox {
|
|
|
1150
1133
|
newPos += (lines[i] ?? '').length + 1;
|
|
1151
1134
|
}
|
|
1152
1135
|
newPos += newCol;
|
|
1153
|
-
this.
|
|
1154
|
-
this.state.currentInput = this.inputBuffer;
|
|
1155
|
-
this.scheduleRender();
|
|
1136
|
+
this.setCursorPosition(newPos);
|
|
1156
1137
|
return;
|
|
1157
1138
|
}
|
|
1158
1139
|
// On last line - navigate history
|
|
1159
1140
|
if (this.historyIndex > 0) {
|
|
1160
1141
|
// Move down in history
|
|
1161
1142
|
this.historyIndex--;
|
|
1162
|
-
|
|
1163
|
-
this.
|
|
1164
|
-
this.
|
|
1165
|
-
this.scheduleRender();
|
|
1143
|
+
const nextBuffer = this.inputHistory[this.inputHistory.length - 1 - this.historyIndex] || '';
|
|
1144
|
+
this.startManualEdit();
|
|
1145
|
+
this.applyBufferChange(nextBuffer, nextBuffer.length);
|
|
1166
1146
|
}
|
|
1167
1147
|
else if (this.historyIndex === 0) {
|
|
1168
1148
|
// Return to current input
|
|
1169
1149
|
this.historyIndex = -1;
|
|
1170
|
-
|
|
1171
|
-
this.
|
|
1172
|
-
this.
|
|
1173
|
-
this.scheduleRender();
|
|
1150
|
+
const restored = this.tempCurrentInput;
|
|
1151
|
+
this.startManualEdit();
|
|
1152
|
+
this.applyBufferChange(restored, restored.length);
|
|
1174
1153
|
}
|
|
1175
1154
|
}
|
|
1176
1155
|
/**
|
|
@@ -1181,10 +1160,9 @@ export class PinnedChatBox {
|
|
|
1181
1160
|
return;
|
|
1182
1161
|
if (this.cursorPosition === 0)
|
|
1183
1162
|
return;
|
|
1184
|
-
this.
|
|
1185
|
-
this.cursorPosition
|
|
1186
|
-
this.
|
|
1187
|
-
this.scheduleRender();
|
|
1163
|
+
this.startManualEdit();
|
|
1164
|
+
const newBuffer = this.inputBuffer.slice(this.cursorPosition);
|
|
1165
|
+
this.applyBufferChange(newBuffer, 0);
|
|
1188
1166
|
}
|
|
1189
1167
|
/**
|
|
1190
1168
|
* Handle Ctrl+K - delete from cursor to end of line
|
|
@@ -1194,9 +1172,9 @@ export class PinnedChatBox {
|
|
|
1194
1172
|
return;
|
|
1195
1173
|
if (this.cursorPosition >= this.inputBuffer.length)
|
|
1196
1174
|
return;
|
|
1197
|
-
this.
|
|
1198
|
-
this.
|
|
1199
|
-
this.
|
|
1175
|
+
this.startManualEdit();
|
|
1176
|
+
const newBuffer = this.inputBuffer.slice(0, this.cursorPosition);
|
|
1177
|
+
this.applyBufferChange(newBuffer, this.cursorPosition);
|
|
1200
1178
|
}
|
|
1201
1179
|
/**
|
|
1202
1180
|
* Handle Ctrl+W - delete word before cursor
|
|
@@ -1209,17 +1187,16 @@ export class PinnedChatBox {
|
|
|
1209
1187
|
// Find the start of the word (skip trailing spaces, then find word boundary)
|
|
1210
1188
|
let pos = this.cursorPosition;
|
|
1211
1189
|
// Skip any spaces before cursor
|
|
1212
|
-
while (pos > 0 && this.inputBuffer[pos - 1]
|
|
1190
|
+
while (pos > 0 && this.isWhitespace(this.inputBuffer[pos - 1])) {
|
|
1213
1191
|
pos--;
|
|
1214
1192
|
}
|
|
1215
1193
|
// Find start of word
|
|
1216
|
-
while (pos > 0 && this.inputBuffer[pos - 1]
|
|
1194
|
+
while (pos > 0 && !this.isWhitespace(this.inputBuffer[pos - 1])) {
|
|
1217
1195
|
pos--;
|
|
1218
1196
|
}
|
|
1219
|
-
|
|
1220
|
-
this.
|
|
1221
|
-
this.
|
|
1222
|
-
this.scheduleRender();
|
|
1197
|
+
const newBuffer = this.inputBuffer.slice(0, pos) + this.inputBuffer.slice(this.cursorPosition);
|
|
1198
|
+
this.startManualEdit();
|
|
1199
|
+
this.applyBufferChange(newBuffer, pos);
|
|
1223
1200
|
}
|
|
1224
1201
|
/**
|
|
1225
1202
|
* Handle Alt+Left - move cursor to previous word
|
|
@@ -1231,15 +1208,14 @@ export class PinnedChatBox {
|
|
|
1231
1208
|
return;
|
|
1232
1209
|
let pos = this.cursorPosition;
|
|
1233
1210
|
// Skip any spaces before cursor
|
|
1234
|
-
while (pos > 0 && this.inputBuffer[pos - 1]
|
|
1211
|
+
while (pos > 0 && this.isWhitespace(this.inputBuffer[pos - 1])) {
|
|
1235
1212
|
pos--;
|
|
1236
1213
|
}
|
|
1237
1214
|
// Find start of word
|
|
1238
|
-
while (pos > 0 && this.inputBuffer[pos - 1]
|
|
1215
|
+
while (pos > 0 && !this.isWhitespace(this.inputBuffer[pos - 1])) {
|
|
1239
1216
|
pos--;
|
|
1240
1217
|
}
|
|
1241
|
-
this.
|
|
1242
|
-
this.scheduleRender();
|
|
1218
|
+
this.setCursorPosition(pos);
|
|
1243
1219
|
}
|
|
1244
1220
|
/**
|
|
1245
1221
|
* Handle Alt+Right - move cursor to next word
|
|
@@ -1251,15 +1227,14 @@ export class PinnedChatBox {
|
|
|
1251
1227
|
return;
|
|
1252
1228
|
let pos = this.cursorPosition;
|
|
1253
1229
|
// Skip current word
|
|
1254
|
-
while (pos < this.inputBuffer.length && this.inputBuffer[pos]
|
|
1230
|
+
while (pos < this.inputBuffer.length && !this.isWhitespace(this.inputBuffer[pos])) {
|
|
1255
1231
|
pos++;
|
|
1256
1232
|
}
|
|
1257
1233
|
// Skip any spaces
|
|
1258
|
-
while (pos < this.inputBuffer.length && this.inputBuffer[pos]
|
|
1234
|
+
while (pos < this.inputBuffer.length && this.isWhitespace(this.inputBuffer[pos])) {
|
|
1259
1235
|
pos++;
|
|
1260
1236
|
}
|
|
1261
|
-
this.
|
|
1262
|
-
this.scheduleRender();
|
|
1237
|
+
this.setCursorPosition(pos);
|
|
1263
1238
|
}
|
|
1264
1239
|
/**
|
|
1265
1240
|
* Add input to history (call after successful submit)
|
|
@@ -1326,15 +1301,12 @@ export class PinnedChatBox {
|
|
|
1326
1301
|
clearInput() {
|
|
1327
1302
|
if (this.isDisposed)
|
|
1328
1303
|
return;
|
|
1329
|
-
this.inputBuffer = '';
|
|
1330
|
-
this.cursorPosition = 0;
|
|
1331
|
-
this.state.currentInput = '';
|
|
1332
1304
|
// Reset history navigation
|
|
1333
1305
|
this.historyIndex = -1;
|
|
1334
1306
|
this.tempCurrentInput = '';
|
|
1335
1307
|
// Clear paste state
|
|
1336
1308
|
this.clearPastedBlock();
|
|
1337
|
-
this.
|
|
1309
|
+
this.applyBufferChange('', 0);
|
|
1338
1310
|
}
|
|
1339
1311
|
/**
|
|
1340
1312
|
* Set input text and optionally cursor position (for readline sync)
|
|
@@ -1343,13 +1315,14 @@ export class PinnedChatBox {
|
|
|
1343
1315
|
setInput(text, cursorPos) {
|
|
1344
1316
|
if (this.isDisposed)
|
|
1345
1317
|
return;
|
|
1346
|
-
const normalized = this.
|
|
1318
|
+
const normalized = this.sanitizeMultilineForDisplay(text).slice(0, this.maxInputLength);
|
|
1347
1319
|
this.inputBuffer = normalized;
|
|
1348
1320
|
// Use provided cursor position, or default to end of input
|
|
1349
1321
|
this.cursorPosition = typeof cursorPos === 'number'
|
|
1350
1322
|
? Math.max(0, Math.min(cursorPos, normalized.length))
|
|
1351
1323
|
: normalized.length;
|
|
1352
1324
|
this.state.currentInput = normalized;
|
|
1325
|
+
this.updateReservedLinesForContent();
|
|
1353
1326
|
// Do NOT schedule render here - readline handles display during input
|
|
1354
1327
|
// render() will only work when isProcessing=true anyway
|
|
1355
1328
|
}
|
|
@@ -1484,10 +1457,16 @@ export class PinnedChatBox {
|
|
|
1484
1457
|
handleResize() {
|
|
1485
1458
|
// Invalidate render state to force re-render with new dimensions
|
|
1486
1459
|
this.invalidateRenderedState();
|
|
1487
|
-
|
|
1488
|
-
|
|
1460
|
+
const wasActive = this.scrollRegionActive;
|
|
1461
|
+
if (wasActive) {
|
|
1462
|
+
// Reset scroll region before recalculating dimensions
|
|
1489
1463
|
this.safeWrite(ANSI.RESET_SCROLL_REGION);
|
|
1490
1464
|
this.scrollRegionActive = false;
|
|
1465
|
+
}
|
|
1466
|
+
// Recompute reserved lines for the new terminal size
|
|
1467
|
+
this.updateReservedLinesForContent();
|
|
1468
|
+
if (wasActive) {
|
|
1469
|
+
// Re-enable scroll region with the updated height
|
|
1491
1470
|
this.enableScrollRegion();
|
|
1492
1471
|
}
|
|
1493
1472
|
this.scheduleRender();
|