Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/agentHost/test/node/osc633Parser.test.ts
13399 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
8
import { Osc633EventType, Osc633Parser } from '../../node/osc633Parser.js';
9
10
suite('Osc633Parser', () => {
11
12
ensureNoDisposablesAreLeakedInTestSuite();
13
14
let parser: Osc633Parser;
15
16
setup(() => {
17
parser = new Osc633Parser();
18
});
19
20
// -- Helper to build OSC 633 sequences --------------------------------
21
22
function osc633(payload: string, terminator: 'bel' | 'st' = 'bel'): string {
23
const term = terminator === 'bel' ? '\x07' : '\x1b\\';
24
return `\x1b]633;${payload}${term}`;
25
}
26
27
// -- Basic sequence extraction ----------------------------------------
28
29
test('no sequences returns data unchanged with no events', () => {
30
const result = parser.parse('hello world');
31
assert.deepStrictEqual(result, {
32
cleanedData: 'hello world',
33
events: [],
34
});
35
});
36
37
test('PromptStart (A) with BEL terminator', () => {
38
const result = parser.parse(`before${osc633('A')}after`);
39
assert.deepStrictEqual(result, {
40
cleanedData: 'beforeafter',
41
events: [{ type: Osc633EventType.PromptStart }],
42
});
43
});
44
45
test('CommandStart (B)', () => {
46
const result = parser.parse(osc633('B'));
47
assert.deepStrictEqual(result, {
48
cleanedData: '',
49
events: [{ type: Osc633EventType.CommandStart }],
50
});
51
});
52
53
test('CommandExecuted (C)', () => {
54
const result = parser.parse(osc633('C'));
55
assert.deepStrictEqual(result, {
56
cleanedData: '',
57
events: [{ type: Osc633EventType.CommandExecuted }],
58
});
59
});
60
61
test('CommandFinished (D) without exit code', () => {
62
const result = parser.parse(osc633('D'));
63
assert.deepStrictEqual(result, {
64
cleanedData: '',
65
events: [{ type: Osc633EventType.CommandFinished, exitCode: undefined }],
66
});
67
});
68
69
test('CommandFinished (D) with exit code 0', () => {
70
const result = parser.parse(osc633('D;0'));
71
assert.deepStrictEqual(result, {
72
cleanedData: '',
73
events: [{ type: Osc633EventType.CommandFinished, exitCode: 0 }],
74
});
75
});
76
77
test('CommandFinished (D) with non-zero exit code', () => {
78
const result = parser.parse(osc633('D;127'));
79
assert.deepStrictEqual(result, {
80
cleanedData: '',
81
events: [{ type: Osc633EventType.CommandFinished, exitCode: 127 }],
82
});
83
});
84
85
test('CommandLine (E) with command and nonce', () => {
86
const result = parser.parse(osc633('E;echo\\x20hello;my-nonce'));
87
assert.deepStrictEqual(result, {
88
cleanedData: '',
89
events: [{
90
type: Osc633EventType.CommandLine,
91
commandLine: 'echo hello',
92
nonce: 'my-nonce',
93
}],
94
});
95
});
96
97
test('CommandLine (E) without nonce', () => {
98
const result = parser.parse(osc633('E;ls\\x20-la'));
99
assert.deepStrictEqual(result, {
100
cleanedData: '',
101
events: [{
102
type: Osc633EventType.CommandLine,
103
commandLine: 'ls -la',
104
nonce: undefined,
105
}],
106
});
107
});
108
109
test('CommandLine (E) with escaped backslash', () => {
110
const result = parser.parse(osc633('E;echo\\x20\\\\hello'));
111
assert.deepStrictEqual(result, {
112
cleanedData: '',
113
events: [{
114
type: Osc633EventType.CommandLine,
115
commandLine: 'echo \\hello',
116
nonce: undefined,
117
}],
118
});
119
});
120
121
test('Property (P) Cwd', () => {
122
const result = parser.parse(osc633('P;Cwd=/home/user'));
123
assert.deepStrictEqual(result, {
124
cleanedData: '',
125
events: [{
126
type: Osc633EventType.Property,
127
key: 'Cwd',
128
value: '/home/user',
129
}],
130
});
131
});
132
133
test('Property (P) without value is ignored', () => {
134
const result = parser.parse(osc633('P;NoEquals'));
135
assert.deepStrictEqual(result, {
136
cleanedData: '',
137
events: [],
138
});
139
});
140
141
// -- ST terminator ----------------------------------------------------
142
143
test('PromptStart (A) with ST terminator', () => {
144
const result = parser.parse(`before${osc633('A', 'st')}after`);
145
assert.deepStrictEqual(result, {
146
cleanedData: 'beforeafter',
147
events: [{ type: Osc633EventType.PromptStart }],
148
});
149
});
150
151
// -- Multiple sequences in one chunk ----------------------------------
152
153
test('multiple sequences in one chunk', () => {
154
const data = `prompt${osc633('A')}$ ${osc633('B')}${osc633('E;ls;nonce1')}${osc633('C')}file1\nfile2\n${osc633('D;0')}`;
155
const result = parser.parse(data);
156
assert.deepStrictEqual(result, {
157
cleanedData: 'prompt$ file1\nfile2\n',
158
events: [
159
{ type: Osc633EventType.PromptStart },
160
{ type: Osc633EventType.CommandStart },
161
{ type: Osc633EventType.CommandLine, commandLine: 'ls', nonce: 'nonce1' },
162
{ type: Osc633EventType.CommandExecuted },
163
{ type: Osc633EventType.CommandFinished, exitCode: 0 },
164
],
165
});
166
});
167
168
// -- Non-633 OSC sequences are preserved ------------------------------
169
170
test('non-633 OSC sequences are preserved in output', () => {
171
const nonOsc = '\x1b]0;window title\x07';
172
const result = parser.parse(`before${nonOsc}after`);
173
assert.deepStrictEqual(result, {
174
cleanedData: `before${nonOsc}after`,
175
events: [],
176
});
177
});
178
179
test('non-633 OSC sequences preserve ST terminator in output', () => {
180
const nonOsc = '\x1b]0;window title\x1b\\';
181
const result = parser.parse(`before${nonOsc}after`);
182
assert.deepStrictEqual(result, {
183
cleanedData: `before${nonOsc}after`,
184
events: [],
185
});
186
});
187
188
// -- Partial sequences across chunks ----------------------------------
189
190
test('sequence split across two chunks (split in payload)', () => {
191
const r1 = parser.parse('before\x1b]633;');
192
assert.strictEqual(r1.cleanedData, 'before');
193
assert.deepStrictEqual(r1.events, []);
194
195
const r2 = parser.parse('A\x07after');
196
assert.strictEqual(r2.cleanedData, 'after');
197
assert.deepStrictEqual(r2.events, [{ type: Osc633EventType.PromptStart }]);
198
});
199
200
test('sequence split across two chunks (split at ESC of ST terminator)', () => {
201
// First chunk ends with ESC (potential start of ST)
202
const r1 = parser.parse('data\x1b]633;D;42\x1b');
203
assert.strictEqual(r1.cleanedData, 'data');
204
assert.deepStrictEqual(r1.events, []);
205
206
// Second chunk starts with \ (completing ST)
207
const r2 = parser.parse('\\more');
208
assert.strictEqual(r2.cleanedData, 'more');
209
assert.deepStrictEqual(r2.events, [{ type: Osc633EventType.CommandFinished, exitCode: 42 }]);
210
});
211
212
test('non-633 OSC sequence split at ESC of ST terminator preserves ST', () => {
213
const r1 = parser.parse('before\x1b]0;window title\x1b');
214
assert.strictEqual(r1.cleanedData, 'before');
215
assert.deepStrictEqual(r1.events, []);
216
217
const r2 = parser.parse('\\after');
218
assert.strictEqual(r2.cleanedData, '\x1b]0;window title\x1b\\after');
219
assert.deepStrictEqual(r2.events, []);
220
});
221
222
test('sequence split across three chunks', () => {
223
const r1 = parser.parse('\x1b]63');
224
assert.strictEqual(r1.cleanedData, '');
225
assert.deepStrictEqual(r1.events, []);
226
227
const r2 = parser.parse('3;C');
228
assert.strictEqual(r2.cleanedData, '');
229
assert.deepStrictEqual(r2.events, []);
230
231
const r3 = parser.parse('\x07output');
232
assert.strictEqual(r3.cleanedData, 'output');
233
assert.deepStrictEqual(r3.events, [{ type: Osc633EventType.CommandExecuted }]);
234
});
235
236
// -- Full command lifecycle -------------------------------------------
237
238
test('full command lifecycle with interleaved data', () => {
239
const allEvents: typeof result.events = [];
240
let allCleaned = '';
241
242
// Prompt appears
243
let result = parser.parse(`${osc633('A')}user@host:~ $ ${osc633('B')}`);
244
allEvents.push(...result.events);
245
allCleaned += result.cleanedData;
246
247
// User types command, shell reports it and executes
248
result = parser.parse(`${osc633('E;echo\\x20hi;nonce1')}${osc633('C')}`);
249
allEvents.push(...result.events);
250
allCleaned += result.cleanedData;
251
252
// Command output
253
result = parser.parse('hi\r\n');
254
allEvents.push(...result.events);
255
allCleaned += result.cleanedData;
256
257
// Command finishes
258
result = parser.parse(`${osc633('D;0')}${osc633('A')}`);
259
allEvents.push(...result.events);
260
allCleaned += result.cleanedData;
261
262
assert.strictEqual(allCleaned, 'user@host:~ $ hi\r\n');
263
assert.deepStrictEqual(allEvents, [
264
{ type: Osc633EventType.PromptStart },
265
{ type: Osc633EventType.CommandStart },
266
{ type: Osc633EventType.CommandLine, commandLine: 'echo hi', nonce: 'nonce1' },
267
{ type: Osc633EventType.CommandExecuted },
268
{ type: Osc633EventType.CommandFinished, exitCode: 0 },
269
{ type: Osc633EventType.PromptStart },
270
]);
271
});
272
273
// -- Edge cases -------------------------------------------------------
274
275
test('empty string', () => {
276
const result = parser.parse('');
277
assert.deepStrictEqual(result, { cleanedData: '', events: [] });
278
});
279
280
test('data with regular ANSI escape sequences (non-OSC) passes through', () => {
281
const ansi = '\x1b[31mred\x1b[0m';
282
const result = parser.parse(ansi);
283
assert.deepStrictEqual(result, { cleanedData: ansi, events: [] });
284
});
285
286
test('CommandLine (E) with semicolons in command', () => {
287
// Semicolons in the command line should be escaped as \x3b
288
const result = parser.parse(osc633('E;echo\\x3b\\x20hello;my-nonce'));
289
assert.deepStrictEqual(result, {
290
cleanedData: '',
291
events: [{
292
type: Osc633EventType.CommandLine,
293
commandLine: 'echo; hello',
294
nonce: 'my-nonce',
295
}],
296
});
297
});
298
});
299
300