@adhdev/daemon-core 0.6.55 → 0.6.56
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/index.d.ts +8 -0
- package/dist/index.js +144 -261
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/cli-adapters/provider-cli-adapter.ts +46 -8
- package/src/cli-adapters/terminal-screen.ts +66 -239
- package/src/daemon/dev-server.ts +69 -35
- package/src/providers/cli-provider-instance.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.56",
|
|
4
4
|
"description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"license": "AGPL-3.0-or-later",
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
34
|
+
"@xterm/xterm": "^6.0.0",
|
|
34
35
|
"chalk": "^5.3.0",
|
|
35
36
|
"conf": "^13.0.0",
|
|
36
37
|
"ws": "^8.19.0"
|
|
@@ -68,7 +68,7 @@ export interface CliScripts {
|
|
|
68
68
|
/** Full PTY buffer → ReadChatResult (messages, status, activeModal) */
|
|
69
69
|
parseOutput?: (input: CliScriptInput) => any;
|
|
70
70
|
/** Lightweight status detection (high-frequency polling) → AgentStatus string */
|
|
71
|
-
detectStatus?: (input: { tail: string }) => string | null;
|
|
71
|
+
detectStatus?: (input: { tail: string; screenText?: string; rawBuffer?: string }) => string | null;
|
|
72
72
|
/** Parse approval modal from PTY output → ModalInfo | null */
|
|
73
73
|
parseApproval?: (input: { buffer: string; rawBuffer?: string; tail: string }) => { message: string; buttons: string[] } | null;
|
|
74
74
|
/** Produce a cli-specific prompt from a dashboard action payload */
|
|
@@ -91,6 +91,8 @@ export interface CliProviderModule {
|
|
|
91
91
|
name: string;
|
|
92
92
|
category: 'cli';
|
|
93
93
|
binary: string;
|
|
94
|
+
sendDelayMs?: number;
|
|
95
|
+
sendKey?: string;
|
|
94
96
|
spawn: {
|
|
95
97
|
command: string;
|
|
96
98
|
args: string[];
|
|
@@ -270,6 +272,7 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
270
272
|
// Output settle debounce — fires after PTY output goes quiet
|
|
271
273
|
private settleTimer: NodeJS.Timeout | null = null;
|
|
272
274
|
private settledBuffer: string = '';
|
|
275
|
+
private submitPendingUntil = 0;
|
|
273
276
|
|
|
274
277
|
// Resize redraw suppression
|
|
275
278
|
private resizeSuppressUntil: number = 0;
|
|
@@ -302,6 +305,8 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
302
305
|
|
|
303
306
|
// Provider approval key mapping
|
|
304
307
|
private readonly approvalKeys: Record<number, string>;
|
|
308
|
+
private readonly sendDelayMs: number;
|
|
309
|
+
private readonly sendKey: string;
|
|
305
310
|
|
|
306
311
|
constructor(provider: CliProviderModule, workingDir: string, private extraArgs: string[] = []) {
|
|
307
312
|
this.provider = provider;
|
|
@@ -325,6 +330,10 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
325
330
|
|
|
326
331
|
const rawKeys = (provider as any).approvalKeys;
|
|
327
332
|
this.approvalKeys = (rawKeys && typeof rawKeys === 'object') ? rawKeys : {};
|
|
333
|
+
this.sendDelayMs = typeof (provider as any).sendDelayMs === 'number' ? Math.max(0, (provider as any).sendDelayMs) : 0;
|
|
334
|
+
this.sendKey = typeof (provider as any).sendKey === 'string' && (provider as any).sendKey.length > 0
|
|
335
|
+
? (provider as any).sendKey
|
|
336
|
+
: '\r';
|
|
328
337
|
|
|
329
338
|
// Scripts are required — loaded by ProviderLoader via compatibility array
|
|
330
339
|
this.cliScripts = (provider as any).scripts || {};
|
|
@@ -460,6 +469,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
460
469
|
private handleOutput(rawData: string): void {
|
|
461
470
|
if (Date.now() < this.resizeSuppressUntil) return;
|
|
462
471
|
|
|
472
|
+
if (rawData.includes('\x1b[6n') || rawData.includes('\x1b[?6n')) {
|
|
473
|
+
// Some TUIs probe cursor position during startup; node-pty does not answer automatically.
|
|
474
|
+
this.ptyProcess?.write('\x1b[1;1R');
|
|
475
|
+
}
|
|
476
|
+
|
|
463
477
|
this.terminalScreen.write(rawData);
|
|
464
478
|
const cleanData = stripAnsi(rawData);
|
|
465
479
|
|
|
@@ -519,11 +533,17 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
519
533
|
|
|
520
534
|
private scheduleSettle(): void {
|
|
521
535
|
if (this.settleTimer) clearTimeout(this.settleTimer);
|
|
536
|
+
const delay = Math.max(
|
|
537
|
+
this.timeouts.outputSettle,
|
|
538
|
+
this.submitPendingUntil > Date.now()
|
|
539
|
+
? (this.submitPendingUntil - Date.now()) + this.timeouts.outputSettle
|
|
540
|
+
: 0,
|
|
541
|
+
);
|
|
522
542
|
this.settleTimer = setTimeout(() => {
|
|
523
543
|
this.settleTimer = null;
|
|
524
544
|
this.settledBuffer = this.recentOutputBuffer;
|
|
525
545
|
this.evaluateSettled();
|
|
526
|
-
},
|
|
546
|
+
}, delay);
|
|
527
547
|
}
|
|
528
548
|
|
|
529
549
|
private evaluateSettled(): void {
|
|
@@ -612,7 +632,11 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
612
632
|
private runDetectStatus(text: string): string | null {
|
|
613
633
|
if (!this.cliScripts?.detectStatus) return null;
|
|
614
634
|
try {
|
|
615
|
-
return this.cliScripts.detectStatus({
|
|
635
|
+
return this.cliScripts.detectStatus({
|
|
636
|
+
tail: text.slice(-500),
|
|
637
|
+
screenText: this.terminalScreen.getText(),
|
|
638
|
+
rawBuffer: this.accumulatedRawBuffer,
|
|
639
|
+
});
|
|
616
640
|
} catch (e: any) {
|
|
617
641
|
LOG.warn('CLI', `[${this.cliType}] detectStatus error: ${e.message}`);
|
|
618
642
|
return null;
|
|
@@ -729,12 +753,22 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
729
753
|
this.responseBuffer = '';
|
|
730
754
|
this.setStatus('generating', 'sendMessage');
|
|
731
755
|
this.onStatusChange?.();
|
|
756
|
+
this.ptyProcess.write(text);
|
|
732
757
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
758
|
+
const submit = () => {
|
|
759
|
+
if (!this.ptyProcess) return;
|
|
760
|
+
this.submitPendingUntil = 0;
|
|
761
|
+
this.ptyProcess.write(this.sendKey);
|
|
762
|
+
this.responseTimeout = setTimeout(() => {
|
|
763
|
+
if (this.isWaitingForResponse) this.finishResponse();
|
|
764
|
+
}, this.timeouts.maxResponse);
|
|
765
|
+
};
|
|
766
|
+
if (this.sendDelayMs > 0) {
|
|
767
|
+
this.submitPendingUntil = Date.now() + this.sendDelayMs;
|
|
768
|
+
setTimeout(submit, this.sendDelayMs);
|
|
769
|
+
} else {
|
|
770
|
+
submit();
|
|
771
|
+
}
|
|
738
772
|
}
|
|
739
773
|
|
|
740
774
|
getPartialResponse(): string {
|
|
@@ -810,10 +844,14 @@ export class ProviderCliAdapter implements CliAdapter {
|
|
|
810
844
|
messages: this.messages.slice(-20),
|
|
811
845
|
structuredMessages: this.structuredMessages.slice(-20),
|
|
812
846
|
messageCount: this.messages.length,
|
|
847
|
+
screenText: this.terminalScreen.getText().slice(-4000),
|
|
813
848
|
startupBuffer: this.startupBuffer.slice(-4000),
|
|
814
849
|
recentOutputBuffer: this.recentOutputBuffer.slice(-500),
|
|
815
850
|
settledBuffer: this.settledBuffer.slice(-500),
|
|
816
851
|
accumulatedBufferLength: this.accumulatedBuffer.length,
|
|
852
|
+
accumulatedRawBufferLength: this.accumulatedRawBuffer.length,
|
|
853
|
+
rawBufferPreview: this.accumulatedRawBuffer.slice(-1000),
|
|
854
|
+
responseBuffer: this.responseBuffer.slice(-1000),
|
|
817
855
|
isWaitingForResponse: this.isWaitingForResponse,
|
|
818
856
|
activeModal: this.activeModal,
|
|
819
857
|
lastApprovalResolvedAt: this.lastApprovalResolvedAt,
|
|
@@ -1,268 +1,95 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* PTY screen snapshot backed by xterm's parser.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Claude Code and similar CLIs use a real terminal UI. A handwritten ANSI
|
|
5
|
+
* parser quickly drifts from reality, so we reuse xterm's terminal model and
|
|
6
|
+
* expose only the current visible viewport as plain text for provider scripts.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
type XtermBufferLine = {
|
|
10
|
+
translateToString(trimRight?: boolean): string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type XtermBuffer = {
|
|
14
|
+
length: number;
|
|
15
|
+
viewportY: number;
|
|
16
|
+
getLine(index: number): XtermBufferLine | undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type XtermTerminal = {
|
|
20
|
+
buffer: { active: XtermBuffer };
|
|
21
|
+
write(data: string, callback?: () => void): void;
|
|
22
|
+
resize(cols: number, rows: number): void;
|
|
23
|
+
dispose(): void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let TerminalCtor: (new (options: { cols: number; rows: number; scrollback: number }) => XtermTerminal) | null = null;
|
|
27
|
+
|
|
28
|
+
function loadTerminalCtor(): new (options: { cols: number; rows: number; scrollback: number }) => XtermTerminal {
|
|
29
|
+
if (!TerminalCtor) {
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
31
|
+
const mod = require('@xterm/xterm');
|
|
32
|
+
TerminalCtor = mod.Terminal || mod.default?.Terminal || mod.default;
|
|
33
|
+
if (!TerminalCtor) {
|
|
34
|
+
throw new Error('@xterm/xterm Terminal export not found');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return TerminalCtor;
|
|
11
38
|
}
|
|
12
39
|
|
|
13
40
|
export class TerminalScreen {
|
|
14
41
|
private rows: number;
|
|
15
42
|
private cols: number;
|
|
16
|
-
private
|
|
17
|
-
private cursorCol = 0;
|
|
18
|
-
private savedRow = 0;
|
|
19
|
-
private savedCol = 0;
|
|
20
|
-
private lines: string[][];
|
|
43
|
+
private terminal: XtermTerminal;
|
|
21
44
|
|
|
22
45
|
constructor(rows = 40, cols = 120) {
|
|
23
|
-
this.rows = rows;
|
|
24
|
-
this.cols = cols;
|
|
25
|
-
this.
|
|
46
|
+
this.rows = Math.max(1, rows | 0);
|
|
47
|
+
this.cols = Math.max(1, cols | 0);
|
|
48
|
+
this.terminal = this.createTerminal();
|
|
26
49
|
}
|
|
27
50
|
|
|
28
51
|
reset(rows = this.rows, cols = this.cols): void {
|
|
29
|
-
this.rows = rows;
|
|
30
|
-
this.cols = cols;
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
33
|
-
this.savedRow = 0;
|
|
34
|
-
this.savedCol = 0;
|
|
35
|
-
this.lines = this.makeLines(rows, cols);
|
|
52
|
+
this.rows = Math.max(1, rows | 0);
|
|
53
|
+
this.cols = Math.max(1, cols | 0);
|
|
54
|
+
this.terminal.dispose();
|
|
55
|
+
this.terminal = this.createTerminal();
|
|
36
56
|
}
|
|
37
57
|
|
|
38
58
|
resize(rows: number, cols: number): void {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const copyRows = Math.min(this.rows, nextRows);
|
|
43
|
-
const copyCols = Math.min(this.cols, nextCols);
|
|
44
|
-
for (let r = 0; r < copyRows; r++) {
|
|
45
|
-
for (let c = 0; c < copyCols; c++) {
|
|
46
|
-
next[r][c] = this.lines[r][c];
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
this.rows = nextRows;
|
|
50
|
-
this.cols = nextCols;
|
|
51
|
-
this.lines = next;
|
|
52
|
-
this.cursorRow = clamp(this.cursorRow, 0, this.rows - 1);
|
|
53
|
-
this.cursorCol = clamp(this.cursorCol, 0, this.cols - 1);
|
|
54
|
-
this.savedRow = clamp(this.savedRow, 0, this.rows - 1);
|
|
55
|
-
this.savedCol = clamp(this.savedCol, 0, this.cols - 1);
|
|
59
|
+
this.rows = Math.max(1, rows | 0);
|
|
60
|
+
this.cols = Math.max(1, cols | 0);
|
|
61
|
+
this.terminal.resize(this.cols, this.rows);
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
write(data: string): void {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const ch = data[i];
|
|
62
|
-
|
|
63
|
-
if (ch === '\x1b') {
|
|
64
|
-
const consumed = this.consumeEscape(data, i);
|
|
65
|
-
i = consumed > i ? consumed : i + 1;
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (ch === '\r') {
|
|
70
|
-
this.cursorCol = 0;
|
|
71
|
-
i++;
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (ch === '\n') {
|
|
76
|
-
this.newLine();
|
|
77
|
-
i++;
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (ch === '\b') {
|
|
82
|
-
this.cursorCol = Math.max(0, this.cursorCol - 1);
|
|
83
|
-
i++;
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (ch === '\t') {
|
|
88
|
-
const nextStop = Math.min(this.cols - 1, this.cursorCol + (8 - (this.cursorCol % 8 || 8)));
|
|
89
|
-
while (this.cursorCol < nextStop) this.putChar(' ');
|
|
90
|
-
i++;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (ch >= ' ' && ch !== '\x7f') {
|
|
95
|
-
this.putChar(ch);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
i++;
|
|
99
|
-
}
|
|
65
|
+
if (!data) return;
|
|
66
|
+
this.terminal.write(data);
|
|
100
67
|
}
|
|
101
68
|
|
|
102
69
|
getText(): string {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
private consumeEscape(data: string, start: number): number {
|
|
112
|
-
const next = data[start + 1];
|
|
113
|
-
if (!next) return start + 1;
|
|
114
|
-
|
|
115
|
-
if (next === '[') {
|
|
116
|
-
let end = start + 2;
|
|
117
|
-
while (end < data.length && !/[@-~]/.test(data[end])) end++;
|
|
118
|
-
if (end >= data.length) return data.length;
|
|
119
|
-
this.applyCsi(data.slice(start + 2, end), data[end]);
|
|
120
|
-
return end + 1;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (next === ']') {
|
|
124
|
-
let end = start + 2;
|
|
125
|
-
while (end < data.length) {
|
|
126
|
-
if (data[end] === '\x07') return end + 1;
|
|
127
|
-
if (data[end] === '\x1b' && data[end + 1] === '\\') return end + 2;
|
|
128
|
-
end++;
|
|
129
|
-
}
|
|
130
|
-
return data.length;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (next === '7') {
|
|
134
|
-
this.savedRow = this.cursorRow;
|
|
135
|
-
this.savedCol = this.cursorCol;
|
|
136
|
-
return start + 2;
|
|
137
|
-
}
|
|
138
|
-
if (next === '8') {
|
|
139
|
-
this.cursorRow = this.savedRow;
|
|
140
|
-
this.cursorCol = this.savedCol;
|
|
141
|
-
return start + 2;
|
|
70
|
+
const buffer = this.terminal.buffer.active;
|
|
71
|
+
const start = Math.max(0, buffer.viewportY || 0);
|
|
72
|
+
const end = Math.max(start, Math.min(buffer.length || 0, start + this.rows));
|
|
73
|
+
const lines: string[] = [];
|
|
74
|
+
|
|
75
|
+
for (let i = start; i < end; i++) {
|
|
76
|
+
const line = buffer.getLine(i);
|
|
77
|
+
lines.push(line ? line.translateToString(true) : '');
|
|
142
78
|
}
|
|
143
79
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const normalized = privateMode ? paramText.slice(1) : paramText;
|
|
150
|
-
const params = normalized.length > 0
|
|
151
|
-
? normalized.split(';').map(p => parseInt(p || '0', 10) || 0)
|
|
152
|
-
: [0];
|
|
153
|
-
|
|
154
|
-
switch (finalChar) {
|
|
155
|
-
case 'A':
|
|
156
|
-
this.cursorRow = clamp(this.cursorRow - (params[0] || 1), 0, this.rows - 1);
|
|
157
|
-
return;
|
|
158
|
-
case 'B':
|
|
159
|
-
this.cursorRow = clamp(this.cursorRow + (params[0] || 1), 0, this.rows - 1);
|
|
160
|
-
return;
|
|
161
|
-
case 'C':
|
|
162
|
-
this.cursorCol = clamp(this.cursorCol + (params[0] || 1), 0, this.cols - 1);
|
|
163
|
-
return;
|
|
164
|
-
case 'D':
|
|
165
|
-
this.cursorCol = clamp(this.cursorCol - (params[0] || 1), 0, this.cols - 1);
|
|
166
|
-
return;
|
|
167
|
-
case 'E':
|
|
168
|
-
this.cursorRow = clamp(this.cursorRow + (params[0] || 1), 0, this.rows - 1);
|
|
169
|
-
this.cursorCol = 0;
|
|
170
|
-
return;
|
|
171
|
-
case 'F':
|
|
172
|
-
this.cursorRow = clamp(this.cursorRow - (params[0] || 1), 0, this.rows - 1);
|
|
173
|
-
this.cursorCol = 0;
|
|
174
|
-
return;
|
|
175
|
-
case 'G':
|
|
176
|
-
this.cursorCol = clamp((params[0] || 1) - 1, 0, this.cols - 1);
|
|
177
|
-
return;
|
|
178
|
-
case 'H':
|
|
179
|
-
case 'f': {
|
|
180
|
-
const row = (params[0] || 1) - 1;
|
|
181
|
-
const col = (params[1] || 1) - 1;
|
|
182
|
-
this.cursorRow = clamp(row, 0, this.rows - 1);
|
|
183
|
-
this.cursorCol = clamp(col, 0, this.cols - 1);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
case 'J': {
|
|
187
|
-
const mode = params[0] || 0;
|
|
188
|
-
if (mode === 2 || mode === 3) {
|
|
189
|
-
this.reset(this.rows, this.cols);
|
|
190
|
-
} else if (mode === 0) {
|
|
191
|
-
this.clearToEndOfScreen();
|
|
192
|
-
} else if (mode === 1) {
|
|
193
|
-
this.clearToStartOfScreen();
|
|
194
|
-
}
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
case 'K': {
|
|
198
|
-
const mode = params[0] || 0;
|
|
199
|
-
if (mode === 2) this.clearLine(this.cursorRow, 0, this.cols - 1);
|
|
200
|
-
else if (mode === 1) this.clearLine(this.cursorRow, 0, this.cursorCol);
|
|
201
|
-
else this.clearLine(this.cursorRow, this.cursorCol, this.cols - 1);
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
case 'm':
|
|
205
|
-
return;
|
|
206
|
-
case 's':
|
|
207
|
-
this.savedRow = this.cursorRow;
|
|
208
|
-
this.savedCol = this.cursorCol;
|
|
209
|
-
return;
|
|
210
|
-
case 'u':
|
|
211
|
-
this.cursorRow = this.savedRow;
|
|
212
|
-
this.cursorCol = this.savedCol;
|
|
213
|
-
return;
|
|
214
|
-
case 'h':
|
|
215
|
-
case 'l':
|
|
216
|
-
if (privateMode && (normalized === '1049' || normalized === '47')) {
|
|
217
|
-
this.reset(this.rows, this.cols);
|
|
218
|
-
}
|
|
219
|
-
return;
|
|
220
|
-
default:
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
private putChar(ch: string): void {
|
|
226
|
-
if (this.cursorRow < 0 || this.cursorRow >= this.rows) return;
|
|
227
|
-
if (this.cursorCol < 0) this.cursorCol = 0;
|
|
228
|
-
if (this.cursorCol >= this.cols) this.newLine();
|
|
229
|
-
this.lines[this.cursorRow][this.cursorCol] = ch;
|
|
230
|
-
this.cursorCol++;
|
|
231
|
-
if (this.cursorCol >= this.cols) this.newLine();
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
private newLine(): void {
|
|
235
|
-
this.cursorCol = 0;
|
|
236
|
-
if (this.cursorRow >= this.rows - 1) {
|
|
237
|
-
this.lines.shift();
|
|
238
|
-
this.lines.push(Array.from({ length: this.cols }, () => ' '));
|
|
239
|
-
} else {
|
|
240
|
-
this.cursorRow++;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
private clearLine(row: number, start: number, end: number): void {
|
|
245
|
-
if (row < 0 || row >= this.rows) return;
|
|
246
|
-
for (let c = clamp(start, 0, this.cols - 1); c <= clamp(end, 0, this.cols - 1); c++) {
|
|
247
|
-
this.lines[row][c] = ' ';
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
private clearToEndOfScreen(): void {
|
|
252
|
-
this.clearLine(this.cursorRow, this.cursorCol, this.cols - 1);
|
|
253
|
-
for (let r = this.cursorRow + 1; r < this.rows; r++) {
|
|
254
|
-
this.clearLine(r, 0, this.cols - 1);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private clearToStartOfScreen(): void {
|
|
259
|
-
for (let r = 0; r < this.cursorRow; r++) {
|
|
260
|
-
this.clearLine(r, 0, this.cols - 1);
|
|
261
|
-
}
|
|
262
|
-
this.clearLine(this.cursorRow, 0, this.cursorCol);
|
|
80
|
+
let first = 0;
|
|
81
|
+
let last = lines.length;
|
|
82
|
+
while (first < last && !lines[first]?.trim()) first++;
|
|
83
|
+
while (last > first && !lines[last - 1]?.trim()) last--;
|
|
84
|
+
return lines.slice(first, last).join('\n');
|
|
263
85
|
}
|
|
264
86
|
|
|
265
|
-
private
|
|
266
|
-
|
|
87
|
+
private createTerminal(): XtermTerminal {
|
|
88
|
+
const Terminal = loadTerminalCtor();
|
|
89
|
+
return new Terminal({
|
|
90
|
+
cols: this.cols,
|
|
91
|
+
rows: this.rows,
|
|
92
|
+
scrollback: 2000,
|
|
93
|
+
});
|
|
267
94
|
}
|
|
268
95
|
}
|