/
terminalRecorder.ts
100 lines (86 loc) · 3 KB
/
terminalRecorder.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IPtyHostProcessReplayEvent } from 'vs/platform/terminal/common/capabilities/capabilities';
import { ReplayEntry } from 'vs/platform/terminal/common/terminalProcess';
const MAX_RECORDER_DATA_SIZE = 1024 * 1024; // 1MB
interface RecorderEntry {
cols: number;
rows: number;
data: string[];
}
export interface IRemoteTerminalProcessReplayEvent {
events: ReplayEntry[];
}
export class TerminalRecorder {
private _entries: RecorderEntry[];
private _totalDataLength: number = 0;
constructor(cols: number, rows: number) {
this._entries = [{ cols, rows, data: [] }];
}
handleResize(cols: number, rows: number): void {
if (this._entries.length > 0) {
const lastEntry = this._entries[this._entries.length - 1];
if (lastEntry.data.length === 0) {
// last entry is just a resize, so just remove it
this._entries.pop();
}
}
if (this._entries.length > 0) {
const lastEntry = this._entries[this._entries.length - 1];
if (lastEntry.cols === cols && lastEntry.rows === rows) {
// nothing changed
return;
}
if (lastEntry.cols === 0 && lastEntry.rows === 0) {
// we finally received a good size!
lastEntry.cols = cols;
lastEntry.rows = rows;
return;
}
}
this._entries.push({ cols, rows, data: [] });
}
handleData(data: string): void {
const lastEntry = this._entries[this._entries.length - 1];
lastEntry.data.push(data);
this._totalDataLength += data.length;
while (this._totalDataLength > MAX_RECORDER_DATA_SIZE) {
const firstEntry = this._entries[0];
const remainingToDelete = this._totalDataLength - MAX_RECORDER_DATA_SIZE;
if (remainingToDelete >= firstEntry.data[0].length) {
// the first data piece must be deleted
this._totalDataLength -= firstEntry.data[0].length;
firstEntry.data.shift();
if (firstEntry.data.length === 0) {
// the first entry must be deleted
this._entries.shift();
}
} else {
// the first data piece must be partially deleted
firstEntry.data[0] = firstEntry.data[0].substr(remainingToDelete);
this._totalDataLength -= remainingToDelete;
}
}
}
generateReplayEventSync(): IPtyHostProcessReplayEvent {
// normalize entries to one element per data array
this._entries.forEach((entry) => {
if (entry.data.length > 0) {
entry.data = [entry.data.join('')];
}
});
return {
events: this._entries.map(entry => ({ cols: entry.cols, rows: entry.rows, data: entry.data[0] ?? '' })),
// No command restoration is needed when relaunching terminals
commands: {
isWindowsPty: false,
commands: []
}
};
}
async generateReplayEvent(): Promise<IPtyHostProcessReplayEvent> {
return this.generateReplayEventSync();
}
}