@xterm/xterm 6.1.0-beta.25 → 6.1.0-beta.251

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 (163) hide show
  1. package/README.md +62 -38
  2. package/css/xterm.css +29 -22
  3. package/lib/xterm.js +1 -1
  4. package/lib/xterm.js.map +1 -1
  5. package/lib/xterm.mjs +8 -34
  6. package/lib/xterm.mjs.map +4 -4
  7. package/package.json +25 -22
  8. package/src/browser/AccessibilityManager.ts +11 -6
  9. package/src/browser/Clipboard.ts +6 -3
  10. package/src/browser/CoreBrowserTerminal.ts +149 -319
  11. package/src/browser/Dom.ts +178 -0
  12. package/src/browser/Linkifier.ts +11 -11
  13. package/src/browser/OscLinkProvider.ts +82 -14
  14. package/src/browser/RenderDebouncer.ts +2 -2
  15. package/src/browser/TimeBasedDebouncer.ts +2 -2
  16. package/src/browser/Types.ts +12 -11
  17. package/src/browser/Viewport.ts +55 -20
  18. package/src/browser/decorations/BufferDecorationRenderer.ts +1 -1
  19. package/src/browser/decorations/OverviewRulerRenderer.ts +33 -17
  20. package/src/browser/input/CompositionHelper.ts +44 -8
  21. package/src/browser/public/Terminal.ts +25 -28
  22. package/src/browser/renderer/dom/DomRenderer.ts +242 -76
  23. package/src/browser/renderer/dom/DomRendererRowFactory.ts +19 -13
  24. package/src/browser/renderer/dom/WidthCache.ts +54 -52
  25. package/src/browser/renderer/shared/Constants.ts +7 -0
  26. package/src/browser/renderer/shared/TextBlinkStateManager.ts +97 -0
  27. package/src/browser/renderer/shared/Types.ts +8 -2
  28. package/src/browser/scrollable/abstractScrollbar.ts +300 -0
  29. package/src/browser/scrollable/fastDomNode.ts +126 -0
  30. package/src/browser/scrollable/globalPointerMoveMonitor.ts +90 -0
  31. package/src/browser/scrollable/horizontalScrollbar.ts +85 -0
  32. package/src/browser/scrollable/mouseEvent.ts +292 -0
  33. package/src/browser/scrollable/scrollable.ts +486 -0
  34. package/src/browser/scrollable/scrollableElement.ts +581 -0
  35. package/src/browser/scrollable/scrollableElementOptions.ts +161 -0
  36. package/src/browser/scrollable/scrollbarArrow.ts +110 -0
  37. package/src/browser/scrollable/scrollbarState.ts +246 -0
  38. package/src/browser/scrollable/scrollbarVisibilityController.ts +113 -0
  39. package/src/browser/scrollable/touch.ts +485 -0
  40. package/src/browser/scrollable/verticalScrollbar.ts +143 -0
  41. package/src/browser/scrollable/widget.ts +23 -0
  42. package/src/browser/services/CharSizeService.ts +2 -2
  43. package/src/browser/services/CoreBrowserService.ts +7 -5
  44. package/src/browser/services/KeyboardService.ts +67 -0
  45. package/src/browser/services/LinkProviderService.ts +1 -1
  46. package/src/browser/services/MouseCoordsService.ts +47 -0
  47. package/src/browser/services/MouseService.ts +518 -25
  48. package/src/browser/services/RenderService.ts +28 -16
  49. package/src/browser/services/SelectionService.ts +45 -39
  50. package/src/browser/services/Services.ts +40 -17
  51. package/src/browser/services/ThemeService.ts +2 -2
  52. package/src/common/Async.ts +141 -0
  53. package/src/common/CircularList.ts +2 -2
  54. package/src/common/Color.ts +8 -0
  55. package/src/common/CoreTerminal.ts +32 -22
  56. package/src/common/Event.ts +118 -0
  57. package/src/common/InputHandler.ts +286 -87
  58. package/src/common/Lifecycle.ts +113 -0
  59. package/src/common/Platform.ts +13 -3
  60. package/src/common/SortedList.ts +7 -3
  61. package/src/common/StringBuilder.ts +67 -0
  62. package/src/common/TaskQueue.ts +14 -5
  63. package/src/common/Types.ts +51 -31
  64. package/src/common/Version.ts +9 -0
  65. package/src/common/buffer/Buffer.ts +34 -19
  66. package/src/common/buffer/BufferLine.ts +140 -68
  67. package/src/common/buffer/BufferLineStringCache.ts +69 -0
  68. package/src/common/buffer/BufferReflow.ts +4 -1
  69. package/src/common/buffer/BufferSet.ts +11 -6
  70. package/src/common/buffer/CellData.ts +57 -0
  71. package/src/common/buffer/Marker.ts +2 -2
  72. package/src/common/buffer/Types.ts +6 -2
  73. package/src/common/data/EscapeSequences.ts +71 -70
  74. package/src/common/input/Keyboard.ts +14 -7
  75. package/src/common/input/KittyKeyboard.ts +526 -0
  76. package/src/common/input/Win32InputMode.ts +297 -0
  77. package/src/common/input/WriteBuffer.ts +107 -38
  78. package/src/common/input/XParseColor.ts +2 -2
  79. package/src/common/parser/ApcParser.ts +196 -0
  80. package/src/common/parser/Constants.ts +14 -4
  81. package/src/common/parser/DcsParser.ts +11 -12
  82. package/src/common/parser/EscapeSequenceParser.ts +205 -63
  83. package/src/common/parser/OscParser.ts +11 -12
  84. package/src/common/parser/Params.ts +27 -8
  85. package/src/common/parser/Types.ts +36 -2
  86. package/src/common/public/BufferLineApiView.ts +2 -2
  87. package/src/common/public/BufferNamespaceApi.ts +3 -3
  88. package/src/common/public/ParserApi.ts +3 -0
  89. package/src/common/services/BufferService.ts +14 -9
  90. package/src/common/services/CharsetService.ts +4 -0
  91. package/src/common/services/CoreService.ts +22 -9
  92. package/src/common/services/DecorationService.ts +255 -8
  93. package/src/common/services/LogService.ts +1 -31
  94. package/src/common/services/{CoreMouseService.ts → MouseStateService.ts} +21 -132
  95. package/src/common/services/OptionsService.ts +13 -4
  96. package/src/common/services/ServiceRegistry.ts +9 -7
  97. package/src/common/services/Services.ts +49 -40
  98. package/src/common/services/UnicodeService.ts +1 -1
  99. package/typings/xterm.d.ts +318 -34
  100. package/src/common/Clone.ts +0 -23
  101. package/src/common/TypedArrayUtils.ts +0 -17
  102. package/src/vs/base/browser/browser.ts +0 -141
  103. package/src/vs/base/browser/canIUse.ts +0 -49
  104. package/src/vs/base/browser/dom.ts +0 -2369
  105. package/src/vs/base/browser/fastDomNode.ts +0 -316
  106. package/src/vs/base/browser/globalPointerMoveMonitor.ts +0 -112
  107. package/src/vs/base/browser/iframe.ts +0 -135
  108. package/src/vs/base/browser/keyboardEvent.ts +0 -213
  109. package/src/vs/base/browser/mouseEvent.ts +0 -229
  110. package/src/vs/base/browser/touch.ts +0 -372
  111. package/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +0 -303
  112. package/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +0 -114
  113. package/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +0 -720
  114. package/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +0 -165
  115. package/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +0 -114
  116. package/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +0 -243
  117. package/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts +0 -118
  118. package/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +0 -116
  119. package/src/vs/base/browser/ui/widget.ts +0 -57
  120. package/src/vs/base/browser/window.ts +0 -14
  121. package/src/vs/base/common/arrays.ts +0 -887
  122. package/src/vs/base/common/arraysFind.ts +0 -202
  123. package/src/vs/base/common/assert.ts +0 -71
  124. package/src/vs/base/common/async.ts +0 -1992
  125. package/src/vs/base/common/cancellation.ts +0 -148
  126. package/src/vs/base/common/charCode.ts +0 -450
  127. package/src/vs/base/common/collections.ts +0 -140
  128. package/src/vs/base/common/decorators.ts +0 -130
  129. package/src/vs/base/common/equals.ts +0 -146
  130. package/src/vs/base/common/errors.ts +0 -303
  131. package/src/vs/base/common/event.ts +0 -1778
  132. package/src/vs/base/common/functional.ts +0 -32
  133. package/src/vs/base/common/hash.ts +0 -316
  134. package/src/vs/base/common/iterator.ts +0 -159
  135. package/src/vs/base/common/keyCodes.ts +0 -526
  136. package/src/vs/base/common/keybindings.ts +0 -284
  137. package/src/vs/base/common/lazy.ts +0 -47
  138. package/src/vs/base/common/lifecycle.ts +0 -801
  139. package/src/vs/base/common/linkedList.ts +0 -142
  140. package/src/vs/base/common/map.ts +0 -202
  141. package/src/vs/base/common/numbers.ts +0 -98
  142. package/src/vs/base/common/observable.ts +0 -76
  143. package/src/vs/base/common/observableInternal/api.ts +0 -31
  144. package/src/vs/base/common/observableInternal/autorun.ts +0 -281
  145. package/src/vs/base/common/observableInternal/base.ts +0 -489
  146. package/src/vs/base/common/observableInternal/debugName.ts +0 -145
  147. package/src/vs/base/common/observableInternal/derived.ts +0 -428
  148. package/src/vs/base/common/observableInternal/lazyObservableValue.ts +0 -146
  149. package/src/vs/base/common/observableInternal/logging.ts +0 -328
  150. package/src/vs/base/common/observableInternal/promise.ts +0 -209
  151. package/src/vs/base/common/observableInternal/utils.ts +0 -610
  152. package/src/vs/base/common/platform.ts +0 -281
  153. package/src/vs/base/common/scrollable.ts +0 -522
  154. package/src/vs/base/common/sequence.ts +0 -34
  155. package/src/vs/base/common/stopwatch.ts +0 -43
  156. package/src/vs/base/common/strings.ts +0 -557
  157. package/src/vs/base/common/symbols.ts +0 -9
  158. package/src/vs/base/common/uint.ts +0 -59
  159. package/src/vs/patches/nls.ts +0 -90
  160. package/src/vs/typings/base-common.d.ts +0 -20
  161. package/src/vs/typings/require.d.ts +0 -42
  162. package/src/vs/typings/vscode-globals-nls.d.ts +0 -36
  163. package/src/vs/typings/vscode-globals-product.d.ts +0 -33
@@ -4,17 +4,19 @@
4
4
  */
5
5
 
6
6
  import { CircularList, IInsertEvent } from 'common/CircularList';
7
+ import { Disposable, toDisposable } from 'common/Lifecycle';
7
8
  import { IdleTaskQueue } from 'common/TaskQueue';
8
9
  import { IAttributeData, IBufferLine, ICellData, ICharset } from 'common/Types';
9
10
  import { ExtendedAttrs } from 'common/buffer/AttributeData';
10
11
  import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
12
+ import { BufferLineStringCache } from 'common/buffer/BufferLineStringCache';
11
13
  import { getWrappedLineTrimmedLength, reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths } from 'common/buffer/BufferReflow';
12
14
  import { CellData } from 'common/buffer/CellData';
13
15
  import { NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, WHITESPACE_CELL_WIDTH } from 'common/buffer/Constants';
14
16
  import { Marker } from 'common/buffer/Marker';
15
17
  import { IBuffer } from 'common/buffer/Types';
16
18
  import { DEFAULT_CHARSET } from 'common/data/Charsets';
17
- import { IBufferService, IOptionsService } from 'common/services/Services';
19
+ import { IBufferService, ILogService, IOptionsService } from 'common/services/Services';
18
20
 
19
21
  export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1
20
22
 
@@ -25,7 +27,7 @@ export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1
25
27
  * - cursor position
26
28
  * - scroll position
27
29
  */
28
- export class Buffer implements IBuffer {
30
+ export class Buffer extends Disposable implements IBuffer {
29
31
  public lines: CircularList<IBufferLine>;
30
32
  public ydisp: number = 0;
31
33
  public ybase: number = 0;
@@ -38,24 +40,37 @@ export class Buffer implements IBuffer {
38
40
  public savedX: number = 0;
39
41
  public savedCurAttrData = DEFAULT_ATTR_DATA.clone();
40
42
  public savedCharset: ICharset | undefined = DEFAULT_CHARSET;
43
+ public savedCharsets: (ICharset | undefined)[] = [];
44
+ public savedGlevel: number = 0;
45
+ public savedOriginMode: boolean = false;
46
+ public savedWraparoundMode: boolean = true;
41
47
  public markers: Marker[] = [];
42
48
  private _nullCell: ICellData = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
43
49
  private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]);
44
50
  private _cols: number;
45
51
  private _rows: number;
46
52
  private _isClearing: boolean = false;
53
+ private _memoryCleanupQueue: InstanceType<typeof IdleTaskQueue>;
54
+ private _memoryCleanupPosition = 0;
55
+ private readonly _stringCache: BufferLineStringCache;
47
56
 
48
57
  constructor(
49
58
  private _hasScrollback: boolean,
50
59
  private _optionsService: IOptionsService,
51
- private _bufferService: IBufferService
60
+ private _bufferService: IBufferService,
61
+ private readonly _logService: ILogService
52
62
  ) {
63
+ super();
53
64
  this._cols = this._bufferService.cols;
54
65
  this._rows = this._bufferService.rows;
55
66
  this.lines = new CircularList<IBufferLine>(this._getCorrectBufferLength(this._rows));
56
67
  this.scrollTop = 0;
57
68
  this.scrollBottom = this._rows - 1;
58
69
  this.setupTabStops();
70
+ this._memoryCleanupQueue = new IdleTaskQueue(this._logService);
71
+ this._register(toDisposable(() => this._memoryCleanupQueue.clear()));
72
+ this._register(toDisposable(() => this.clearAllMarkers()));
73
+ this._stringCache = this._register(new BufferLineStringCache());
59
74
  }
60
75
 
61
76
  public getNullCell(attr?: IAttributeData): ICellData {
@@ -85,7 +100,7 @@ export class Buffer implements IBuffer {
85
100
  }
86
101
 
87
102
  public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {
88
- return new BufferLine(this._bufferService.cols, this.getNullCell(attr), isWrapped);
103
+ return new BufferLine(this._stringCache, this._bufferService.cols, this.getNullCell(attr), isWrapped);
89
104
  }
90
105
 
91
106
  public get hasScrollback(): boolean {
@@ -118,9 +133,7 @@ export class Buffer implements IBuffer {
118
133
  */
119
134
  public fillViewportRows(fillAttr?: IAttributeData): void {
120
135
  if (this.lines.length === 0) {
121
- if (fillAttr === undefined) {
122
- fillAttr = DEFAULT_ATTR_DATA;
123
- }
136
+ fillAttr ??= DEFAULT_ATTR_DATA;
124
137
  let i = this._rows;
125
138
  while (i--) {
126
139
  this.lines.push(this.getBlankLine(fillAttr));
@@ -132,6 +145,7 @@ export class Buffer implements IBuffer {
132
145
  * Clears the buffer to it's initial state, discarding all previous data.
133
146
  */
134
147
  public clear(): void {
148
+ this._stringCache.clear();
135
149
  this.ydisp = 0;
136
150
  this.ybase = 0;
137
151
  this.y = 0;
@@ -150,6 +164,7 @@ export class Buffer implements IBuffer {
150
164
  public resize(newCols: number, newRows: number): void {
151
165
  // store reference to null cell with default attrs
152
166
  const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);
167
+ this._stringCache.clear();
153
168
 
154
169
  // count bufferlines with overly big memory to be cleaned afterwards
155
170
  let dirtyMemoryLines = 0;
@@ -184,7 +199,7 @@ export class Buffer implements IBuffer {
184
199
  if (this._optionsService.rawOptions.windowsPty.backend !== undefined || this._optionsService.rawOptions.windowsPty.buildNumber !== undefined) {
185
200
  // Just add the new missing rows on Windows as conpty reprints the screen with it's
186
201
  // view of the world. Once a line enters scrollback for conpty it remains there
187
- this.lines.push(new BufferLine(newCols, nullCell));
202
+ this.lines.push(new BufferLine(this._stringCache, newCols, nullCell, false));
188
203
  } else {
189
204
  if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
190
205
  // There is room above the buffer and there are no empty elements below the line,
@@ -198,7 +213,7 @@ export class Buffer implements IBuffer {
198
213
  } else {
199
214
  // Add a blank line if there is no buffer left at the top to scroll to, or if there
200
215
  // are blank lines after the cursor
201
- this.lines.push(new BufferLine(newCols, nullCell));
216
+ this.lines.push(new BufferLine(this._stringCache, newCols, nullCell, false));
202
217
  }
203
218
  }
204
219
  }
@@ -260,6 +275,13 @@ export class Buffer implements IBuffer {
260
275
  this._cols = newCols;
261
276
  this._rows = newRows;
262
277
 
278
+ // Ensure the cursor position invariant: ybase + y must be within buffer bounds
279
+ // This can be violated during reflow or when shrinking rows
280
+ if (this.lines.length > 0) {
281
+ const maxY = Math.max(0, this.lines.length - this.ybase - 1);
282
+ this.y = Math.min(this.y, maxY);
283
+ }
284
+
263
285
  this._memoryCleanupQueue.clear();
264
286
  // schedule memory cleanup only, if more than 10% of the lines are affected
265
287
  if (dirtyMemoryLines > 0.1 * this.lines.length) {
@@ -268,9 +290,6 @@ export class Buffer implements IBuffer {
268
290
  }
269
291
  }
270
292
 
271
- private _memoryCleanupQueue = new IdleTaskQueue();
272
- private _memoryCleanupPosition = 0;
273
-
274
293
  private _batchedMemoryCleanup(): boolean {
275
294
  let normalRun = true;
276
295
  if (this._memoryCleanupPosition >= this.lines.length) {
@@ -335,7 +354,7 @@ export class Buffer implements IBuffer {
335
354
  }
336
355
  if (this.lines.length < newRows) {
337
356
  // Add an extra row at the bottom of the viewport
338
- this.lines.push(new BufferLine(newCols, nullCell));
357
+ this.lines.push(new BufferLine(this._stringCache, newCols, nullCell, false));
339
358
  }
340
359
  } else {
341
360
  if (this.ydisp === this.ybase) {
@@ -578,9 +597,7 @@ export class Buffer implements IBuffer {
578
597
  * @param x The position to move the cursor to the previous tab stop.
579
598
  */
580
599
  public prevStop(x?: number): number {
581
- if (x === null || x === undefined) {
582
- x = this.x;
583
- }
600
+ x ??= this.x;
584
601
  while (!this.tabs[--x] && x > 0);
585
602
  return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x;
586
603
  }
@@ -590,9 +607,7 @@ export class Buffer implements IBuffer {
590
607
  * @param x The position to move the cursor one tab stop forward.
591
608
  */
592
609
  public nextStop(x?: number): number {
593
- if (x === null || x === undefined) {
594
- x = this.x;
595
- }
610
+ x ??= this.x;
596
611
  while (!this.tabs[++x] && x < this._cols);
597
612
  return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x;
598
613
  }
@@ -8,26 +8,28 @@ import { AttributeData } from 'common/buffer/AttributeData';
8
8
  import { CellData } from 'common/buffer/CellData';
9
9
  import { Attributes, BgFlags, CHAR_DATA_ATTR_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, Content, NULL_CELL_CHAR, NULL_CELL_CODE, NULL_CELL_WIDTH, WHITESPACE_CELL_CHAR } from 'common/buffer/Constants';
10
10
  import { stringFromCodePoint } from 'common/input/TextDecoder';
11
-
12
- /**
13
- * buffer memory layout:
14
- *
15
- * | uint32_t | uint32_t | uint32_t |
16
- * | `content` | `FG` | `BG` |
17
- * | wcwidth(2) comb(1) codepoint(21) | flags(8) R(8) G(8) B(8) | flags(8) R(8) G(8) B(8) |
18
- */
19
-
20
-
21
- /** typed array slots taken by one cell */
22
- const CELL_SIZE = 3;
11
+ import { StringBuilder } from 'common/StringBuilder';
12
+
13
+ // Buffer memory layout:
14
+ //
15
+ // [0]: content `uint32_t` - wcwidth(2) comb(1) codepoint(21)
16
+ // [1]: fg `uint32_t` - flags(8) r(8) g(8) b(8)
17
+ // [2]: bg `uint32_t` - flags(8) r(8) g(8) b(8)
18
+
19
+ const enum Constants {
20
+ /** The number of 32 bit array indices taken by one cell. */
21
+ CELL_INDICIES = 3,
22
+ /** Factor when to cleanup underlying array buffer after shrinking. */
23
+ CLEANUP_THRESHOLD = 2
24
+ }
23
25
 
24
26
  /**
25
27
  * Cell member indices.
26
28
  *
27
29
  * Direct access:
28
- * `content = data[column * CELL_SIZE + Cell.CONTENT];`
29
- * `fg = data[column * CELL_SIZE + Cell.FG];`
30
- * `bg = data[column * CELL_SIZE + Cell.BG];`
30
+ * `content = data[column * Constants.CELL_INDICIES + Cell.CONTENT];`
31
+ * `fg = data[column * Constants.CELL_INDICIES + Cell.FG];`
32
+ * `bg = data[column * Constants.CELL_INDICIES + Cell.BG];`
31
33
  */
32
34
  const enum Cell {
33
35
  CONTENT = 0,
@@ -39,9 +41,20 @@ export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData());
39
41
 
40
42
  // Work variables to avoid garbage collection
41
43
  let $startIndex = 0;
44
+ const $workCell = new CellData();
45
+ const $translateToStringBuilder = new StringBuilder();
42
46
 
43
- /** Factor when to cleanup underlying array buffer after shrinking. */
44
- const CLEANUP_THRESHOLD = 2;
47
+ export interface IBufferLineStringCacheEntry {
48
+ value: string | undefined;
49
+ isTrimmed: boolean;
50
+ generation: number;
51
+ }
52
+
53
+ export interface IBufferLineStringCache {
54
+ generation: number;
55
+ allocateEntry(): IBufferLineStringCacheEntry;
56
+ touch?(): void;
57
+ }
45
58
 
46
59
  /**
47
60
  * Typed array based bufferline implementation.
@@ -62,11 +75,17 @@ export class BufferLine implements IBufferLine {
62
75
  protected _data: Uint32Array;
63
76
  protected _combined: {[index: number]: string} = {};
64
77
  protected _extendedAttrs: {[index: number]: IExtendedAttrs | undefined} = {};
78
+ protected _stringCacheEntryRef: WeakRef<IBufferLineStringCacheEntry> | undefined;
65
79
  public length: number;
66
80
 
67
- constructor(cols: number, fillCellData?: ICellData, public isWrapped: boolean = false) {
68
- this._data = new Uint32Array(cols * CELL_SIZE);
69
- const cell = fillCellData || CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
81
+ constructor(
82
+ protected readonly _stringCache: IBufferLineStringCache,
83
+ cols: number,
84
+ fillCellData?: ICellData,
85
+ public isWrapped: boolean = false
86
+ ) {
87
+ this._data = new Uint32Array(cols * Constants.CELL_INDICIES);
88
+ const cell = fillCellData ?? CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
70
89
  for (let i = 0; i < cols; ++i) {
71
90
  this.setCell(i, cell);
72
91
  }
@@ -78,10 +97,10 @@ export class BufferLine implements IBufferLine {
78
97
  * @deprecated
79
98
  */
80
99
  public get(index: number): CharData {
81
- const content = this._data[index * CELL_SIZE + Cell.CONTENT];
100
+ const content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
82
101
  const cp = content & Content.CODEPOINT_MASK;
83
102
  return [
84
- this._data[index * CELL_SIZE + Cell.FG],
103
+ this._data[index * Constants.CELL_INDICIES + Cell.FG],
85
104
  (content & Content.IS_COMBINED_MASK)
86
105
  ? this._combined[index]
87
106
  : (cp) ? stringFromCodePoint(cp) : '',
@@ -97,12 +116,13 @@ export class BufferLine implements IBufferLine {
97
116
  * @deprecated
98
117
  */
99
118
  public set(index: number, value: CharData): void {
100
- this._data[index * CELL_SIZE + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];
119
+ this._invalidateStringCache();
120
+ this._data[index * Constants.CELL_INDICIES + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];
101
121
  if (value[CHAR_DATA_CHAR_INDEX].length > 1) {
102
122
  this._combined[index] = value[1];
103
- this._data[index * CELL_SIZE + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
123
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
104
124
  } else {
105
- this._data[index * CELL_SIZE + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
125
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
106
126
  }
107
127
  }
108
128
 
@@ -111,22 +131,22 @@ export class BufferLine implements IBufferLine {
111
131
  * use these when only one value is needed, otherwise use `loadCell`
112
132
  */
113
133
  public getWidth(index: number): number {
114
- return this._data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT;
134
+ return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] >> Content.WIDTH_SHIFT;
115
135
  }
116
136
 
117
137
  /** Test whether content has width. */
118
138
  public hasWidth(index: number): number {
119
- return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.WIDTH_MASK;
139
+ return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.WIDTH_MASK;
120
140
  }
121
141
 
122
142
  /** Get FG cell component. */
123
143
  public getFg(index: number): number {
124
- return this._data[index * CELL_SIZE + Cell.FG];
144
+ return this._data[index * Constants.CELL_INDICIES + Cell.FG];
125
145
  }
126
146
 
127
147
  /** Get BG cell component. */
128
148
  public getBg(index: number): number {
129
- return this._data[index * CELL_SIZE + Cell.BG];
149
+ return this._data[index * Constants.CELL_INDICIES + Cell.BG];
130
150
  }
131
151
 
132
152
  /**
@@ -135,7 +155,7 @@ export class BufferLine implements IBufferLine {
135
155
  * from real empty cells.
136
156
  */
137
157
  public hasContent(index: number): number {
138
- return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK;
158
+ return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.HAS_CONTENT_MASK;
139
159
  }
140
160
 
141
161
  /**
@@ -144,7 +164,7 @@ export class BufferLine implements IBufferLine {
144
164
  * a single UTF32 codepoint or the last codepoint of a combined string.
145
165
  */
146
166
  public getCodePoint(index: number): number {
147
- const content = this._data[index * CELL_SIZE + Cell.CONTENT];
167
+ const content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
148
168
  if (content & Content.IS_COMBINED_MASK) {
149
169
  return this._combined[index].charCodeAt(this._combined[index].length - 1);
150
170
  }
@@ -153,12 +173,12 @@ export class BufferLine implements IBufferLine {
153
173
 
154
174
  /** Test whether the cell contains a combined string. */
155
175
  public isCombined(index: number): number {
156
- return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.IS_COMBINED_MASK;
176
+ return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.IS_COMBINED_MASK;
157
177
  }
158
178
 
159
179
  /** Returns the string content of the cell. */
160
180
  public getString(index: number): string {
161
- const content = this._data[index * CELL_SIZE + Cell.CONTENT];
181
+ const content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
162
182
  if (content & Content.IS_COMBINED_MASK) {
163
183
  return this._combined[index];
164
184
  }
@@ -171,7 +191,7 @@ export class BufferLine implements IBufferLine {
171
191
 
172
192
  /** Get state of protected flag. */
173
193
  public isProtected(index: number): number {
174
- return this._data[index * CELL_SIZE + Cell.BG] & BgFlags.PROTECTED;
194
+ return this._data[index * Constants.CELL_INDICIES + Cell.BG] & BgFlags.PROTECTED;
175
195
  }
176
196
 
177
197
  /**
@@ -179,7 +199,7 @@ export class BufferLine implements IBufferLine {
179
199
  * to GC as it significantly reduced the amount of new objects/references needed.
180
200
  */
181
201
  public loadCell(index: number, cell: ICellData): ICellData {
182
- $startIndex = index * CELL_SIZE;
202
+ $startIndex = index * Constants.CELL_INDICIES;
183
203
  cell.content = this._data[$startIndex + Cell.CONTENT];
184
204
  cell.fg = this._data[$startIndex + Cell.FG];
185
205
  cell.bg = this._data[$startIndex + Cell.BG];
@@ -196,15 +216,16 @@ export class BufferLine implements IBufferLine {
196
216
  * Set data at `index` to `cell`.
197
217
  */
198
218
  public setCell(index: number, cell: ICellData): void {
219
+ this._invalidateStringCache();
199
220
  if (cell.content & Content.IS_COMBINED_MASK) {
200
221
  this._combined[index] = cell.combinedData;
201
222
  }
202
223
  if (cell.bg & BgFlags.HAS_EXTENDED) {
203
224
  this._extendedAttrs[index] = cell.extended;
204
225
  }
205
- this._data[index * CELL_SIZE + Cell.CONTENT] = cell.content;
206
- this._data[index * CELL_SIZE + Cell.FG] = cell.fg;
207
- this._data[index * CELL_SIZE + Cell.BG] = cell.bg;
226
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = cell.content;
227
+ this._data[index * Constants.CELL_INDICIES + Cell.FG] = cell.fg;
228
+ this._data[index * Constants.CELL_INDICIES + Cell.BG] = cell.bg;
208
229
  }
209
230
 
210
231
  /**
@@ -213,12 +234,13 @@ export class BufferLine implements IBufferLine {
213
234
  * it gets an optimized access method.
214
235
  */
215
236
  public setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void {
237
+ this._invalidateStringCache();
216
238
  if (attrs.bg & BgFlags.HAS_EXTENDED) {
217
239
  this._extendedAttrs[index] = attrs.extended;
218
240
  }
219
- this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
220
- this._data[index * CELL_SIZE + Cell.FG] = attrs.fg;
221
- this._data[index * CELL_SIZE + Cell.BG] = attrs.bg;
241
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
242
+ this._data[index * Constants.CELL_INDICIES + Cell.FG] = attrs.fg;
243
+ this._data[index * Constants.CELL_INDICIES + Cell.BG] = attrs.bg;
222
244
  }
223
245
 
224
246
  /**
@@ -228,7 +250,8 @@ export class BufferLine implements IBufferLine {
228
250
  * by the previous `setDataFromCodePoint` call, we can omit it here.
229
251
  */
230
252
  public addCodepointToCell(index: number, codePoint: number, width: number): void {
231
- let content = this._data[index * CELL_SIZE + Cell.CONTENT];
253
+ this._invalidateStringCache();
254
+ let content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
232
255
  if (content & Content.IS_COMBINED_MASK) {
233
256
  // we already have a combined string, simply add
234
257
  this._combined[index] += stringFromCodePoint(codePoint);
@@ -250,10 +273,11 @@ export class BufferLine implements IBufferLine {
250
273
  content &= ~Content.WIDTH_MASK;
251
274
  content |= width << Content.WIDTH_SHIFT;
252
275
  }
253
- this._data[index * CELL_SIZE + Cell.CONTENT] = content;
276
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = content;
254
277
  }
255
278
 
256
279
  public insertCells(pos: number, n: number, fillCellData: ICellData): void {
280
+ this._invalidateStringCache();
257
281
  pos %= this.length;
258
282
 
259
283
  // handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char
@@ -262,9 +286,8 @@ export class BufferLine implements IBufferLine {
262
286
  }
263
287
 
264
288
  if (n < this.length - pos) {
265
- const cell = new CellData();
266
289
  for (let i = this.length - pos - n - 1; i >= 0; --i) {
267
- this.setCell(pos + n + i, this.loadCell(pos + i, cell));
290
+ this.setCell(pos + n + i, this.loadCell(pos + i, $workCell));
268
291
  }
269
292
  for (let i = 0; i < n; ++i) {
270
293
  this.setCell(pos + i, fillCellData);
@@ -282,11 +305,11 @@ export class BufferLine implements IBufferLine {
282
305
  }
283
306
 
284
307
  public deleteCells(pos: number, n: number, fillCellData: ICellData): void {
308
+ this._invalidateStringCache();
285
309
  pos %= this.length;
286
310
  if (n < this.length - pos) {
287
- const cell = new CellData();
288
311
  for (let i = 0; i < this.length - pos - n; ++i) {
289
- this.setCell(pos + i, this.loadCell(pos + n + i, cell));
312
+ this.setCell(pos + i, this.loadCell(pos + n + i, $workCell));
290
313
  }
291
314
  for (let i = this.length - n; i < this.length; ++i) {
292
315
  this.setCell(i, fillCellData);
@@ -309,6 +332,7 @@ export class BufferLine implements IBufferLine {
309
332
  }
310
333
 
311
334
  public replaceCells(start: number, end: number, fillCellData: ICellData, respectProtect: boolean = false): void {
335
+ this._invalidateStringCache();
312
336
  // full branching on respectProtect==true, hopefully getting fast JIT for standard case
313
337
  if (respectProtect) {
314
338
  if (start && this.getWidth(start - 1) === 2 && !this.isProtected(start - 1)) {
@@ -345,13 +369,14 @@ export class BufferLine implements IBufferLine {
345
369
  * The underlying array buffer will not change if there is still enough space
346
370
  * to hold the new buffer line data.
347
371
  * Returns a boolean indicating, whether a `cleanupMemory` call would free
348
- * excess memory (true after shrinking > CLEANUP_THRESHOLD).
372
+ * excess memory (true after shrinking > Constants.CLEANUP_THRESHOLD).
349
373
  */
350
374
  public resize(cols: number, fillCellData: ICellData): boolean {
375
+ this._invalidateStringCache();
351
376
  if (cols === this.length) {
352
- return this._data.length * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength;
377
+ return this._data.length * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength;
353
378
  }
354
- const uint32Cells = cols * CELL_SIZE;
379
+ const uint32Cells = cols * Constants.CELL_INDICIES;
355
380
  if (cols > this.length) {
356
381
  if (this._data.buffer.byteLength >= uint32Cells * 4) {
357
382
  // optimization: avoid alloc and data copy if buffer has enough room
@@ -386,17 +411,17 @@ export class BufferLine implements IBufferLine {
386
411
  }
387
412
  }
388
413
  this.length = cols;
389
- return uint32Cells * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength;
414
+ return uint32Cells * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength;
390
415
  }
391
416
 
392
417
  /**
393
418
  * Cleanup underlying array buffer.
394
419
  * A cleanup will be triggered if the array buffer exceeds the actual used
395
- * memory by a factor of CLEANUP_THRESHOLD.
420
+ * memory by a factor of Constants.CLEANUP_THRESHOLD.
396
421
  * Returns 0 or 1 indicating whether a cleanup happened.
397
422
  */
398
423
  public cleanupMemory(): number {
399
- if (this._data.length * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength) {
424
+ if (this._data.length * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength) {
400
425
  const data = new Uint32Array(this._data.length);
401
426
  data.set(this._data);
402
427
  this._data = data;
@@ -407,6 +432,7 @@ export class BufferLine implements IBufferLine {
407
432
 
408
433
  /** fill a line with fillCharData */
409
434
  public fill(fillCellData: ICellData, respectProtect: boolean = false): void {
435
+ this._invalidateStringCache();
410
436
  // full branching on respectProtect==true, hopefully getting fast JIT for standard case
411
437
  if (respectProtect) {
412
438
  for (let i = 0; i < this.length; ++i) {
@@ -425,6 +451,7 @@ export class BufferLine implements IBufferLine {
425
451
 
426
452
  /** alter to a full copy of line */
427
453
  public copyFrom(line: BufferLine): void {
454
+ this._invalidateStringCache();
428
455
  if (this.length !== line.length) {
429
456
  this._data = new Uint32Array(line._data);
430
457
  } else {
@@ -445,7 +472,7 @@ export class BufferLine implements IBufferLine {
445
472
 
446
473
  /** create a new clone */
447
474
  public clone(): IBufferLine {
448
- const newLine = new BufferLine(0);
475
+ const newLine = new BufferLine(this._stringCache, 0, undefined, false);
449
476
  newLine._data = new Uint32Array(this._data);
450
477
  newLine.length = this.length;
451
478
  for (const el in this._combined) {
@@ -460,8 +487,8 @@ export class BufferLine implements IBufferLine {
460
487
 
461
488
  public getTrimmedLength(): number {
462
489
  for (let i = this.length - 1; i >= 0; --i) {
463
- if ((this._data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK)) {
464
- return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT);
490
+ if ((this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] & Content.HAS_CONTENT_MASK)) {
491
+ return i + (this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] >> Content.WIDTH_SHIFT);
465
492
  }
466
493
  }
467
494
  return 0;
@@ -469,30 +496,31 @@ export class BufferLine implements IBufferLine {
469
496
 
470
497
  public getNoBgTrimmedLength(): number {
471
498
  for (let i = this.length - 1; i >= 0; --i) {
472
- if ((this._data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK) || (this._data[i * CELL_SIZE + Cell.BG] & Attributes.CM_MASK)) {
473
- return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT);
499
+ if ((this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] & Content.HAS_CONTENT_MASK) || (this._data[i * Constants.CELL_INDICIES + Cell.BG] & Attributes.CM_MASK)) {
500
+ return i + (this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] >> Content.WIDTH_SHIFT);
474
501
  }
475
502
  }
476
503
  return 0;
477
504
  }
478
505
 
479
506
  public copyCellsFrom(src: BufferLine, srcCol: number, destCol: number, length: number, applyInReverse: boolean): void {
507
+ this._invalidateStringCache();
480
508
  const srcData = src._data;
481
509
  if (applyInReverse) {
482
510
  for (let cell = length - 1; cell >= 0; cell--) {
483
- for (let i = 0; i < CELL_SIZE; i++) {
484
- this._data[(destCol + cell) * CELL_SIZE + i] = srcData[(srcCol + cell) * CELL_SIZE + i];
511
+ for (let i = 0; i < Constants.CELL_INDICIES; i++) {
512
+ this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i];
485
513
  }
486
- if (srcData[(srcCol + cell) * CELL_SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
514
+ if (srcData[(srcCol + cell) * Constants.CELL_INDICIES + Cell.BG] & BgFlags.HAS_EXTENDED) {
487
515
  this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell];
488
516
  }
489
517
  }
490
518
  } else {
491
519
  for (let cell = 0; cell < length; cell++) {
492
- for (let i = 0; i < CELL_SIZE; i++) {
493
- this._data[(destCol + cell) * CELL_SIZE + i] = srcData[(srcCol + cell) * CELL_SIZE + i];
520
+ for (let i = 0; i < Constants.CELL_INDICIES; i++) {
521
+ this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i];
494
522
  }
495
- if (srcData[(srcCol + cell) * CELL_SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
523
+ if (srcData[(srcCol + cell) * Constants.CELL_INDICIES + Cell.BG] & BgFlags.HAS_EXTENDED) {
496
524
  this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell];
497
525
  }
498
526
  }
@@ -509,7 +537,8 @@ export class BufferLine implements IBufferLine {
509
537
  }
510
538
 
511
539
  /**
512
- * Translates the buffer line to a string.
540
+ * Translates the buffer line to a string. Caching only applies to canonical full-line translation
541
+ * requests (regardless of `trimRight` value).
513
542
  *
514
543
  * @param trimRight Whether to trim any empty cells on the right.
515
544
  * @param startCol The column to start the string (0-based inclusive).
@@ -522,6 +551,19 @@ export class BufferLine implements IBufferLine {
522
551
  * returned string, the corresponding entries in `outColumns` will have the same column number.
523
552
  */
524
553
  public translateToString(trimRight?: boolean, startCol?: number, endCol?: number, outColumns?: number[]): string {
554
+ const isCanonicalRequest = (startCol === undefined || startCol === 0) && endCol === undefined && outColumns === undefined;
555
+ if (isCanonicalRequest) {
556
+ this._stringCache.touch?.();
557
+ }
558
+ const stringCacheEntry = isCanonicalRequest ? this._getStringCacheEntry(false) : undefined;
559
+ if (isCanonicalRequest && stringCacheEntry?.value !== undefined) {
560
+ if (trimRight) {
561
+ return stringCacheEntry.isTrimmed ? stringCacheEntry.value : stringCacheEntry.value.trimEnd();
562
+ }
563
+ if (!stringCacheEntry.isTrimmed) {
564
+ return stringCacheEntry.value;
565
+ }
566
+ }
525
567
  startCol = startCol ?? 0;
526
568
  endCol = endCol ?? this.length;
527
569
  if (trimRight) {
@@ -530,12 +572,12 @@ export class BufferLine implements IBufferLine {
530
572
  if (outColumns) {
531
573
  outColumns.length = 0;
532
574
  }
533
- let result = '';
575
+ $translateToStringBuilder.reset();
534
576
  while (startCol < endCol) {
535
- const content = this._data[startCol * CELL_SIZE + Cell.CONTENT];
577
+ const content = this._data[startCol * Constants.CELL_INDICIES + Cell.CONTENT];
536
578
  const cp = content & Content.CODEPOINT_MASK;
537
579
  const chars = (content & Content.IS_COMBINED_MASK) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR;
538
- result += chars;
580
+ $translateToStringBuilder.append(chars);
539
581
  if (outColumns) {
540
582
  for (let i = 0; i < chars.length; ++i) {
541
583
  outColumns.push(startCol);
@@ -546,6 +588,36 @@ export class BufferLine implements IBufferLine {
546
588
  if (outColumns) {
547
589
  outColumns.push(startCol);
548
590
  }
591
+ const result = $translateToStringBuilder.toString();
592
+ $translateToStringBuilder.reset();
593
+ if (isCanonicalRequest) {
594
+ const cacheEntry = this._getStringCacheEntry(true)!;
595
+ cacheEntry.value = result;
596
+ cacheEntry.isTrimmed = !!trimRight;
597
+ }
549
598
  return result;
550
599
  }
600
+
601
+ protected _getStringCacheEntry(createIfNeeded: boolean): IBufferLineStringCacheEntry | undefined {
602
+ const cachedEntry = this._stringCacheEntryRef?.deref();
603
+ if (cachedEntry) {
604
+ if (cachedEntry.generation === this._stringCache.generation) {
605
+ return cachedEntry;
606
+ }
607
+ }
608
+ if (!createIfNeeded) {
609
+ return undefined;
610
+ }
611
+ const cacheEntry = this._stringCache.allocateEntry();
612
+ this._stringCacheEntryRef = new WeakRef(cacheEntry);
613
+ return cacheEntry;
614
+ }
615
+
616
+ private _invalidateStringCache(): void {
617
+ const cacheEntry = this._getStringCacheEntry(false);
618
+ if (cacheEntry) {
619
+ cacheEntry.value = undefined;
620
+ cacheEntry.isTrimmed = false;
621
+ }
622
+ }
551
623
  }