@opensumi/ide-terminal-next 2.18.13 → 2.19.1

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.
Files changed (43) hide show
  1. package/lib/browser/component/tab.item.d.ts.map +1 -1
  2. package/lib/browser/component/tab.item.js +2 -1
  3. package/lib/browser/component/tab.item.js.map +1 -1
  4. package/lib/browser/component/tab.module.less +15 -6
  5. package/lib/browser/contribution/terminal.command.d.ts +1 -0
  6. package/lib/browser/contribution/terminal.command.d.ts.map +1 -1
  7. package/lib/browser/contribution/terminal.command.js +8 -5
  8. package/lib/browser/contribution/terminal.command.js.map +1 -1
  9. package/lib/browser/contribution/terminal.menu.js +4 -4
  10. package/lib/browser/contribution/terminal.menu.js.map +1 -1
  11. package/lib/browser/contribution/terminal.preference.d.ts +3 -0
  12. package/lib/browser/contribution/terminal.preference.d.ts.map +1 -1
  13. package/lib/browser/contribution/terminal.preference.js +24 -1
  14. package/lib/browser/contribution/terminal.preference.js.map +1 -1
  15. package/lib/browser/terminal.addon.d.ts +4 -0
  16. package/lib/browser/terminal.addon.d.ts.map +1 -1
  17. package/lib/browser/terminal.addon.js +14 -3
  18. package/lib/browser/terminal.addon.js.map +1 -1
  19. package/lib/browser/terminal.client.d.ts +2 -2
  20. package/lib/browser/terminal.client.d.ts.map +1 -1
  21. package/lib/browser/terminal.client.js +16 -4
  22. package/lib/browser/terminal.client.js.map +1 -1
  23. package/lib/browser/terminal.preference.d.ts.map +1 -1
  24. package/lib/browser/terminal.preference.js +6 -1
  25. package/lib/browser/terminal.preference.js.map +1 -1
  26. package/lib/browser/terminal.profile.internal.js +1 -1
  27. package/lib/browser/terminal.profile.internal.js.map +1 -1
  28. package/lib/browser/terminal.typeAhead.addon.d.ts +372 -0
  29. package/lib/browser/terminal.typeAhead.addon.d.ts.map +1 -0
  30. package/lib/browser/terminal.typeAhead.addon.js +1349 -0
  31. package/lib/browser/terminal.typeAhead.addon.js.map +1 -0
  32. package/lib/browser/terminal.typeAhead.ext.d.ts +587 -0
  33. package/lib/browser/terminal.typeAhead.ext.d.ts.map +1 -0
  34. package/lib/browser/terminal.typeAhead.ext.js +524 -0
  35. package/lib/browser/terminal.typeAhead.ext.js.map +1 -0
  36. package/lib/browser/xterm.d.ts.map +1 -1
  37. package/lib/browser/xterm.js +6 -2
  38. package/lib/browser/xterm.js.map +1 -1
  39. package/lib/common/preference.d.ts +8 -1
  40. package/lib/common/preference.d.ts.map +1 -1
  41. package/lib/common/preference.js +42 -12
  42. package/lib/common/preference.js.map +1 -1
  43. package/package.json +18 -18
@@ -0,0 +1,1349 @@
1
+ "use strict";
2
+ /* eslint-disable no-console */
3
+ /* eslint-disable @typescript-eslint/no-inferrable-types */
4
+ /* eslint-disable @typescript-eslint/quotes */
5
+ /* eslint-disable arrow-parens */
6
+ /* ---------------------------------------------------------------------------------------------
7
+ * Copyright (c) Microsoft Corporation. All rights reserved.
8
+ * Licensed under the MIT License. See License.txt in the project root for license information.
9
+ *--------------------------------------------------------------------------------------------*/
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.TypeAheadAddon = exports.PredictionTimeline = exports.PredictionStats = exports.isTenativeCharacterPrediction = exports.escapeRegExpCharacters = exports.DEFAULT_LOCAL_ECHO_EXCLUDE = void 0;
12
+ const tslib_1 = require("tslib");
13
+ const ide_core_common_1 = require("@opensumi/ide-core-common");
14
+ const terminal_typeAhead_ext_1 = require("./terminal.typeAhead.ext");
15
+ exports.DEFAULT_LOCAL_ECHO_EXCLUDE = ['vim', 'vi', 'nano', 'tmux'];
16
+ /**
17
+ * Escapes regular expression characters in a given string
18
+ */
19
+ function escapeRegExpCharacters(value) {
20
+ return value.replace(/[\\\{\}\*\+\?\|\^\$\.\[\]\(\)]/g, '\\$&');
21
+ }
22
+ exports.escapeRegExpCharacters = escapeRegExpCharacters;
23
+ const ESC = '\x1b';
24
+ const CSI = `${ESC}[`;
25
+ const SHOW_CURSOR = `${CSI}?25h`;
26
+ const HIDE_CURSOR = `${CSI}?25l`;
27
+ const DELETE_CHAR = `${CSI}X`;
28
+ const DELETE_REST_OF_LINE = `${CSI}K`;
29
+ const CSI_STYLE_RE = /^\x1b\[[0-9;]*m/;
30
+ const CSI_MOVE_RE = /^\x1b\[?([0-9]*)(;[35])?O?([DC])/;
31
+ const NOT_WORD_RE = /[^a-z0-9]/i;
32
+ const statsBufferSize = 24;
33
+ const statsSendTelemetryEvery = 1000 * 60 * 5; // how often to collect stats
34
+ const statsMinSamplesToTurnOn = 5;
35
+ const statsMinAccuracyToTurnOn = 0.3;
36
+ const statsToggleOffThreshold = 0.5; // if latency is less than `threshold * this`, turn off
37
+ /**
38
+ * Codes that should be omitted from sending to the prediction engine and instead omitted directly:
39
+ * - Hide cursor (DECTCEM): We wrap the local echo sequence in hide and show
40
+ * CSI ? 2 5 l
41
+ * - Show cursor (DECTCEM): We wrap the local echo sequence in hide and show
42
+ * CSI ? 2 5 h
43
+ * - Device Status Report (DSR): These sequence fire report events from xterm which could cause
44
+ * double reporting and potentially a stack overflow (#119472)
45
+ * CSI Ps n
46
+ * CSI ? Ps n
47
+ */
48
+ const PREDICTION_OMIT_RE = /^(\x1b\[(\??25[hl]|\??[0-9;]+n))+/;
49
+ const core = (terminal) => terminal._core;
50
+ const flushOutput = (terminal) => {
51
+ // TODO: Flushing output is not possible anymore without async
52
+ };
53
+ // 本地变量控制是否打开Log,因为这部分Log目前不需要落盘
54
+ // TODO: DEBUG环境监测来自动化本变量
55
+ const SHOW_DEBUG_LOG = false;
56
+ const typeAheadLogger = (...message) => {
57
+ if (SHOW_DEBUG_LOG) {
58
+ console.log(message);
59
+ }
60
+ };
61
+ class Cursor {
62
+ constructor(rows, cols, _buffer) {
63
+ this.rows = rows;
64
+ this.cols = cols;
65
+ this._buffer = _buffer;
66
+ this._x = 0;
67
+ this._y = 1;
68
+ this._baseY = 1;
69
+ this._x = _buffer.cursorX;
70
+ this._y = _buffer.cursorY;
71
+ this._baseY = _buffer.baseY;
72
+ }
73
+ get x() {
74
+ return this._x;
75
+ }
76
+ get y() {
77
+ return this._y;
78
+ }
79
+ get baseY() {
80
+ return this._baseY;
81
+ }
82
+ get coordinate() {
83
+ return { x: this._x, y: this._y, baseY: this._baseY };
84
+ }
85
+ getLine() {
86
+ return this._buffer.getLine(this._y + this._baseY);
87
+ }
88
+ getCell(loadInto) {
89
+ var _a;
90
+ return (_a = this.getLine()) === null || _a === void 0 ? void 0 : _a.getCell(this._x, loadInto);
91
+ }
92
+ moveTo(coordinate) {
93
+ this._x = coordinate.x;
94
+ this._y = coordinate.y + coordinate.baseY - this._baseY;
95
+ return this.moveInstruction();
96
+ }
97
+ clone() {
98
+ const c = new Cursor(this.rows, this.cols, this._buffer);
99
+ c.moveTo(this);
100
+ return c;
101
+ }
102
+ move(x, y) {
103
+ this._x = x;
104
+ this._y = y;
105
+ return this.moveInstruction();
106
+ }
107
+ shift(x = 0, y = 0) {
108
+ this._x += x;
109
+ this._y += y;
110
+ return this.moveInstruction();
111
+ }
112
+ moveInstruction() {
113
+ // 如果 y 坐标大于xterm的rows(一屏显示行数), 做一下额外处理
114
+ if (this._y >= this.rows) {
115
+ this._baseY += this._y - (this.rows - 1);
116
+ this._y = this.rows - 1;
117
+ }
118
+ else if (this._y < 0) {
119
+ this._baseY -= this._y;
120
+ this._y = 0;
121
+ }
122
+ // 坐标都加1,因为xterm的cursorX,Y是从0开始,而CSI的坐标控制是从1开始
123
+ return `${CSI}${this._y + 1};${this._x + 1}H`;
124
+ }
125
+ }
126
+ const moveToWordBoundary = (b, cursor, direction) => {
127
+ let ateLeadingWhitespace = false;
128
+ if (direction < 0) {
129
+ cursor.shift(-1);
130
+ }
131
+ let cell;
132
+ while (cursor.x >= 0) {
133
+ cell = cursor.getCell(cell);
134
+ if (!(cell === null || cell === void 0 ? void 0 : cell.getCode())) {
135
+ return;
136
+ }
137
+ const chars = cell.getChars();
138
+ if (NOT_WORD_RE.test(chars)) {
139
+ if (ateLeadingWhitespace) {
140
+ break;
141
+ }
142
+ }
143
+ else {
144
+ ateLeadingWhitespace = true;
145
+ }
146
+ cursor.shift(direction);
147
+ }
148
+ if (direction < 0) {
149
+ cursor.shift(1); // we want to place the cursor after the whitespace starting the word
150
+ }
151
+ };
152
+ class StringReader {
153
+ constructor(_input) {
154
+ this._input = _input;
155
+ this.index = 0;
156
+ }
157
+ get remaining() {
158
+ return this._input.length - this.index;
159
+ }
160
+ get eof() {
161
+ return this.index === this._input.length;
162
+ }
163
+ get rest() {
164
+ return this._input.slice(this.index);
165
+ }
166
+ /**
167
+ * Advances the reader and returns the character if it matches.
168
+ */
169
+ eatChar(char) {
170
+ if (this._input[this.index] !== char) {
171
+ return;
172
+ }
173
+ this.index++;
174
+ return char;
175
+ }
176
+ /**
177
+ * Advances the reader and returns the string if it matches.
178
+ */
179
+ eatStr(substr) {
180
+ if (this._input.slice(this.index, substr.length) !== substr) {
181
+ return;
182
+ }
183
+ this.index += substr.length;
184
+ return substr;
185
+ }
186
+ /**
187
+ * Matches and eats the substring character-by-character. If EOF is reached
188
+ * before the substring is consumed, it will buffer. Index is not moved
189
+ * if it's not a match.
190
+ */
191
+ eatGradually(substr) {
192
+ const prevIndex = this.index;
193
+ for (let i = 0; i < substr.length; i++) {
194
+ if (i > 0 && this.eof) {
195
+ return 2 /* Buffer */;
196
+ }
197
+ if (!this.eatChar(substr[i])) {
198
+ this.index = prevIndex;
199
+ return 1 /* Failure */;
200
+ }
201
+ }
202
+ return 0 /* Success */;
203
+ }
204
+ /**
205
+ * Advances the reader and returns the regex if it matches.
206
+ */
207
+ eatRe(re) {
208
+ const match = re.exec(this._input.slice(this.index));
209
+ if (!match) {
210
+ return;
211
+ }
212
+ this.index += match[0].length;
213
+ return match;
214
+ }
215
+ /**
216
+ * Advances the reader and returns the character if the code matches.
217
+ */
218
+ eatCharCode(min = 0, max = min + 1) {
219
+ const code = this._input.charCodeAt(this.index);
220
+ if (code < min || code >= max) {
221
+ return undefined;
222
+ }
223
+ this.index++;
224
+ return code;
225
+ }
226
+ }
227
+ /**
228
+ * Preidction which never tests true. Will always discard predictions made
229
+ * after it.
230
+ */
231
+ class HardBoundary {
232
+ constructor() {
233
+ this.clearAfterTimeout = false;
234
+ }
235
+ apply() {
236
+ return '';
237
+ }
238
+ rollback() {
239
+ return '';
240
+ }
241
+ rollForwards() {
242
+ return '';
243
+ }
244
+ matches() {
245
+ return 1 /* Failure */;
246
+ }
247
+ }
248
+ /**
249
+ * Wraps another prediction. Does not apply the prediction, but will pass
250
+ * through its `matches` request.
251
+ */
252
+ class TentativeBoundary {
253
+ constructor(inner) {
254
+ this.inner = inner;
255
+ }
256
+ apply(buffer, cursor) {
257
+ this._appliedCursor = cursor.clone();
258
+ this.inner.apply(buffer, this._appliedCursor);
259
+ return '';
260
+ }
261
+ rollback(cursor) {
262
+ this.inner.rollback(cursor.clone());
263
+ return '';
264
+ }
265
+ rollForwards(cursor, withInput) {
266
+ if (this._appliedCursor) {
267
+ cursor.moveTo(this._appliedCursor);
268
+ }
269
+ return withInput;
270
+ }
271
+ matches(input) {
272
+ return this.inner.matches(input);
273
+ }
274
+ }
275
+ const isTenativeCharacterPrediction = (p) => p instanceof TentativeBoundary && p.inner instanceof CharacterPrediction;
276
+ exports.isTenativeCharacterPrediction = isTenativeCharacterPrediction;
277
+ /**
278
+ * Prediction for a single alphanumeric character.
279
+ */
280
+ class CharacterPrediction {
281
+ constructor(_style, _char) {
282
+ this._style = _style;
283
+ this._char = _char;
284
+ this.affectsStyle = true;
285
+ }
286
+ apply(_, cursor) {
287
+ const cell = cursor.getCell();
288
+ // 检查光标位置是否有字符和相关的样式,如果有的话,就先把那个字符和相关信息存下来,用于后续可能的回滚
289
+ // 同时储存当前的光标,同样用于后续的RollForwards/RollBack
290
+ this.appliedAt = cell
291
+ ? { pos: cursor.coordinate, oldAttributes: attributesToSeq(cell), oldChar: cell.getChars() }
292
+ : { pos: cursor.coordinate, oldAttributes: '', oldChar: '' };
293
+ cursor.shift(1);
294
+ // eg. character l -> \x1B[2ml\x1B[22m
295
+ return this._style.apply + this._char + this._style.undo;
296
+ }
297
+ rollback(cursor) {
298
+ if (!this.appliedAt) {
299
+ return ''; // not applied
300
+ }
301
+ const { oldAttributes, oldChar, pos } = this.appliedAt;
302
+ const r = cursor.moveTo(pos) + (oldChar ? `${oldAttributes}${oldChar}${cursor.moveTo(pos)}` : DELETE_CHAR);
303
+ return r;
304
+ }
305
+ rollForwards(cursor, input) {
306
+ if (!this.appliedAt) {
307
+ return ''; // not applied
308
+ }
309
+ return cursor.clone().moveTo(this.appliedAt.pos) + input;
310
+ }
311
+ matches(input, lookBehind) {
312
+ const startIndex = input.index;
313
+ // remove any styling CSI before checking the char
314
+ while (input.eatRe(CSI_STYLE_RE)) { }
315
+ if (input.eof) {
316
+ return 2 /* Buffer */;
317
+ }
318
+ if (input.eatChar(this._char)) {
319
+ return 0 /* Success */;
320
+ }
321
+ if (lookBehind instanceof CharacterPrediction) {
322
+ // see #112842
323
+ const sillyZshOutcome = input.eatGradually(`\b${lookBehind._char}${this._char}`);
324
+ if (sillyZshOutcome !== 1 /* Failure */) {
325
+ return sillyZshOutcome;
326
+ }
327
+ }
328
+ input.index = startIndex;
329
+ return 1 /* Failure */;
330
+ }
331
+ }
332
+ class BackspacePrediction {
333
+ constructor(_terminal) {
334
+ this._terminal = _terminal;
335
+ }
336
+ apply(_, cursor) {
337
+ var _a;
338
+ // at eol if everything to the right is whitespace (zsh will emit a "clear line" code in this case)
339
+ // todo: can be optimized if `getTrimmedLength` is exposed from xterm
340
+ const isLastChar = !((_a = cursor.getLine()) === null || _a === void 0 ? void 0 : _a.translateToString(undefined, cursor.x).trim());
341
+ const pos = cursor.coordinate;
342
+ const move = cursor.shift(-1);
343
+ const cell = cursor.getCell();
344
+ this._appliedAt = cell
345
+ ? { isLastChar, pos, oldAttributes: attributesToSeq(cell), oldChar: cell.getChars() }
346
+ : { isLastChar, pos, oldAttributes: '', oldChar: '' };
347
+ return move + DELETE_CHAR;
348
+ }
349
+ rollback(cursor) {
350
+ if (!this._appliedAt) {
351
+ return ''; // not applied
352
+ }
353
+ const { oldAttributes, oldChar, pos } = this._appliedAt;
354
+ if (!oldChar) {
355
+ return cursor.moveTo(pos) + DELETE_CHAR;
356
+ }
357
+ return (oldAttributes + oldChar + cursor.moveTo(pos) + attributesToSeq(core(this._terminal)._inputHandler._curAttrData));
358
+ }
359
+ rollForwards() {
360
+ return '';
361
+ }
362
+ matches(input) {
363
+ var _a;
364
+ if ((_a = this._appliedAt) === null || _a === void 0 ? void 0 : _a.isLastChar) {
365
+ const r1 = input.eatGradually(`\b${CSI}K`);
366
+ if (r1 !== 1 /* Failure */) {
367
+ return r1;
368
+ }
369
+ const r2 = input.eatGradually(`\b \b`);
370
+ if (r2 !== 1 /* Failure */) {
371
+ return r2;
372
+ }
373
+ }
374
+ return 1 /* Failure */;
375
+ }
376
+ }
377
+ class NewlinePrediction {
378
+ apply(_, cursor) {
379
+ this._prevPosition = cursor.coordinate;
380
+ cursor.move(0, cursor.y + 1);
381
+ return '\r\n';
382
+ }
383
+ rollback(cursor) {
384
+ return this._prevPosition ? cursor.moveTo(this._prevPosition) : '';
385
+ }
386
+ rollForwards() {
387
+ return ''; // does not need to rewrite
388
+ }
389
+ matches(input) {
390
+ return input.eatGradually('\r\n');
391
+ }
392
+ }
393
+ /**
394
+ * Prediction when the cursor reaches the end of the line. Similar to newline
395
+ * prediction, but shells handle it slightly differently.
396
+ */
397
+ class LinewrapPrediction extends NewlinePrediction {
398
+ apply(_, cursor) {
399
+ this._prevPosition = cursor.coordinate;
400
+ cursor.move(0, cursor.y + 1);
401
+ return ' \r';
402
+ }
403
+ matches(input) {
404
+ // bash and zshell add a space which wraps in the terminal, then a CR
405
+ const r = input.eatGradually(' \r');
406
+ if (r !== 1 /* Failure */) {
407
+ // zshell additionally adds a clear line after wrapping to be safe -- eat it
408
+ const r2 = input.eatGradually(DELETE_REST_OF_LINE);
409
+ return r2 === 2 /* Buffer */ ? 2 /* Buffer */ : r;
410
+ }
411
+ return input.eatGradually('\r\n');
412
+ }
413
+ }
414
+ class CursorMovePrediction {
415
+ constructor(_direction, _moveByWords, _amount) {
416
+ this._direction = _direction;
417
+ this._moveByWords = _moveByWords;
418
+ this._amount = _amount;
419
+ }
420
+ apply(buffer, cursor) {
421
+ const prevPosition = cursor.x;
422
+ const currentCell = cursor.getCell();
423
+ const prevAttrs = currentCell ? attributesToSeq(currentCell) : '';
424
+ const { _amount: amount, _direction: direction, _moveByWords: moveByWords } = this;
425
+ const delta = direction === "D" /* Back */ ? -1 : 1;
426
+ const target = cursor.clone();
427
+ if (moveByWords) {
428
+ for (let i = 0; i < amount; i++) {
429
+ moveToWordBoundary(buffer, target, delta);
430
+ }
431
+ }
432
+ else {
433
+ target.shift(delta * amount);
434
+ }
435
+ this._applied = {
436
+ amount: Math.abs(cursor.x - target.x),
437
+ prevPosition,
438
+ prevAttrs,
439
+ rollForward: cursor.moveTo(target),
440
+ };
441
+ return this._applied.rollForward;
442
+ }
443
+ rollback(cursor) {
444
+ if (!this._applied) {
445
+ return '';
446
+ }
447
+ return cursor.move(this._applied.prevPosition, cursor.y) + this._applied.prevAttrs;
448
+ }
449
+ rollForwards() {
450
+ return ''; // does not need to rewrite
451
+ }
452
+ matches(input) {
453
+ if (!this._applied) {
454
+ return 1 /* Failure */;
455
+ }
456
+ const direction = this._direction;
457
+ const { amount, rollForward } = this._applied;
458
+ // arg can be omitted to move one character. We don't eatGradually() here
459
+ // or below moves that don't go as far as the cursor would be buffered
460
+ // indefinitely
461
+ if (input.eatStr(`${CSI}${direction}`.repeat(amount))) {
462
+ return 0 /* Success */;
463
+ }
464
+ // \b is the equivalent to moving one character back
465
+ if (direction === "D" /* Back */) {
466
+ if (input.eatStr(`\b`.repeat(amount))) {
467
+ return 0 /* Success */;
468
+ }
469
+ }
470
+ // check if the cursor position is set absolutely
471
+ if (rollForward) {
472
+ const r = input.eatGradually(rollForward);
473
+ if (r !== 1 /* Failure */) {
474
+ return r;
475
+ }
476
+ }
477
+ // check for a relative move in the direction
478
+ return input.eatGradually(`${CSI}${amount}${direction}`);
479
+ }
480
+ }
481
+ class PredictionStats extends ide_core_common_1.Disposable {
482
+ constructor(timeline) {
483
+ super();
484
+ this._stats = [];
485
+ this._index = 0;
486
+ this._addedAtTime = new WeakMap();
487
+ this._changeEmitter = new ide_core_common_1.Emitter();
488
+ this.onChange = this._changeEmitter.event;
489
+ this.addDispose(timeline.onPredictionAdded((p) => this._addedAtTime.set(p, Date.now())));
490
+ this.addDispose(timeline.onPredictionSucceeded(this._pushStat.bind(this, true)));
491
+ this.addDispose(timeline.onPredictionFailed(this._pushStat.bind(this, false)));
492
+ }
493
+ /**
494
+ * Gets the percent (0-1) of predictions that were accurate.
495
+ */
496
+ get accuracy() {
497
+ let correctCount = 0;
498
+ for (const [, correct] of this._stats) {
499
+ if (correct) {
500
+ correctCount++;
501
+ }
502
+ }
503
+ return correctCount / (this._stats.length || 1);
504
+ }
505
+ /**
506
+ * Gets the number of recorded stats.
507
+ */
508
+ get sampleSize() {
509
+ return this._stats.length;
510
+ }
511
+ /**
512
+ * Gets latency stats of successful predictions.
513
+ */
514
+ get latency() {
515
+ const latencies = this._stats
516
+ .filter(([, correct]) => correct)
517
+ .map(([s]) => s)
518
+ .sort();
519
+ return {
520
+ count: latencies.length,
521
+ min: latencies[0],
522
+ median: latencies[Math.floor(latencies.length / 2)],
523
+ max: latencies[latencies.length - 1],
524
+ };
525
+ }
526
+ /**
527
+ * Gets the maximum observed latency.
528
+ */
529
+ get maxLatency() {
530
+ let max = -Infinity;
531
+ for (const [latency, correct] of this._stats) {
532
+ if (correct) {
533
+ max = Math.max(latency, max);
534
+ }
535
+ }
536
+ return max;
537
+ }
538
+ _pushStat(correct, prediction) {
539
+ const started = this._addedAtTime.get(prediction);
540
+ this._stats[this._index] = [Date.now() - started, correct];
541
+ this._index = (this._index + 1) % statsBufferSize;
542
+ this._changeEmitter.fire();
543
+ }
544
+ }
545
+ exports.PredictionStats = PredictionStats;
546
+ class PredictionTimeline {
547
+ constructor(terminal, _style) {
548
+ this.terminal = terminal;
549
+ this._style = _style;
550
+ /**
551
+ * Expected queue of events. Only predictions for the lowest are
552
+ * written into the terminal.
553
+ */
554
+ this._expected = [];
555
+ /**
556
+ * Current prediction generation.
557
+ */
558
+ this._currentGen = 0;
559
+ /**
560
+ * Whether predictions are echoed to the terminal. If false, predictions
561
+ * will still be computed internally for latency metrics, but input will
562
+ * never be adjusted.
563
+ */
564
+ this._showPredictions = false;
565
+ this._addedEmitter = new ide_core_common_1.Emitter();
566
+ this.onPredictionAdded = this._addedEmitter.event;
567
+ this._failedEmitter = new ide_core_common_1.Emitter();
568
+ this.onPredictionFailed = this._failedEmitter.event;
569
+ this._succeededEmitter = new ide_core_common_1.Emitter();
570
+ this.onPredictionSucceeded = this._succeededEmitter.event;
571
+ }
572
+ get _currentGenerationPredictions() {
573
+ return this._expected.filter(({ gen }) => gen === this._expected[0].gen).map(({ p }) => p);
574
+ }
575
+ get isShowingPredictions() {
576
+ return this._showPredictions;
577
+ }
578
+ get length() {
579
+ return this._expected.length;
580
+ }
581
+ setShowPredictions(show) {
582
+ if (show === this._showPredictions) {
583
+ return;
584
+ }
585
+ typeAheadLogger('set predictions:', show);
586
+ this._showPredictions = show;
587
+ const buffer = this._getActiveBuffer();
588
+ if (!buffer) {
589
+ return;
590
+ }
591
+ const toApply = this._currentGenerationPredictions;
592
+ if (show) {
593
+ this.clearCursor();
594
+ this._style.expectIncomingStyle(toApply.reduce((count, p) => (p.affectsStyle ? count + 1 : count), 0));
595
+ this.terminal.write(toApply.map((p) => p.apply(buffer, this.physicalCursor(buffer))).join(''));
596
+ }
597
+ else {
598
+ this.terminal.write(toApply
599
+ .reverse()
600
+ .map((p) => p.rollback(this.physicalCursor(buffer)))
601
+ .join(''));
602
+ }
603
+ }
604
+ /**
605
+ * Undoes any predictions written and resets expectations.
606
+ */
607
+ undoAllPredictions() {
608
+ const buffer = this._getActiveBuffer();
609
+ if (this._showPredictions && buffer) {
610
+ this.terminal.write(this._currentGenerationPredictions
611
+ .reverse()
612
+ .map((p) => p.rollback(this.physicalCursor(buffer)))
613
+ .join(''));
614
+ }
615
+ this._expected = [];
616
+ }
617
+ /**
618
+ * Should be called when input is incoming to the temrinal.
619
+ */
620
+ beforeServerInput(input) {
621
+ var _a;
622
+ const originalInput = input;
623
+ if (this._inputBuffer) {
624
+ input = this._inputBuffer + input;
625
+ this._inputBuffer = undefined;
626
+ }
627
+ if (!this._expected.length) {
628
+ this._clearPredictionState();
629
+ return input;
630
+ }
631
+ const buffer = this._getActiveBuffer();
632
+ if (!buffer) {
633
+ this._clearPredictionState();
634
+ return input;
635
+ }
636
+ let output = '';
637
+ const reader = new StringReader(input);
638
+ const startingGen = this._expected[0].gen;
639
+ const emitPredictionOmitted = () => {
640
+ const omit = reader.eatRe(PREDICTION_OMIT_RE);
641
+ if (omit) {
642
+ output += omit[0];
643
+ }
644
+ };
645
+ ReadLoop: while (this._expected.length && reader.remaining > 0) {
646
+ emitPredictionOmitted();
647
+ const { p: prediction, gen } = this._expected[0];
648
+ const cursor = this.physicalCursor(buffer);
649
+ const beforeTestReaderIndex = reader.index;
650
+ switch (prediction.matches(reader, this._lookBehind)) {
651
+ case 0 /* Success */: {
652
+ // if the input character matches what the next prediction expected, undo
653
+ // the prediction and write the real character out.
654
+ const eaten = input.slice(beforeTestReaderIndex, reader.index);
655
+ if (gen === startingGen) {
656
+ output += (_a = prediction.rollForwards) === null || _a === void 0 ? void 0 : _a.call(prediction, cursor, eaten);
657
+ }
658
+ else {
659
+ prediction.apply(buffer, this.physicalCursor(buffer)); // move cursor for additional apply
660
+ output += eaten;
661
+ }
662
+ this._succeededEmitter.fire(prediction);
663
+ this._lookBehind = prediction;
664
+ this._expected.shift();
665
+ break;
666
+ }
667
+ case 2 /* Buffer */:
668
+ // on a buffer, store the remaining data and completely read data
669
+ // to be output as normal.
670
+ this._inputBuffer = input.slice(beforeTestReaderIndex);
671
+ reader.index = input.length;
672
+ break ReadLoop;
673
+ case 1 /* Failure */: {
674
+ // on a failure, roll back all remaining items in this generation
675
+ // and clear predictions, since they are no longer valid
676
+ const rollback = this._expected.filter((p) => p.gen === startingGen).reverse();
677
+ output += rollback.map(({ p }) => p.rollback(this.physicalCursor(buffer))).join('');
678
+ if (rollback.some((r) => r.p.affectsStyle)) {
679
+ // reading the current style should generally be safe, since predictions
680
+ // always restore the style if they modify it.
681
+ output += attributesToSeq(core(this.terminal)._inputHandler._curAttrData);
682
+ }
683
+ this._clearPredictionState();
684
+ this._failedEmitter.fire(prediction);
685
+ break ReadLoop;
686
+ }
687
+ }
688
+ }
689
+ emitPredictionOmitted();
690
+ // Extra data (like the result of running a command) should cause us to
691
+ // reset the cursor
692
+ if (!reader.eof) {
693
+ output += reader.rest;
694
+ this._clearPredictionState();
695
+ }
696
+ // If we passed a generation boundary, apply the current generation's predictions
697
+ if (this._expected.length && startingGen !== this._expected[0].gen) {
698
+ for (const { p, gen } of this._expected) {
699
+ if (gen !== this._expected[0].gen) {
700
+ break;
701
+ }
702
+ if (p.affectsStyle) {
703
+ this._style.expectIncomingStyle();
704
+ }
705
+ output += p.apply(buffer, this.physicalCursor(buffer));
706
+ }
707
+ }
708
+ if (!this._showPredictions) {
709
+ return originalInput;
710
+ }
711
+ if (output.length === 0 || output === input) {
712
+ return output;
713
+ }
714
+ if (this._physicalCursor) {
715
+ output += this._physicalCursor.moveInstruction();
716
+ }
717
+ // prevent cursor flickering while typing
718
+ output = HIDE_CURSOR + output + SHOW_CURSOR;
719
+ return output;
720
+ }
721
+ /**
722
+ * Clears any expected predictions and stored state. Should be called when
723
+ * the pty gives us something we don't recognize.
724
+ */
725
+ _clearPredictionState() {
726
+ this._expected = [];
727
+ this.clearCursor();
728
+ this._lookBehind = undefined;
729
+ }
730
+ /**
731
+ * Appends a typeahead prediction.
732
+ */
733
+ addPrediction(buffer, prediction) {
734
+ typeAheadLogger('prediction type: \x1b[36m%s\x1b[0m', prediction.constructor.name);
735
+ // if (prediction.constructor.name === 'TentativeBoundary') {
736
+ // console.log('prediction type inner: \x1b[36m%s\x1b[0m', prediction['inner'].constructor.name);
737
+ // }
738
+ this._expected.push({ gen: this._currentGen, p: prediction });
739
+ this._addedEmitter.fire(prediction);
740
+ if (this._currentGen !== this._expected[0].gen) {
741
+ prediction.apply(buffer, this.tentativeCursor(buffer));
742
+ return false;
743
+ }
744
+ const text = prediction.apply(buffer, this.physicalCursor(buffer));
745
+ this._tenativeCursor = undefined; // next read will get or clone the physical cursor
746
+ if (this._showPredictions && text) {
747
+ if (prediction.affectsStyle) {
748
+ this._style.expectIncomingStyle();
749
+ }
750
+ typeAheadLogger('predict:', JSON.stringify(text));
751
+ typeAheadLogger('prediction type: \x1b[36m%s\x1b[0m', prediction.constructor.name);
752
+ this.terminal.write(text);
753
+ }
754
+ return true;
755
+ }
756
+ addBoundary(buffer, prediction) {
757
+ let applied = false;
758
+ if (buffer && prediction) {
759
+ // We apply the prediction so that it's matched against, but wrapped
760
+ // in a tentativeboundary so that it doesn't affect the physical cursor.
761
+ // Then we apply it specifically to the tentative cursor.
762
+ applied = this.addPrediction(buffer, new TentativeBoundary(prediction));
763
+ prediction.apply(buffer, this.tentativeCursor(buffer));
764
+ }
765
+ this._currentGen++;
766
+ return applied;
767
+ }
768
+ /**
769
+ * Peeks the last prediction written.
770
+ */
771
+ peekEnd() {
772
+ var _a;
773
+ return (_a = this._expected[this._expected.length - 1]) === null || _a === void 0 ? void 0 : _a.p;
774
+ }
775
+ /**
776
+ * Peeks the first pending prediction.
777
+ */
778
+ peekStart() {
779
+ var _a;
780
+ return (_a = this._expected[0]) === null || _a === void 0 ? void 0 : _a.p;
781
+ }
782
+ /**
783
+ * Current position of the cursor in the terminal.
784
+ */
785
+ physicalCursor(buffer) {
786
+ if (!this._physicalCursor) {
787
+ if (this._showPredictions) {
788
+ flushOutput(this.terminal);
789
+ }
790
+ this._physicalCursor = new Cursor(this.terminal.rows, this.terminal.cols, buffer);
791
+ }
792
+ return this._physicalCursor;
793
+ }
794
+ /**
795
+ * Cursor position if all predictions and boundaries that have been inserted
796
+ * so far turn out to be successfully predicted.
797
+ */
798
+ tentativeCursor(buffer) {
799
+ if (!this._tenativeCursor) {
800
+ this._tenativeCursor = this.physicalCursor(buffer).clone();
801
+ }
802
+ return this._tenativeCursor;
803
+ }
804
+ clearCursor() {
805
+ this._physicalCursor = undefined;
806
+ this._tenativeCursor = undefined;
807
+ }
808
+ _getActiveBuffer() {
809
+ const buffer = this.terminal.buffer.active;
810
+ return buffer.type === 'normal' ? buffer : undefined;
811
+ }
812
+ }
813
+ exports.PredictionTimeline = PredictionTimeline;
814
+ /**
815
+ * Gets the escape sequence args to restore state/appearance in the cell.
816
+ */
817
+ const attributesToArgs = (cell) => {
818
+ if (cell.isAttributeDefault()) {
819
+ return [0];
820
+ }
821
+ const args = [];
822
+ if (cell.isBold()) {
823
+ args.push(1);
824
+ }
825
+ if (cell.isDim()) {
826
+ args.push(2);
827
+ }
828
+ if (cell.isItalic()) {
829
+ args.push(3);
830
+ }
831
+ if (cell.isUnderline()) {
832
+ args.push(4);
833
+ }
834
+ if (cell.isBlink()) {
835
+ args.push(5);
836
+ }
837
+ if (cell.isInverse()) {
838
+ args.push(7);
839
+ }
840
+ if (cell.isInvisible()) {
841
+ args.push(8);
842
+ }
843
+ if (cell.isFgRGB()) {
844
+ args.push(38, 2, cell.getFgColor() >>> 24, (cell.getFgColor() >>> 16) & 0xff, cell.getFgColor() & 0xff);
845
+ }
846
+ if (cell.isFgPalette()) {
847
+ args.push(38, 5, cell.getFgColor());
848
+ }
849
+ if (cell.isFgDefault()) {
850
+ args.push(39);
851
+ }
852
+ if (cell.isBgRGB()) {
853
+ args.push(48, 2, cell.getBgColor() >>> 24, (cell.getBgColor() >>> 16) & 0xff, cell.getBgColor() & 0xff);
854
+ }
855
+ if (cell.isBgPalette()) {
856
+ args.push(48, 5, cell.getBgColor());
857
+ }
858
+ if (cell.isBgDefault()) {
859
+ args.push(49);
860
+ }
861
+ return args;
862
+ };
863
+ /**
864
+ * Gets the escape sequence to restore state/appearance in the cell.
865
+ */
866
+ const attributesToSeq = (cell) => `${CSI}${attributesToArgs(cell).join(';')}m`;
867
+ const arrayHasPrefixAt = (a, ai, b) => {
868
+ if (a.length - ai > b.length) {
869
+ return false;
870
+ }
871
+ for (let bi = 0; bi < b.length; bi++, ai++) {
872
+ if (b[ai] !== a[ai]) {
873
+ return false;
874
+ }
875
+ }
876
+ return true;
877
+ };
878
+ /**
879
+ * @see https://github.com/xtermjs/xterm.js/blob/065eb13a9d3145bea687239680ec9696d9112b8e/src/common/InputHandler.ts#L2127
880
+ */
881
+ const getColorWidth = (params, pos) => {
882
+ const accu = [0, 0, -1, 0, 0, 0];
883
+ let cSpace = 0;
884
+ let advance = 0;
885
+ do {
886
+ const v = params[pos + advance];
887
+ accu[advance + cSpace] = typeof v === 'number' ? v : v[0];
888
+ if (typeof v !== 'number') {
889
+ let i = 0;
890
+ do {
891
+ if (accu[1] === 5) {
892
+ cSpace = 1;
893
+ }
894
+ accu[advance + i + 1 + cSpace] = v[i];
895
+ } while (++i < v.length && i + advance + 1 + cSpace < accu.length);
896
+ break;
897
+ }
898
+ // exit early if can decide color mode with semicolons
899
+ if ((accu[1] === 5 && advance + cSpace >= 2) || (accu[1] === 2 && advance + cSpace >= 5)) {
900
+ break;
901
+ }
902
+ // offset colorSpace slot for semicolon mode
903
+ if (accu[1]) {
904
+ cSpace = 1;
905
+ }
906
+ } while (++advance + pos < params.length && advance + cSpace < accu.length);
907
+ return advance;
908
+ };
909
+ class TypeAheadStyle {
910
+ constructor(value, _terminal) {
911
+ this._terminal = _terminal;
912
+ /**
913
+ * Number of typeahead style arguments we expect to read. If this is 0 and
914
+ * we see a style coming in, we know that the PTY actually wanted to update.
915
+ */
916
+ this._expectedIncomingStyles = 0;
917
+ this.onUpdate(value);
918
+ }
919
+ static _compileArgs(args) {
920
+ return `${CSI}${args.join(';')}m`;
921
+ }
922
+ /**
923
+ * Signals that a style was written to the terminal and we should watch
924
+ * for it coming in.
925
+ */
926
+ expectIncomingStyle(n = 1) {
927
+ this._expectedIncomingStyles += n * 2;
928
+ }
929
+ /**
930
+ * Starts tracking for CSI changes in the terminal.
931
+ */
932
+ startTracking() {
933
+ this._expectedIncomingStyles = 0;
934
+ this._onDidWriteSGR(attributesToArgs(core(this._terminal)._inputHandler._curAttrData));
935
+ this._csiHandler = this._terminal.parser.registerCsiHandler({ final: 'm' }, (args) => {
936
+ this._onDidWriteSGR(args);
937
+ return false;
938
+ });
939
+ }
940
+ /**
941
+ * Stops tracking terminal CSI changes.
942
+ */
943
+ debounceStopTracking() {
944
+ this._stopTracking();
945
+ }
946
+ /**
947
+ * @inheritdoc
948
+ */
949
+ dispose() {
950
+ this._stopTracking();
951
+ }
952
+ _stopTracking() {
953
+ var _a;
954
+ (_a = this._csiHandler) === null || _a === void 0 ? void 0 : _a.dispose();
955
+ this._csiHandler = undefined;
956
+ }
957
+ _onDidWriteSGR(args) {
958
+ const originalUndo = this._undoArgs;
959
+ for (let i = 0; i < args.length;) {
960
+ const px = args[i];
961
+ const p = typeof px === 'number' ? px : px[0];
962
+ if (this._expectedIncomingStyles) {
963
+ if (arrayHasPrefixAt(args, i, this._undoArgs)) {
964
+ this._expectedIncomingStyles--;
965
+ i += this._undoArgs.length;
966
+ continue;
967
+ }
968
+ if (arrayHasPrefixAt(args, i, this._applyArgs)) {
969
+ this._expectedIncomingStyles--;
970
+ i += this._applyArgs.length;
971
+ continue;
972
+ }
973
+ }
974
+ const width = p === 38 || p === 48 || p === 58 ? getColorWidth(args, i) : 1;
975
+ switch (this._applyArgs[0]) {
976
+ case 1:
977
+ if (p === 2) {
978
+ this._undoArgs = [22, 2];
979
+ }
980
+ else if (p === 22 || p === 0) {
981
+ this._undoArgs = [22];
982
+ }
983
+ break;
984
+ case 2:
985
+ if (p === 1) {
986
+ this._undoArgs = [22, 1];
987
+ }
988
+ else if (p === 22 || p === 0) {
989
+ this._undoArgs = [22];
990
+ }
991
+ break;
992
+ case 38:
993
+ if (p === 0 || p === 39 || p === 100) {
994
+ this._undoArgs = [39];
995
+ }
996
+ else if ((p >= 30 && p <= 38) || (p >= 90 && p <= 97)) {
997
+ this._undoArgs = args.slice(i, i + width);
998
+ }
999
+ break;
1000
+ default:
1001
+ if (p === this._applyArgs[0]) {
1002
+ this._undoArgs = this._applyArgs;
1003
+ }
1004
+ else if (p === 0) {
1005
+ this._undoArgs = this._originalUndoArgs;
1006
+ }
1007
+ // no-op
1008
+ }
1009
+ i += width;
1010
+ }
1011
+ if (originalUndo !== this._undoArgs) {
1012
+ this.undo = TypeAheadStyle._compileArgs(this._undoArgs);
1013
+ }
1014
+ }
1015
+ /**
1016
+ * Updates the current typeahead style.
1017
+ */
1018
+ onUpdate(style) {
1019
+ const { applyArgs, undoArgs } = this._getArgs(style);
1020
+ this._applyArgs = applyArgs;
1021
+ this._undoArgs = this._originalUndoArgs = undoArgs;
1022
+ this.apply = TypeAheadStyle._compileArgs(this._applyArgs);
1023
+ this.undo = TypeAheadStyle._compileArgs(this._undoArgs);
1024
+ }
1025
+ _getArgs(style) {
1026
+ switch (style) {
1027
+ case 'bold':
1028
+ return { applyArgs: [1], undoArgs: [22] };
1029
+ case 'dim':
1030
+ return { applyArgs: [2], undoArgs: [22] };
1031
+ case 'italic':
1032
+ return { applyArgs: [3], undoArgs: [23] };
1033
+ case 'underlined':
1034
+ return { applyArgs: [4], undoArgs: [24] };
1035
+ case 'inverted':
1036
+ return { applyArgs: [7], undoArgs: [27] };
1037
+ default: {
1038
+ let color;
1039
+ try {
1040
+ color = terminal_typeAhead_ext_1.Color.fromHex(style);
1041
+ }
1042
+ catch (_a) {
1043
+ color = new terminal_typeAhead_ext_1.Color(new terminal_typeAhead_ext_1.RGBA(255, 0, 0, 1));
1044
+ }
1045
+ const { r, g, b } = color.rgba;
1046
+ return { applyArgs: [38, 2, r, g, b], undoArgs: [39] };
1047
+ }
1048
+ }
1049
+ }
1050
+ }
1051
+ tslib_1.__decorate([
1052
+ (0, ide_core_common_1.debounce)(2000),
1053
+ tslib_1.__metadata("design:type", Function),
1054
+ tslib_1.__metadata("design:paramtypes", []),
1055
+ tslib_1.__metadata("design:returntype", void 0)
1056
+ ], TypeAheadStyle.prototype, "debounceStopTracking", null);
1057
+ const compileExcludeRegexp = (programs = exports.DEFAULT_LOCAL_ECHO_EXCLUDE) => new RegExp(`\\b(${programs.map(escapeRegExpCharacters).join('|')})\\b`, 'i');
1058
+ class TypeAheadAddon extends ide_core_common_1.Disposable {
1059
+ constructor(onBeforeProcessData, // @ITelemetryService private readonly _telemetryService: ITelemetryService,
1060
+ _config, onDidTerminalPreferenceChange) {
1061
+ super();
1062
+ this.onBeforeProcessData = onBeforeProcessData;
1063
+ this._config = _config;
1064
+ this.onDidTerminalPreferenceChange = onDidTerminalPreferenceChange;
1065
+ this._terminalTitle = '';
1066
+ this.disposableCollection = new ide_core_common_1.DisposableCollection();
1067
+ this._excludeProgramRe = compileExcludeRegexp(this._config.localEchoExcludePrograms);
1068
+ this.addDispose(this.onDidTerminalPreferenceChange((e) => {
1069
+ var _a;
1070
+ switch (e.preferenceName) {
1071
+ case "terminal.integrated.localEchoLatencyThreshold" /* LocalEchoLatencyThreshold */: {
1072
+ if (e.newValue && e.newValue !== this._config.localEchoLatencyThreshold) {
1073
+ this._config.localEchoLatencyThreshold = e.newValue;
1074
+ }
1075
+ break;
1076
+ }
1077
+ case "terminal.integrated.localEchoExcludePrograms" /* LocalEchoExcludePrograms */: {
1078
+ if (e.newValue && e.newValue !== this._config.localEchoExcludePrograms) {
1079
+ this._config.localEchoExcludePrograms = e.newValue;
1080
+ this._excludeProgramRe = compileExcludeRegexp(this._config.localEchoExcludePrograms);
1081
+ }
1082
+ break;
1083
+ }
1084
+ case "terminal.integrated.localEchoStyle" /* LocalEchoStyle */: {
1085
+ if (e.newValue !== this._config.localEchoStyle) {
1086
+ this._config.localEchoStyle = e.newValue;
1087
+ (_a = this._typeAheadStyle) === null || _a === void 0 ? void 0 : _a.onUpdate(this._config.localEchoStyle);
1088
+ }
1089
+ break;
1090
+ }
1091
+ default:
1092
+ break;
1093
+ }
1094
+ }));
1095
+ }
1096
+ _register(o) {
1097
+ if (o === this) {
1098
+ throw new Error('Cannot register a disposable on itself!');
1099
+ }
1100
+ this.disposableCollection.push(o);
1101
+ return o;
1102
+ }
1103
+ activate(terminal) {
1104
+ // window.xterm = terminal;
1105
+ typeAheadLogger('#DEBUG1# activate terminal type ahead addon');
1106
+ const style = (this._typeAheadStyle = this._register(new TypeAheadStyle(this._config.localEchoStyle, terminal)));
1107
+ const timeline = (this._timeline = new PredictionTimeline(terminal, this._typeAheadStyle));
1108
+ const stats = (this.stats = new PredictionStats(this._timeline)); // register disposeable;
1109
+ timeline.setShowPredictions(this._config.localEchoLatencyThreshold === 0);
1110
+ this._register(terminal.onData((e) => this._onUserData(e)));
1111
+ this._register(terminal.onTitleChange((title) => {
1112
+ this._terminalTitle = title;
1113
+ this._reevaluatePredictorState(stats, timeline);
1114
+ }));
1115
+ this._register(terminal.onResize(() => {
1116
+ timeline.setShowPredictions(false);
1117
+ timeline.clearCursor();
1118
+ this._reevaluatePredictorState(stats, timeline);
1119
+ }));
1120
+ // this._register(this._config.onConfigChanged(() => {
1121
+ // style.onUpdate(this._config.localEchoStyle);
1122
+ // this._config.localEchoLatencyThreshold = this._config.localEchoLatencyThreshold;
1123
+ // this._excludeProgramRe = compileExcludeRegexp(this._config.localEchoExcludePrograms);
1124
+ // this._reevaluatePredictorState(stats, timeline);
1125
+ // }));
1126
+ this._register(this._timeline.onPredictionSucceeded((p) => {
1127
+ var _a;
1128
+ if (((_a = this._lastRow) === null || _a === void 0 ? void 0 : _a.charState) === 1 /* HasPendingChar */ &&
1129
+ (0, exports.isTenativeCharacterPrediction)(p) &&
1130
+ p.inner.appliedAt) {
1131
+ if (p.inner.appliedAt.pos.y + p.inner.appliedAt.pos.baseY === this._lastRow.y) {
1132
+ this._lastRow.charState = 2 /* Validated */;
1133
+ }
1134
+ }
1135
+ }));
1136
+ this.onBeforeProcessData((e) => this._onBeforeProcessData(e));
1137
+ let nextStatsSend;
1138
+ this._register(stats.onChange(() => {
1139
+ if (!nextStatsSend) {
1140
+ nextStatsSend = setTimeout(() => {
1141
+ this._sendLatencyStats(stats);
1142
+ nextStatsSend = undefined;
1143
+ }, statsSendTelemetryEvery);
1144
+ }
1145
+ if (timeline.length === 0) {
1146
+ style.debounceStopTracking();
1147
+ }
1148
+ this._reevaluatePredictorState(stats, timeline);
1149
+ }));
1150
+ }
1151
+ reset() {
1152
+ this._lastRow = undefined;
1153
+ }
1154
+ _deferClearingPredictions() {
1155
+ var _a, _b;
1156
+ if (!this.stats || !this._timeline) {
1157
+ return;
1158
+ }
1159
+ (_a = this._clearPredictionDebounce) === null || _a === void 0 ? void 0 : _a.dispose();
1160
+ if (this._timeline.length === 0 || ((_b = this._timeline.peekStart()) === null || _b === void 0 ? void 0 : _b.clearAfterTimeout) === false) {
1161
+ this._clearPredictionDebounce = undefined;
1162
+ return;
1163
+ }
1164
+ this._clearPredictionDebounce = (0, ide_core_common_1.disposableTimeout)(() => {
1165
+ var _a, _b;
1166
+ (_a = this._timeline) === null || _a === void 0 ? void 0 : _a.undoAllPredictions();
1167
+ if (((_b = this._lastRow) === null || _b === void 0 ? void 0 : _b.charState) === 1 /* HasPendingChar */) {
1168
+ this._lastRow.charState = 0 /* Unknown */;
1169
+ }
1170
+ }, Math.max(500, (this.stats.maxLatency * 3) / 2));
1171
+ }
1172
+ /**
1173
+ * Note on debounce:
1174
+ *
1175
+ * We want to toggle the state only when the user has a pause in their
1176
+ * typing. Otherwise, we could turn this on when the PTY sent data but the
1177
+ * terminal cursor is not updated, causes issues.
1178
+ */
1179
+ _reevaluatePredictorState(stats, timeline) {
1180
+ this._reevaluatePredictorStateNow(stats, timeline);
1181
+ }
1182
+ _reevaluatePredictorStateNow(stats, timeline) {
1183
+ if (this._excludeProgramRe.test(this._terminalTitle)) {
1184
+ timeline.setShowPredictions(false);
1185
+ }
1186
+ else if (this._config.localEchoLatencyThreshold < 0) {
1187
+ timeline.setShowPredictions(false);
1188
+ }
1189
+ else if (this._config.localEchoLatencyThreshold === 0) {
1190
+ timeline.setShowPredictions(true);
1191
+ }
1192
+ else if (stats.sampleSize > statsMinSamplesToTurnOn && stats.accuracy > statsMinAccuracyToTurnOn) {
1193
+ const latency = stats.latency.median;
1194
+ if (latency >= this._config.localEchoLatencyThreshold) {
1195
+ timeline.setShowPredictions(true);
1196
+ }
1197
+ else if (latency < this._config.localEchoLatencyThreshold / statsToggleOffThreshold) {
1198
+ timeline.setShowPredictions(false);
1199
+ }
1200
+ }
1201
+ }
1202
+ _sendLatencyStats(stats) {
1203
+ /* __GDPR__
1204
+ "terminalLatencyStats" : {
1205
+ "min" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
1206
+ "max" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
1207
+ "median" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
1208
+ "count" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
1209
+ "predictionAccuracy" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
1210
+ }
1211
+ */
1212
+ // this._telemetryService.publicLog('terminalLatencyStats', {
1213
+ // ...stats.latency,
1214
+ // predictionAccuracy: stats.accuracy,
1215
+ // });
1216
+ // TODO: 加入到reporter中
1217
+ console.log('terminalLatencyStats', Object.assign(Object.assign({}, stats.latency), { predictionAccuracy: stats.accuracy }));
1218
+ }
1219
+ _onUserData(data) {
1220
+ var _a, _b, _c, _d;
1221
+ if (((_a = this._timeline) === null || _a === void 0 ? void 0 : _a.terminal.buffer.active.type) !== 'normal') {
1222
+ return;
1223
+ }
1224
+ typeAheadLogger('user data:', JSON.stringify({ data }));
1225
+ const terminal = this._timeline.terminal;
1226
+ const buffer = terminal.buffer.active;
1227
+ // Detect programs like git log/less that use the normal buffer but don't
1228
+ // take input by deafult (fixes #109541)
1229
+ if (buffer.cursorX === 1 && buffer.cursorY === terminal.rows - 1) {
1230
+ if (((_c = (_b = buffer
1231
+ .getLine(buffer.cursorY + buffer.baseY)) === null || _b === void 0 ? void 0 : _b.getCell(0)) === null || _c === void 0 ? void 0 : _c.getChars()) === ':') {
1232
+ return;
1233
+ }
1234
+ }
1235
+ // the following code guards the terminal prompt to avoid being able to
1236
+ // arrow or backspace-into the prompt. Record the lowest X value at which
1237
+ // the user gave input, and mark all additions before that as tentative.
1238
+ const actualY = buffer.baseY + buffer.cursorY;
1239
+ if (actualY !== ((_d = this._lastRow) === null || _d === void 0 ? void 0 : _d.y)) {
1240
+ this._lastRow = {
1241
+ y: actualY,
1242
+ startingX: buffer.cursorX,
1243
+ endingX: buffer.cursorX,
1244
+ charState: 0 /* Unknown */,
1245
+ };
1246
+ }
1247
+ else {
1248
+ this._lastRow.startingX = Math.min(this._lastRow.startingX, buffer.cursorX);
1249
+ this._lastRow.endingX = Math.max(this._lastRow.endingX, this._timeline.physicalCursor(buffer).x);
1250
+ }
1251
+ const addLeftNavigating = (p) => this._timeline.tentativeCursor(buffer).x <= this._lastRow.startingX
1252
+ ? this._timeline.addBoundary(buffer, p)
1253
+ : this._timeline.addPrediction(buffer, p);
1254
+ const addRightNavigating = (p) => this._timeline.tentativeCursor(buffer).x >= this._lastRow.endingX - 1
1255
+ ? this._timeline.addBoundary(buffer, p)
1256
+ : this._timeline.addPrediction(buffer, p);
1257
+ /** @see https://github.com/xtermjs/xterm.js/blob/1913e9512c048e3cf56bb5f5df51bfff6899c184/src/common/input/Keyboard.ts */
1258
+ const reader = new StringReader(data);
1259
+ while (reader.remaining > 0) {
1260
+ if (reader.eatCharCode(127)) {
1261
+ // backspace
1262
+ const previous = this._timeline.peekEnd();
1263
+ if (previous && previous instanceof CharacterPrediction) {
1264
+ this._timeline.addBoundary();
1265
+ }
1266
+ // backspace must be able to read the previously-written character in
1267
+ // the event that it needs to undo it
1268
+ if (this._timeline.isShowingPredictions) {
1269
+ flushOutput(this._timeline.terminal);
1270
+ }
1271
+ if (this._timeline.tentativeCursor(buffer).x <= this._lastRow.startingX) {
1272
+ this._timeline.addBoundary(buffer, new BackspacePrediction(this._timeline.terminal));
1273
+ }
1274
+ else {
1275
+ // Backspace decrements our ability to go right.
1276
+ this._lastRow.endingX--;
1277
+ this._timeline.addPrediction(buffer, new BackspacePrediction(this._timeline.terminal));
1278
+ }
1279
+ continue;
1280
+ }
1281
+ if (reader.eatCharCode(32, 126)) {
1282
+ // alphanum
1283
+ const char = data[reader.index - 1]; // eat 匹配成功之后 index + 1,因此要拿 index - 1 的字符才是刚刚 eat 的字符
1284
+ // 检测到正常字符输入,创建字符推断
1285
+ const prediction = new CharacterPrediction(this._typeAheadStyle, char);
1286
+ if (this._lastRow.charState === 0 /* Unknown */) {
1287
+ // ?大概是这一行还没有接受过输入的时候,是Unknown状态,创建一个Boundary
1288
+ this._timeline.addBoundary(buffer, prediction);
1289
+ this._lastRow.charState = 1 /* HasPendingChar */;
1290
+ }
1291
+ else {
1292
+ this._timeline.addPrediction(buffer, prediction);
1293
+ }
1294
+ if (this._timeline.tentativeCursor(buffer).x >= terminal.cols) {
1295
+ this._timeline.addBoundary(buffer, new LinewrapPrediction());
1296
+ }
1297
+ continue;
1298
+ }
1299
+ const cursorMv = reader.eatRe(CSI_MOVE_RE);
1300
+ if (cursorMv) {
1301
+ const direction = cursorMv[3];
1302
+ const p = new CursorMovePrediction(direction, !!cursorMv[2], Number(cursorMv[1]) || 1);
1303
+ if (direction === "D" /* Back */) {
1304
+ addLeftNavigating(p);
1305
+ }
1306
+ else {
1307
+ addRightNavigating(p);
1308
+ }
1309
+ continue;
1310
+ }
1311
+ if (reader.eatStr(`${ESC}f`)) {
1312
+ addRightNavigating(new CursorMovePrediction("C" /* Forwards */, true, 1));
1313
+ continue;
1314
+ }
1315
+ if (reader.eatStr(`${ESC}b`)) {
1316
+ addLeftNavigating(new CursorMovePrediction("D" /* Back */, true, 1));
1317
+ continue;
1318
+ }
1319
+ if (reader.eatChar('\r') && buffer.cursorY < terminal.rows - 1) {
1320
+ this._timeline.addPrediction(buffer, new NewlinePrediction());
1321
+ continue;
1322
+ }
1323
+ // something else
1324
+ this._timeline.addBoundary(buffer, new HardBoundary());
1325
+ break;
1326
+ }
1327
+ if (this._timeline.length === 1) {
1328
+ this._deferClearingPredictions();
1329
+ this._typeAheadStyle.startTracking();
1330
+ }
1331
+ }
1332
+ _onBeforeProcessData(event) {
1333
+ if (!this._timeline) {
1334
+ return;
1335
+ }
1336
+ typeAheadLogger('incoming data:', JSON.stringify(event.data));
1337
+ event.data = this._timeline.beforeServerInput(event.data);
1338
+ typeAheadLogger('emitted data:', JSON.stringify(event.data));
1339
+ this._deferClearingPredictions();
1340
+ }
1341
+ }
1342
+ tslib_1.__decorate([
1343
+ (0, ide_core_common_1.debounce)(100),
1344
+ tslib_1.__metadata("design:type", Function),
1345
+ tslib_1.__metadata("design:paramtypes", [PredictionStats, PredictionTimeline]),
1346
+ tslib_1.__metadata("design:returntype", void 0)
1347
+ ], TypeAheadAddon.prototype, "_reevaluatePredictorState", null);
1348
+ exports.TypeAheadAddon = TypeAheadAddon;
1349
+ //# sourceMappingURL=terminal.typeAhead.addon.js.map