erosolar-cli 1.7.192 → 1.7.194
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/bin/selfTest.d.ts.map +1 -1
- package/dist/bin/selfTest.js +3 -105
- package/dist/bin/selfTest.js.map +1 -1
- package/dist/contracts/module-schema.json +1 -1
- package/dist/core/performanceMonitor.d.ts +18 -0
- package/dist/core/performanceMonitor.d.ts.map +1 -1
- package/dist/core/performanceMonitor.js.map +1 -1
- package/dist/core/schemaValidator.d.ts +38 -0
- package/dist/core/schemaValidator.d.ts.map +1 -1
- package/dist/core/schemaValidator.js +62 -0
- package/dist/core/schemaValidator.js.map +1 -1
- package/dist/core/testUtils.d.ts +121 -0
- package/dist/core/testUtils.d.ts.map +1 -0
- package/dist/core/testUtils.js +235 -0
- package/dist/core/testUtils.js.map +1 -0
- package/dist/core/toolPreconditions.d.ts.map +1 -1
- package/dist/core/toolPreconditions.js +55 -3
- package/dist/core/toolPreconditions.js.map +1 -1
- package/dist/core/toolRuntime.d.ts +46 -0
- package/dist/core/toolRuntime.d.ts.map +1 -1
- package/dist/core/toolRuntime.js +37 -12
- package/dist/core/toolRuntime.js.map +1 -1
- package/dist/core/types.d.ts +233 -68
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +75 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/unified/tools.js +1 -1
- package/dist/core/unified/tools.js.map +1 -1
- package/dist/providers/anthropicProvider.js +2 -2
- package/dist/providers/anthropicProvider.js.map +1 -1
- package/dist/providers/openaiChatCompletionsProvider.js +4 -5
- package/dist/providers/openaiChatCompletionsProvider.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +15 -3
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +95 -11
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/shell/shellApp.d.ts.map +1 -1
- package/dist/shell/shellApp.js +16 -0
- package/dist/shell/shellApp.js.map +1 -1
- package/dist/shell/terminalInput.d.ts +36 -1
- package/dist/shell/terminalInput.d.ts.map +1 -1
- package/dist/shell/terminalInput.js +248 -21
- package/dist/shell/terminalInput.js.map +1 -1
- package/dist/shell/terminalInputAdapter.d.ts +27 -4
- package/dist/shell/terminalInputAdapter.d.ts.map +1 -1
- package/dist/shell/terminalInputAdapter.js +28 -3
- package/dist/shell/terminalInputAdapter.js.map +1 -1
- package/dist/tools/editTools.js +2 -2
- package/dist/tools/editTools.js.map +1 -1
- package/dist/ui/display.d.ts +6 -21
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +88 -177
- package/dist/ui/display.js.map +1 -1
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ import { EventEmitter } from 'node:events';
|
|
|
13
13
|
* Input modes - determines how input is handled
|
|
14
14
|
*/
|
|
15
15
|
export type InputMode = 'idle' | 'streaming' | 'paste';
|
|
16
|
+
export type EditGuardMode = 'display-edits' | 'ask-permission';
|
|
16
17
|
/**
|
|
17
18
|
* Events emitted by TerminalInput
|
|
18
19
|
*/
|
|
@@ -51,6 +52,8 @@ export declare class TerminalInput extends EventEmitter {
|
|
|
51
52
|
private mode;
|
|
52
53
|
private pasteBuffer;
|
|
53
54
|
private isPasting;
|
|
55
|
+
private pastePlaceholders;
|
|
56
|
+
private pasteCounter;
|
|
54
57
|
private history;
|
|
55
58
|
private historyIndex;
|
|
56
59
|
private tempInput;
|
|
@@ -64,6 +67,9 @@ export declare class TerminalInput extends EventEmitter {
|
|
|
64
67
|
private isRendering;
|
|
65
68
|
private disposed;
|
|
66
69
|
private enabled;
|
|
70
|
+
private contextUsage;
|
|
71
|
+
private editMode;
|
|
72
|
+
private outputInterceptorCleanup?;
|
|
67
73
|
constructor(writeStream?: NodeJS.WriteStream, config?: TerminalInputConfig);
|
|
68
74
|
/**
|
|
69
75
|
* Enable bracketed paste mode in terminal
|
|
@@ -94,9 +100,13 @@ export declare class TerminalInput extends EventEmitter {
|
|
|
94
100
|
*/
|
|
95
101
|
getMode(): InputMode;
|
|
96
102
|
/**
|
|
97
|
-
* Get the current buffer content
|
|
103
|
+
* Get the current buffer content (may contain placeholders for pasted text)
|
|
98
104
|
*/
|
|
99
105
|
getBuffer(): string;
|
|
106
|
+
/**
|
|
107
|
+
* Get the actual text with paste placeholders expanded
|
|
108
|
+
*/
|
|
109
|
+
getText(): string;
|
|
100
110
|
/**
|
|
101
111
|
* Set buffer content
|
|
102
112
|
*/
|
|
@@ -133,6 +143,17 @@ export declare class TerminalInput extends EventEmitter {
|
|
|
133
143
|
* Handle terminal resize
|
|
134
144
|
*/
|
|
135
145
|
handleResize(): void;
|
|
146
|
+
/**
|
|
147
|
+
* Register with display's output interceptor to position cursor correctly.
|
|
148
|
+
* When scroll region is active, output needs to go to the scroll region,
|
|
149
|
+
* not the protected bottom area where the input is rendered.
|
|
150
|
+
*/
|
|
151
|
+
registerOutputInterceptor(display: {
|
|
152
|
+
registerOutputInterceptor: (i: {
|
|
153
|
+
beforeWrite?: () => void;
|
|
154
|
+
afterWrite?: () => void;
|
|
155
|
+
}) => () => void;
|
|
156
|
+
}): void;
|
|
136
157
|
/**
|
|
137
158
|
* Dispose and clean up
|
|
138
159
|
*/
|
|
@@ -164,6 +185,20 @@ export declare class TerminalInput extends EventEmitter {
|
|
|
164
185
|
private disableScrollRegion;
|
|
165
186
|
private updateReservedLines;
|
|
166
187
|
private wrapBuffer;
|
|
188
|
+
private getComposedLength;
|
|
189
|
+
private assembleText;
|
|
190
|
+
private shiftPlaceholders;
|
|
191
|
+
private removeRange;
|
|
192
|
+
private insertPlainText;
|
|
193
|
+
private findPlaceholderAt;
|
|
194
|
+
private buildPlaceholder;
|
|
195
|
+
private insertPastePlaceholder;
|
|
196
|
+
private deletePlaceholder;
|
|
197
|
+
updateContextUsage(value: number | null): void;
|
|
198
|
+
getEditMode(): EditGuardMode;
|
|
199
|
+
applyEditMode(mode: EditGuardMode): void;
|
|
200
|
+
private setEditMode;
|
|
201
|
+
toggleEditMode(): void;
|
|
167
202
|
private scheduleRender;
|
|
168
203
|
private canRender;
|
|
169
204
|
private isTTY;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminalInput.d.ts","sourceRoot":"","sources":["../../src/shell/terminalInput.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"terminalInput.d.ts","sourceRoot":"","sources":["../../src/shell/terminalInput.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* - No readline dependency for display
|
|
10
10
|
*/
|
|
11
11
|
import { EventEmitter } from 'node:events';
|
|
12
|
+
import { isMultilinePaste } from '../core/multilinePasteHandler.js';
|
|
12
13
|
// ANSI escape codes
|
|
13
14
|
const ESC = {
|
|
14
15
|
// Cursor control
|
|
@@ -49,6 +50,8 @@ export class TerminalInput extends EventEmitter {
|
|
|
49
50
|
// Paste accumulation
|
|
50
51
|
pasteBuffer = '';
|
|
51
52
|
isPasting = false;
|
|
53
|
+
pastePlaceholders = [];
|
|
54
|
+
pasteCounter = 0;
|
|
52
55
|
// History
|
|
53
56
|
history = [];
|
|
54
57
|
historyIndex = -1;
|
|
@@ -66,11 +69,15 @@ export class TerminalInput extends EventEmitter {
|
|
|
66
69
|
// Lifecycle
|
|
67
70
|
disposed = false;
|
|
68
71
|
enabled = true;
|
|
72
|
+
contextUsage = null;
|
|
73
|
+
editMode = 'display-edits';
|
|
74
|
+
// Output interceptor cleanup
|
|
75
|
+
outputInterceptorCleanup;
|
|
69
76
|
constructor(writeStream = process.stdout, config = {}) {
|
|
70
77
|
super();
|
|
71
78
|
this.out = writeStream;
|
|
72
79
|
this.config = {
|
|
73
|
-
maxLines: config.maxLines ??
|
|
80
|
+
maxLines: config.maxLines ?? 1000,
|
|
74
81
|
maxLength: config.maxLength ?? 10000,
|
|
75
82
|
maxQueueSize: config.maxQueueSize ?? 100,
|
|
76
83
|
promptChar: config.promptChar ?? '> ',
|
|
@@ -178,16 +185,24 @@ export class TerminalInput extends EventEmitter {
|
|
|
178
185
|
return this.mode;
|
|
179
186
|
}
|
|
180
187
|
/**
|
|
181
|
-
* Get the current buffer content
|
|
188
|
+
* Get the current buffer content (may contain placeholders for pasted text)
|
|
182
189
|
*/
|
|
183
190
|
getBuffer() {
|
|
184
191
|
return this.buffer;
|
|
185
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Get the actual text with paste placeholders expanded
|
|
195
|
+
*/
|
|
196
|
+
getText() {
|
|
197
|
+
return this.assembleText();
|
|
198
|
+
}
|
|
186
199
|
/**
|
|
187
200
|
* Set buffer content
|
|
188
201
|
*/
|
|
189
202
|
setBuffer(text, cursorPos) {
|
|
190
|
-
this.
|
|
203
|
+
this.pastePlaceholders = [];
|
|
204
|
+
const clean = this.sanitize(text).slice(0, this.config.maxLength);
|
|
205
|
+
this.buffer = clean;
|
|
191
206
|
this.cursor = cursorPos ?? this.buffer.length;
|
|
192
207
|
this.clampCursor();
|
|
193
208
|
this.scheduleRender();
|
|
@@ -200,6 +215,7 @@ export class TerminalInput extends EventEmitter {
|
|
|
200
215
|
this.cursor = 0;
|
|
201
216
|
this.historyIndex = -1;
|
|
202
217
|
this.tempInput = '';
|
|
218
|
+
this.pastePlaceholders = [];
|
|
203
219
|
this.scheduleRender();
|
|
204
220
|
}
|
|
205
221
|
/**
|
|
@@ -241,7 +257,8 @@ export class TerminalInput extends EventEmitter {
|
|
|
241
257
|
const maxWidth = Math.max(8, cols - 4);
|
|
242
258
|
// Wrap buffer into display lines
|
|
243
259
|
const { lines, cursorLine, cursorCol } = this.wrapBuffer(maxWidth);
|
|
244
|
-
const
|
|
260
|
+
const maxVisible = Math.max(1, Math.min(this.config.maxLines, rows - 1));
|
|
261
|
+
const displayLines = Math.min(lines.length, maxVisible);
|
|
245
262
|
// Update reserved lines if needed
|
|
246
263
|
this.updateReservedLines(displayLines);
|
|
247
264
|
// Calculate display window (keep cursor visible)
|
|
@@ -264,6 +281,15 @@ export class TerminalInput extends EventEmitter {
|
|
|
264
281
|
this.write('─'.repeat(sepWidth));
|
|
265
282
|
// Status hints
|
|
266
283
|
const hints = [];
|
|
284
|
+
if (this.contextUsage !== null) {
|
|
285
|
+
const remaining = Math.max(0, 100 - this.contextUsage);
|
|
286
|
+
hints.push(`context ${remaining}% left`);
|
|
287
|
+
}
|
|
288
|
+
hints.push(this.editMode === 'display-edits' ? 'mode: display edits' : 'mode: ask permission');
|
|
289
|
+
if (this.pastePlaceholders.length > 0) {
|
|
290
|
+
const count = this.pastePlaceholders.length;
|
|
291
|
+
hints.push(`${count} paste block${count === 1 ? '' : 's'}`);
|
|
292
|
+
}
|
|
267
293
|
if (lines.length > 1)
|
|
268
294
|
hints.push(`${lines.length} lines`);
|
|
269
295
|
if (this.mode === 'streaming' && this.queue.length > 0) {
|
|
@@ -351,12 +377,44 @@ export class TerminalInput extends EventEmitter {
|
|
|
351
377
|
}
|
|
352
378
|
this.scheduleRender();
|
|
353
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Register with display's output interceptor to position cursor correctly.
|
|
382
|
+
* When scroll region is active, output needs to go to the scroll region,
|
|
383
|
+
* not the protected bottom area where the input is rendered.
|
|
384
|
+
*/
|
|
385
|
+
registerOutputInterceptor(display) {
|
|
386
|
+
if (this.outputInterceptorCleanup) {
|
|
387
|
+
this.outputInterceptorCleanup();
|
|
388
|
+
}
|
|
389
|
+
this.outputInterceptorCleanup = display.registerOutputInterceptor({
|
|
390
|
+
beforeWrite: () => {
|
|
391
|
+
// When scroll region is active, move cursor to scroll area before output
|
|
392
|
+
if (this.scrollRegionActive) {
|
|
393
|
+
const { rows } = this.getSize();
|
|
394
|
+
const scrollBottom = Math.max(1, rows - this.reservedLines);
|
|
395
|
+
this.write(ESC.SAVE);
|
|
396
|
+
this.write(ESC.TO(scrollBottom, 1));
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
afterWrite: () => {
|
|
400
|
+
// Restore cursor position after output
|
|
401
|
+
if (this.scrollRegionActive) {
|
|
402
|
+
this.write(ESC.RESTORE);
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
}
|
|
354
407
|
/**
|
|
355
408
|
* Dispose and clean up
|
|
356
409
|
*/
|
|
357
410
|
dispose() {
|
|
358
411
|
if (this.disposed)
|
|
359
412
|
return;
|
|
413
|
+
// Clean up output interceptor
|
|
414
|
+
if (this.outputInterceptorCleanup) {
|
|
415
|
+
this.outputInterceptorCleanup();
|
|
416
|
+
this.outputInterceptorCleanup = undefined;
|
|
417
|
+
}
|
|
360
418
|
this.disposed = true;
|
|
361
419
|
this.enabled = false;
|
|
362
420
|
this.disableScrollRegion();
|
|
@@ -373,7 +431,6 @@ export class TerminalInput extends EventEmitter {
|
|
|
373
431
|
case 'c':
|
|
374
432
|
if (this.buffer.length > 0) {
|
|
375
433
|
this.clear();
|
|
376
|
-
this.write('^C\n');
|
|
377
434
|
}
|
|
378
435
|
else {
|
|
379
436
|
this.emit('interrupt');
|
|
@@ -455,6 +512,10 @@ export class TerminalInput extends EventEmitter {
|
|
|
455
512
|
this.moveCursorToLineEnd();
|
|
456
513
|
return true;
|
|
457
514
|
case 'tab':
|
|
515
|
+
if (key.shift) {
|
|
516
|
+
this.toggleEditMode();
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
458
519
|
this.insertText(' ');
|
|
459
520
|
return true;
|
|
460
521
|
}
|
|
@@ -464,42 +525,58 @@ export class TerminalInput extends EventEmitter {
|
|
|
464
525
|
const clean = this.sanitize(text);
|
|
465
526
|
if (!clean)
|
|
466
527
|
return;
|
|
467
|
-
const available = this.config.maxLength - this.
|
|
528
|
+
const available = this.config.maxLength - this.getComposedLength();
|
|
468
529
|
if (available <= 0)
|
|
469
530
|
return;
|
|
470
531
|
const chunk = clean.slice(0, available);
|
|
471
|
-
|
|
472
|
-
this.cursor
|
|
532
|
+
const placeholder = this.findPlaceholderAt(this.cursor);
|
|
533
|
+
const insertPos = placeholder && this.cursor > placeholder.start ? placeholder.end : this.cursor;
|
|
534
|
+
this.insertPlainText(chunk, insertPos);
|
|
535
|
+
this.cursor = insertPos + chunk.length;
|
|
473
536
|
this.emit('change', this.buffer);
|
|
474
537
|
this.scheduleRender();
|
|
475
538
|
}
|
|
476
539
|
insertNewline() {
|
|
477
|
-
if (this.
|
|
540
|
+
if (this.getComposedLength() >= this.config.maxLength)
|
|
478
541
|
return;
|
|
479
|
-
|
|
480
|
-
this.cursor
|
|
542
|
+
const placeholder = this.findPlaceholderAt(this.cursor);
|
|
543
|
+
const insertPos = placeholder && this.cursor > placeholder.start ? placeholder.end : this.cursor;
|
|
544
|
+
this.insertPlainText('\n', insertPos);
|
|
545
|
+
this.cursor = insertPos + 1;
|
|
481
546
|
this.emit('change', this.buffer);
|
|
482
547
|
this.scheduleRender();
|
|
483
548
|
}
|
|
484
549
|
deleteBackward() {
|
|
485
550
|
if (this.cursor === 0)
|
|
486
551
|
return;
|
|
487
|
-
|
|
488
|
-
|
|
552
|
+
const placeholder = this.findPlaceholderAt(this.cursor - 1);
|
|
553
|
+
if (placeholder) {
|
|
554
|
+
this.deletePlaceholder(placeholder);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
this.removeRange(this.cursor - 1, this.cursor);
|
|
558
|
+
this.cursor = Math.max(0, this.cursor - 1);
|
|
559
|
+
}
|
|
489
560
|
this.emit('change', this.buffer);
|
|
490
561
|
this.scheduleRender();
|
|
491
562
|
}
|
|
492
563
|
deleteForward() {
|
|
493
564
|
if (this.cursor >= this.buffer.length)
|
|
494
565
|
return;
|
|
495
|
-
|
|
566
|
+
const placeholder = this.findPlaceholderAt(this.cursor);
|
|
567
|
+
if (placeholder) {
|
|
568
|
+
this.deletePlaceholder(placeholder);
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
this.removeRange(this.cursor, this.cursor + 1);
|
|
572
|
+
}
|
|
496
573
|
this.emit('change', this.buffer);
|
|
497
574
|
this.scheduleRender();
|
|
498
575
|
}
|
|
499
576
|
deleteToStart() {
|
|
500
577
|
if (this.cursor === 0)
|
|
501
578
|
return;
|
|
502
|
-
this.
|
|
579
|
+
this.removeRange(0, this.cursor);
|
|
503
580
|
this.cursor = 0;
|
|
504
581
|
this.emit('change', this.buffer);
|
|
505
582
|
this.scheduleRender();
|
|
@@ -507,13 +584,20 @@ export class TerminalInput extends EventEmitter {
|
|
|
507
584
|
deleteToEnd() {
|
|
508
585
|
if (this.cursor >= this.buffer.length)
|
|
509
586
|
return;
|
|
510
|
-
this.
|
|
587
|
+
this.removeRange(this.cursor, this.buffer.length);
|
|
511
588
|
this.emit('change', this.buffer);
|
|
512
589
|
this.scheduleRender();
|
|
513
590
|
}
|
|
514
591
|
deleteWord() {
|
|
515
592
|
if (this.cursor === 0)
|
|
516
593
|
return;
|
|
594
|
+
const placeholder = this.findPlaceholderAt(this.cursor - 1);
|
|
595
|
+
if (placeholder) {
|
|
596
|
+
this.deletePlaceholder(placeholder);
|
|
597
|
+
this.emit('change', this.buffer);
|
|
598
|
+
this.scheduleRender();
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
517
601
|
let pos = this.cursor;
|
|
518
602
|
// Skip whitespace
|
|
519
603
|
while (pos > 0 && this.isWhitespace(this.buffer[pos - 1]))
|
|
@@ -678,7 +762,7 @@ export class TerminalInput extends EventEmitter {
|
|
|
678
762
|
};
|
|
679
763
|
}
|
|
680
764
|
submit() {
|
|
681
|
-
const text = this.
|
|
765
|
+
const text = this.assembleText().trim();
|
|
682
766
|
if (!text)
|
|
683
767
|
return;
|
|
684
768
|
// Add to history
|
|
@@ -714,12 +798,22 @@ export class TerminalInput extends EventEmitter {
|
|
|
714
798
|
this.isPasting = false;
|
|
715
799
|
if (!content)
|
|
716
800
|
return;
|
|
717
|
-
// Insert paste content at cursor
|
|
718
801
|
const clean = this.sanitize(content);
|
|
719
|
-
|
|
802
|
+
if (!clean)
|
|
803
|
+
return;
|
|
804
|
+
const available = this.config.maxLength - this.getComposedLength();
|
|
805
|
+
if (available <= 0)
|
|
806
|
+
return;
|
|
720
807
|
const chunk = clean.slice(0, available);
|
|
721
|
-
|
|
722
|
-
|
|
808
|
+
if (isMultilinePaste(chunk)) {
|
|
809
|
+
this.insertPastePlaceholder(chunk);
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
const placeholder = this.findPlaceholderAt(this.cursor);
|
|
813
|
+
const insertPos = placeholder && this.cursor > placeholder.start ? placeholder.end : this.cursor;
|
|
814
|
+
this.insertPlainText(chunk, insertPos);
|
|
815
|
+
this.cursor = insertPos + chunk.length;
|
|
816
|
+
}
|
|
723
817
|
this.emit('change', this.buffer);
|
|
724
818
|
this.scheduleRender();
|
|
725
819
|
}
|
|
@@ -808,6 +902,139 @@ export class TerminalInput extends EventEmitter {
|
|
|
808
902
|
// ===========================================================================
|
|
809
903
|
// UTILITIES
|
|
810
904
|
// ===========================================================================
|
|
905
|
+
getComposedLength() {
|
|
906
|
+
if (this.pastePlaceholders.length === 0) {
|
|
907
|
+
return this.buffer.length;
|
|
908
|
+
}
|
|
909
|
+
const sorted = [...this.pastePlaceholders].sort((a, b) => a.start - b.start);
|
|
910
|
+
let composed = 0;
|
|
911
|
+
let cursor = 0;
|
|
912
|
+
for (const placeholder of sorted) {
|
|
913
|
+
composed += Math.max(0, placeholder.start - cursor);
|
|
914
|
+
composed += placeholder.content.length;
|
|
915
|
+
cursor = placeholder.end;
|
|
916
|
+
}
|
|
917
|
+
composed += Math.max(0, this.buffer.length - cursor);
|
|
918
|
+
return composed;
|
|
919
|
+
}
|
|
920
|
+
assembleText() {
|
|
921
|
+
if (this.pastePlaceholders.length === 0) {
|
|
922
|
+
return this.buffer;
|
|
923
|
+
}
|
|
924
|
+
const sorted = [...this.pastePlaceholders].sort((a, b) => a.start - b.start);
|
|
925
|
+
let result = '';
|
|
926
|
+
let cursor = 0;
|
|
927
|
+
for (const placeholder of sorted) {
|
|
928
|
+
result += this.buffer.slice(cursor, placeholder.start);
|
|
929
|
+
result += placeholder.content;
|
|
930
|
+
cursor = placeholder.end;
|
|
931
|
+
}
|
|
932
|
+
result += this.buffer.slice(cursor);
|
|
933
|
+
return result;
|
|
934
|
+
}
|
|
935
|
+
shiftPlaceholders(start, delta, excludeId) {
|
|
936
|
+
if (delta === 0)
|
|
937
|
+
return;
|
|
938
|
+
for (const placeholder of this.pastePlaceholders) {
|
|
939
|
+
if (excludeId && placeholder.id === excludeId)
|
|
940
|
+
continue;
|
|
941
|
+
if (placeholder.start >= start) {
|
|
942
|
+
placeholder.start += delta;
|
|
943
|
+
placeholder.end += delta;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
removeRange(start, end) {
|
|
948
|
+
// Expand deletion to cover any placeholder that overlaps the range
|
|
949
|
+
let adjustedStart = start;
|
|
950
|
+
let adjustedEnd = end;
|
|
951
|
+
for (const ph of this.pastePlaceholders) {
|
|
952
|
+
if (ph.start < adjustedEnd && ph.end > adjustedStart) {
|
|
953
|
+
adjustedStart = Math.min(adjustedStart, ph.start);
|
|
954
|
+
adjustedEnd = Math.max(adjustedEnd, ph.end);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
const length = Math.max(0, adjustedEnd - adjustedStart);
|
|
958
|
+
if (length === 0)
|
|
959
|
+
return;
|
|
960
|
+
this.buffer = this.buffer.slice(0, adjustedStart) + this.buffer.slice(adjustedEnd);
|
|
961
|
+
// Remove any placeholders that were within the deleted range
|
|
962
|
+
this.pastePlaceholders = this.pastePlaceholders.filter((ph) => {
|
|
963
|
+
return ph.end <= adjustedStart || ph.start >= adjustedEnd;
|
|
964
|
+
});
|
|
965
|
+
// Shift remaining placeholders that were after the deleted range
|
|
966
|
+
this.shiftPlaceholders(adjustedEnd, -length);
|
|
967
|
+
this.clampCursor();
|
|
968
|
+
}
|
|
969
|
+
insertPlainText(text, position) {
|
|
970
|
+
if (!text)
|
|
971
|
+
return;
|
|
972
|
+
this.shiftPlaceholders(position, text.length);
|
|
973
|
+
this.buffer = this.buffer.slice(0, position) + text + this.buffer.slice(position);
|
|
974
|
+
}
|
|
975
|
+
findPlaceholderAt(position) {
|
|
976
|
+
return this.pastePlaceholders.find((ph) => position >= ph.start && position < ph.end) ?? null;
|
|
977
|
+
}
|
|
978
|
+
buildPlaceholder(lineCount) {
|
|
979
|
+
const id = ++this.pasteCounter;
|
|
980
|
+
const plural = lineCount === 1 ? '' : 's';
|
|
981
|
+
const placeholder = `[Pasted text #${id} +${lineCount} line${plural}]`;
|
|
982
|
+
return { id, placeholder };
|
|
983
|
+
}
|
|
984
|
+
insertPastePlaceholder(content) {
|
|
985
|
+
const available = this.config.maxLength - this.getComposedLength();
|
|
986
|
+
if (available <= 0)
|
|
987
|
+
return;
|
|
988
|
+
const cleanContent = content.slice(0, available);
|
|
989
|
+
const lineCount = cleanContent.split('\n').length;
|
|
990
|
+
const { id, placeholder } = this.buildPlaceholder(lineCount);
|
|
991
|
+
const insertPos = this.cursor;
|
|
992
|
+
this.shiftPlaceholders(insertPos, placeholder.length);
|
|
993
|
+
this.pastePlaceholders.push({
|
|
994
|
+
id,
|
|
995
|
+
content: cleanContent,
|
|
996
|
+
lineCount,
|
|
997
|
+
placeholder,
|
|
998
|
+
start: insertPos,
|
|
999
|
+
end: insertPos + placeholder.length,
|
|
1000
|
+
});
|
|
1001
|
+
this.buffer = this.buffer.slice(0, insertPos) + placeholder + this.buffer.slice(insertPos);
|
|
1002
|
+
this.cursor = insertPos + placeholder.length;
|
|
1003
|
+
}
|
|
1004
|
+
deletePlaceholder(placeholder) {
|
|
1005
|
+
const length = placeholder.end - placeholder.start;
|
|
1006
|
+
this.buffer = this.buffer.slice(0, placeholder.start) + this.buffer.slice(placeholder.end);
|
|
1007
|
+
this.pastePlaceholders = this.pastePlaceholders.filter((ph) => ph.id !== placeholder.id);
|
|
1008
|
+
this.shiftPlaceholders(placeholder.end, -length, placeholder.id);
|
|
1009
|
+
this.cursor = placeholder.start;
|
|
1010
|
+
}
|
|
1011
|
+
updateContextUsage(value) {
|
|
1012
|
+
if (value === null || !Number.isFinite(value)) {
|
|
1013
|
+
this.contextUsage = null;
|
|
1014
|
+
}
|
|
1015
|
+
else {
|
|
1016
|
+
const bounded = Math.max(0, Math.min(100, Math.round(value)));
|
|
1017
|
+
this.contextUsage = bounded;
|
|
1018
|
+
}
|
|
1019
|
+
this.scheduleRender();
|
|
1020
|
+
}
|
|
1021
|
+
getEditMode() {
|
|
1022
|
+
return this.editMode;
|
|
1023
|
+
}
|
|
1024
|
+
applyEditMode(mode) {
|
|
1025
|
+
this.setEditMode(mode);
|
|
1026
|
+
}
|
|
1027
|
+
setEditMode(mode) {
|
|
1028
|
+
if (this.editMode === mode)
|
|
1029
|
+
return;
|
|
1030
|
+
this.editMode = mode;
|
|
1031
|
+
this.emit('mode-change', this.editMode);
|
|
1032
|
+
this.scheduleRender();
|
|
1033
|
+
}
|
|
1034
|
+
toggleEditMode() {
|
|
1035
|
+
const next = this.editMode === 'display-edits' ? 'ask-permission' : 'display-edits';
|
|
1036
|
+
this.setEditMode(next);
|
|
1037
|
+
}
|
|
811
1038
|
scheduleRender() {
|
|
812
1039
|
if (!this.canRender())
|
|
813
1040
|
return;
|