@xterm/xterm 6.1.0-beta.24 → 6.1.0-beta.240

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 (162) 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 +31 -21
  56. package/src/common/Event.ts +118 -0
  57. package/src/common/InputHandler.ts +283 -85
  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 +133 -65
  67. package/src/common/buffer/BufferLineStringCache.ts +69 -0
  68. package/src/common/buffer/BufferSet.ts +11 -6
  69. package/src/common/buffer/CellData.ts +57 -0
  70. package/src/common/buffer/Marker.ts +2 -2
  71. package/src/common/buffer/Types.ts +6 -2
  72. package/src/common/data/EscapeSequences.ts +71 -70
  73. package/src/common/input/Keyboard.ts +14 -7
  74. package/src/common/input/KittyKeyboard.ts +526 -0
  75. package/src/common/input/Win32InputMode.ts +297 -0
  76. package/src/common/input/WriteBuffer.ts +69 -32
  77. package/src/common/input/XParseColor.ts +2 -2
  78. package/src/common/parser/ApcParser.ts +196 -0
  79. package/src/common/parser/Constants.ts +14 -4
  80. package/src/common/parser/DcsParser.ts +11 -12
  81. package/src/common/parser/EscapeSequenceParser.ts +205 -63
  82. package/src/common/parser/OscParser.ts +11 -12
  83. package/src/common/parser/Params.ts +27 -8
  84. package/src/common/parser/Types.ts +36 -2
  85. package/src/common/public/BufferLineApiView.ts +2 -2
  86. package/src/common/public/BufferNamespaceApi.ts +3 -3
  87. package/src/common/public/ParserApi.ts +3 -0
  88. package/src/common/services/BufferService.ts +14 -9
  89. package/src/common/services/CharsetService.ts +4 -0
  90. package/src/common/services/CoreService.ts +22 -9
  91. package/src/common/services/DecorationService.ts +258 -8
  92. package/src/common/services/LogService.ts +1 -31
  93. package/src/common/services/{CoreMouseService.ts → MouseStateService.ts} +21 -132
  94. package/src/common/services/OptionsService.ts +13 -4
  95. package/src/common/services/ServiceRegistry.ts +9 -7
  96. package/src/common/services/Services.ts +49 -40
  97. package/src/common/services/UnicodeService.ts +1 -1
  98. package/typings/xterm.d.ts +318 -34
  99. package/src/common/Clone.ts +0 -23
  100. package/src/common/TypedArrayUtils.ts +0 -17
  101. package/src/vs/base/browser/browser.ts +0 -141
  102. package/src/vs/base/browser/canIUse.ts +0 -49
  103. package/src/vs/base/browser/dom.ts +0 -2369
  104. package/src/vs/base/browser/fastDomNode.ts +0 -316
  105. package/src/vs/base/browser/globalPointerMoveMonitor.ts +0 -112
  106. package/src/vs/base/browser/iframe.ts +0 -135
  107. package/src/vs/base/browser/keyboardEvent.ts +0 -213
  108. package/src/vs/base/browser/mouseEvent.ts +0 -229
  109. package/src/vs/base/browser/touch.ts +0 -372
  110. package/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +0 -303
  111. package/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +0 -114
  112. package/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +0 -720
  113. package/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +0 -165
  114. package/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +0 -114
  115. package/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +0 -243
  116. package/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts +0 -118
  117. package/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +0 -116
  118. package/src/vs/base/browser/ui/widget.ts +0 -57
  119. package/src/vs/base/browser/window.ts +0 -14
  120. package/src/vs/base/common/arrays.ts +0 -887
  121. package/src/vs/base/common/arraysFind.ts +0 -202
  122. package/src/vs/base/common/assert.ts +0 -71
  123. package/src/vs/base/common/async.ts +0 -1992
  124. package/src/vs/base/common/cancellation.ts +0 -148
  125. package/src/vs/base/common/charCode.ts +0 -450
  126. package/src/vs/base/common/collections.ts +0 -140
  127. package/src/vs/base/common/decorators.ts +0 -130
  128. package/src/vs/base/common/equals.ts +0 -146
  129. package/src/vs/base/common/errors.ts +0 -303
  130. package/src/vs/base/common/event.ts +0 -1778
  131. package/src/vs/base/common/functional.ts +0 -32
  132. package/src/vs/base/common/hash.ts +0 -316
  133. package/src/vs/base/common/iterator.ts +0 -159
  134. package/src/vs/base/common/keyCodes.ts +0 -526
  135. package/src/vs/base/common/keybindings.ts +0 -284
  136. package/src/vs/base/common/lazy.ts +0 -47
  137. package/src/vs/base/common/lifecycle.ts +0 -801
  138. package/src/vs/base/common/linkedList.ts +0 -142
  139. package/src/vs/base/common/map.ts +0 -202
  140. package/src/vs/base/common/numbers.ts +0 -98
  141. package/src/vs/base/common/observable.ts +0 -76
  142. package/src/vs/base/common/observableInternal/api.ts +0 -31
  143. package/src/vs/base/common/observableInternal/autorun.ts +0 -281
  144. package/src/vs/base/common/observableInternal/base.ts +0 -489
  145. package/src/vs/base/common/observableInternal/debugName.ts +0 -145
  146. package/src/vs/base/common/observableInternal/derived.ts +0 -428
  147. package/src/vs/base/common/observableInternal/lazyObservableValue.ts +0 -146
  148. package/src/vs/base/common/observableInternal/logging.ts +0 -328
  149. package/src/vs/base/common/observableInternal/promise.ts +0 -209
  150. package/src/vs/base/common/observableInternal/utils.ts +0 -610
  151. package/src/vs/base/common/platform.ts +0 -281
  152. package/src/vs/base/common/scrollable.ts +0 -522
  153. package/src/vs/base/common/sequence.ts +0 -34
  154. package/src/vs/base/common/stopwatch.ts +0 -43
  155. package/src/vs/base/common/strings.ts +0 -557
  156. package/src/vs/base/common/symbols.ts +0 -9
  157. package/src/vs/base/common/uint.ts +0 -59
  158. package/src/vs/patches/nls.ts +0 -90
  159. package/src/vs/typings/base-common.d.ts +0 -20
  160. package/src/vs/typings/require.d.ts +0 -42
  161. package/src/vs/typings/vscode-globals-nls.d.ts +0 -36
  162. 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
  }
@@ -9,25 +9,26 @@ 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
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;
12
+ // Buffer memory layout:
13
+ //
14
+ // [0]: content `uint32_t` - wcwidth(2) comb(1) codepoint(21)
15
+ // [1]: fg `uint32_t` - flags(8) r(8) g(8) b(8)
16
+ // [2]: bg `uint32_t` - flags(8) r(8) g(8) b(8)
17
+
18
+ const enum Constants {
19
+ /** The number of 32 bit array indices taken by one cell. */
20
+ CELL_INDICIES = 3,
21
+ /** Factor when to cleanup underlying array buffer after shrinking. */
22
+ CLEANUP_THRESHOLD = 2
23
+ }
23
24
 
24
25
  /**
25
26
  * Cell member indices.
26
27
  *
27
28
  * 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];`
29
+ * `content = data[column * Constants.CELL_INDICIES + Cell.CONTENT];`
30
+ * `fg = data[column * Constants.CELL_INDICIES + Cell.FG];`
31
+ * `bg = data[column * Constants.CELL_INDICIES + Cell.BG];`
31
32
  */
32
33
  const enum Cell {
33
34
  CONTENT = 0,
@@ -39,9 +40,19 @@ export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData());
39
40
 
40
41
  // Work variables to avoid garbage collection
41
42
  let $startIndex = 0;
43
+ const $workCell = new CellData();
42
44
 
43
- /** Factor when to cleanup underlying array buffer after shrinking. */
44
- const CLEANUP_THRESHOLD = 2;
45
+ export interface IBufferLineStringCacheEntry {
46
+ value: string | undefined;
47
+ isTrimmed: boolean;
48
+ generation: number;
49
+ }
50
+
51
+ export interface IBufferLineStringCache {
52
+ generation: number;
53
+ allocateEntry(): IBufferLineStringCacheEntry;
54
+ touch?(): void;
55
+ }
45
56
 
46
57
  /**
47
58
  * Typed array based bufferline implementation.
@@ -62,11 +73,17 @@ export class BufferLine implements IBufferLine {
62
73
  protected _data: Uint32Array;
63
74
  protected _combined: {[index: number]: string} = {};
64
75
  protected _extendedAttrs: {[index: number]: IExtendedAttrs | undefined} = {};
76
+ protected _stringCacheEntryRef: WeakRef<IBufferLineStringCacheEntry> | undefined;
65
77
  public length: number;
66
78
 
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]);
79
+ constructor(
80
+ protected readonly _stringCache: IBufferLineStringCache,
81
+ cols: number,
82
+ fillCellData?: ICellData,
83
+ public isWrapped: boolean = false
84
+ ) {
85
+ this._data = new Uint32Array(cols * Constants.CELL_INDICIES);
86
+ const cell = fillCellData ?? CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
70
87
  for (let i = 0; i < cols; ++i) {
71
88
  this.setCell(i, cell);
72
89
  }
@@ -78,10 +95,10 @@ export class BufferLine implements IBufferLine {
78
95
  * @deprecated
79
96
  */
80
97
  public get(index: number): CharData {
81
- const content = this._data[index * CELL_SIZE + Cell.CONTENT];
98
+ const content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
82
99
  const cp = content & Content.CODEPOINT_MASK;
83
100
  return [
84
- this._data[index * CELL_SIZE + Cell.FG],
101
+ this._data[index * Constants.CELL_INDICIES + Cell.FG],
85
102
  (content & Content.IS_COMBINED_MASK)
86
103
  ? this._combined[index]
87
104
  : (cp) ? stringFromCodePoint(cp) : '',
@@ -97,12 +114,13 @@ export class BufferLine implements IBufferLine {
97
114
  * @deprecated
98
115
  */
99
116
  public set(index: number, value: CharData): void {
100
- this._data[index * CELL_SIZE + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];
117
+ this._invalidateStringCache();
118
+ this._data[index * Constants.CELL_INDICIES + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];
101
119
  if (value[CHAR_DATA_CHAR_INDEX].length > 1) {
102
120
  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);
121
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
104
122
  } else {
105
- this._data[index * CELL_SIZE + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
123
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
106
124
  }
107
125
  }
108
126
 
@@ -111,22 +129,22 @@ export class BufferLine implements IBufferLine {
111
129
  * use these when only one value is needed, otherwise use `loadCell`
112
130
  */
113
131
  public getWidth(index: number): number {
114
- return this._data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT;
132
+ return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] >> Content.WIDTH_SHIFT;
115
133
  }
116
134
 
117
135
  /** Test whether content has width. */
118
136
  public hasWidth(index: number): number {
119
- return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.WIDTH_MASK;
137
+ return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.WIDTH_MASK;
120
138
  }
121
139
 
122
140
  /** Get FG cell component. */
123
141
  public getFg(index: number): number {
124
- return this._data[index * CELL_SIZE + Cell.FG];
142
+ return this._data[index * Constants.CELL_INDICIES + Cell.FG];
125
143
  }
126
144
 
127
145
  /** Get BG cell component. */
128
146
  public getBg(index: number): number {
129
- return this._data[index * CELL_SIZE + Cell.BG];
147
+ return this._data[index * Constants.CELL_INDICIES + Cell.BG];
130
148
  }
131
149
 
132
150
  /**
@@ -135,7 +153,7 @@ export class BufferLine implements IBufferLine {
135
153
  * from real empty cells.
136
154
  */
137
155
  public hasContent(index: number): number {
138
- return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK;
156
+ return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.HAS_CONTENT_MASK;
139
157
  }
140
158
 
141
159
  /**
@@ -144,7 +162,7 @@ export class BufferLine implements IBufferLine {
144
162
  * a single UTF32 codepoint or the last codepoint of a combined string.
145
163
  */
146
164
  public getCodePoint(index: number): number {
147
- const content = this._data[index * CELL_SIZE + Cell.CONTENT];
165
+ const content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
148
166
  if (content & Content.IS_COMBINED_MASK) {
149
167
  return this._combined[index].charCodeAt(this._combined[index].length - 1);
150
168
  }
@@ -153,12 +171,12 @@ export class BufferLine implements IBufferLine {
153
171
 
154
172
  /** Test whether the cell contains a combined string. */
155
173
  public isCombined(index: number): number {
156
- return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.IS_COMBINED_MASK;
174
+ return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.IS_COMBINED_MASK;
157
175
  }
158
176
 
159
177
  /** Returns the string content of the cell. */
160
178
  public getString(index: number): string {
161
- const content = this._data[index * CELL_SIZE + Cell.CONTENT];
179
+ const content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
162
180
  if (content & Content.IS_COMBINED_MASK) {
163
181
  return this._combined[index];
164
182
  }
@@ -171,7 +189,7 @@ export class BufferLine implements IBufferLine {
171
189
 
172
190
  /** Get state of protected flag. */
173
191
  public isProtected(index: number): number {
174
- return this._data[index * CELL_SIZE + Cell.BG] & BgFlags.PROTECTED;
192
+ return this._data[index * Constants.CELL_INDICIES + Cell.BG] & BgFlags.PROTECTED;
175
193
  }
176
194
 
177
195
  /**
@@ -179,7 +197,7 @@ export class BufferLine implements IBufferLine {
179
197
  * to GC as it significantly reduced the amount of new objects/references needed.
180
198
  */
181
199
  public loadCell(index: number, cell: ICellData): ICellData {
182
- $startIndex = index * CELL_SIZE;
200
+ $startIndex = index * Constants.CELL_INDICIES;
183
201
  cell.content = this._data[$startIndex + Cell.CONTENT];
184
202
  cell.fg = this._data[$startIndex + Cell.FG];
185
203
  cell.bg = this._data[$startIndex + Cell.BG];
@@ -196,15 +214,16 @@ export class BufferLine implements IBufferLine {
196
214
  * Set data at `index` to `cell`.
197
215
  */
198
216
  public setCell(index: number, cell: ICellData): void {
217
+ this._invalidateStringCache();
199
218
  if (cell.content & Content.IS_COMBINED_MASK) {
200
219
  this._combined[index] = cell.combinedData;
201
220
  }
202
221
  if (cell.bg & BgFlags.HAS_EXTENDED) {
203
222
  this._extendedAttrs[index] = cell.extended;
204
223
  }
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;
224
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = cell.content;
225
+ this._data[index * Constants.CELL_INDICIES + Cell.FG] = cell.fg;
226
+ this._data[index * Constants.CELL_INDICIES + Cell.BG] = cell.bg;
208
227
  }
209
228
 
210
229
  /**
@@ -213,12 +232,13 @@ export class BufferLine implements IBufferLine {
213
232
  * it gets an optimized access method.
214
233
  */
215
234
  public setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void {
235
+ this._invalidateStringCache();
216
236
  if (attrs.bg & BgFlags.HAS_EXTENDED) {
217
237
  this._extendedAttrs[index] = attrs.extended;
218
238
  }
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;
239
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
240
+ this._data[index * Constants.CELL_INDICIES + Cell.FG] = attrs.fg;
241
+ this._data[index * Constants.CELL_INDICIES + Cell.BG] = attrs.bg;
222
242
  }
223
243
 
224
244
  /**
@@ -228,7 +248,8 @@ export class BufferLine implements IBufferLine {
228
248
  * by the previous `setDataFromCodePoint` call, we can omit it here.
229
249
  */
230
250
  public addCodepointToCell(index: number, codePoint: number, width: number): void {
231
- let content = this._data[index * CELL_SIZE + Cell.CONTENT];
251
+ this._invalidateStringCache();
252
+ let content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
232
253
  if (content & Content.IS_COMBINED_MASK) {
233
254
  // we already have a combined string, simply add
234
255
  this._combined[index] += stringFromCodePoint(codePoint);
@@ -250,10 +271,11 @@ export class BufferLine implements IBufferLine {
250
271
  content &= ~Content.WIDTH_MASK;
251
272
  content |= width << Content.WIDTH_SHIFT;
252
273
  }
253
- this._data[index * CELL_SIZE + Cell.CONTENT] = content;
274
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = content;
254
275
  }
255
276
 
256
277
  public insertCells(pos: number, n: number, fillCellData: ICellData): void {
278
+ this._invalidateStringCache();
257
279
  pos %= this.length;
258
280
 
259
281
  // handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char
@@ -262,9 +284,8 @@ export class BufferLine implements IBufferLine {
262
284
  }
263
285
 
264
286
  if (n < this.length - pos) {
265
- const cell = new CellData();
266
287
  for (let i = this.length - pos - n - 1; i >= 0; --i) {
267
- this.setCell(pos + n + i, this.loadCell(pos + i, cell));
288
+ this.setCell(pos + n + i, this.loadCell(pos + i, $workCell));
268
289
  }
269
290
  for (let i = 0; i < n; ++i) {
270
291
  this.setCell(pos + i, fillCellData);
@@ -282,11 +303,11 @@ export class BufferLine implements IBufferLine {
282
303
  }
283
304
 
284
305
  public deleteCells(pos: number, n: number, fillCellData: ICellData): void {
306
+ this._invalidateStringCache();
285
307
  pos %= this.length;
286
308
  if (n < this.length - pos) {
287
- const cell = new CellData();
288
309
  for (let i = 0; i < this.length - pos - n; ++i) {
289
- this.setCell(pos + i, this.loadCell(pos + n + i, cell));
310
+ this.setCell(pos + i, this.loadCell(pos + n + i, $workCell));
290
311
  }
291
312
  for (let i = this.length - n; i < this.length; ++i) {
292
313
  this.setCell(i, fillCellData);
@@ -309,6 +330,7 @@ export class BufferLine implements IBufferLine {
309
330
  }
310
331
 
311
332
  public replaceCells(start: number, end: number, fillCellData: ICellData, respectProtect: boolean = false): void {
333
+ this._invalidateStringCache();
312
334
  // full branching on respectProtect==true, hopefully getting fast JIT for standard case
313
335
  if (respectProtect) {
314
336
  if (start && this.getWidth(start - 1) === 2 && !this.isProtected(start - 1)) {
@@ -345,13 +367,14 @@ export class BufferLine implements IBufferLine {
345
367
  * The underlying array buffer will not change if there is still enough space
346
368
  * to hold the new buffer line data.
347
369
  * Returns a boolean indicating, whether a `cleanupMemory` call would free
348
- * excess memory (true after shrinking > CLEANUP_THRESHOLD).
370
+ * excess memory (true after shrinking > Constants.CLEANUP_THRESHOLD).
349
371
  */
350
372
  public resize(cols: number, fillCellData: ICellData): boolean {
373
+ this._invalidateStringCache();
351
374
  if (cols === this.length) {
352
- return this._data.length * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength;
375
+ return this._data.length * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength;
353
376
  }
354
- const uint32Cells = cols * CELL_SIZE;
377
+ const uint32Cells = cols * Constants.CELL_INDICIES;
355
378
  if (cols > this.length) {
356
379
  if (this._data.buffer.byteLength >= uint32Cells * 4) {
357
380
  // optimization: avoid alloc and data copy if buffer has enough room
@@ -386,17 +409,17 @@ export class BufferLine implements IBufferLine {
386
409
  }
387
410
  }
388
411
  this.length = cols;
389
- return uint32Cells * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength;
412
+ return uint32Cells * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength;
390
413
  }
391
414
 
392
415
  /**
393
416
  * Cleanup underlying array buffer.
394
417
  * A cleanup will be triggered if the array buffer exceeds the actual used
395
- * memory by a factor of CLEANUP_THRESHOLD.
418
+ * memory by a factor of Constants.CLEANUP_THRESHOLD.
396
419
  * Returns 0 or 1 indicating whether a cleanup happened.
397
420
  */
398
421
  public cleanupMemory(): number {
399
- if (this._data.length * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength) {
422
+ if (this._data.length * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength) {
400
423
  const data = new Uint32Array(this._data.length);
401
424
  data.set(this._data);
402
425
  this._data = data;
@@ -407,6 +430,7 @@ export class BufferLine implements IBufferLine {
407
430
 
408
431
  /** fill a line with fillCharData */
409
432
  public fill(fillCellData: ICellData, respectProtect: boolean = false): void {
433
+ this._invalidateStringCache();
410
434
  // full branching on respectProtect==true, hopefully getting fast JIT for standard case
411
435
  if (respectProtect) {
412
436
  for (let i = 0; i < this.length; ++i) {
@@ -425,6 +449,7 @@ export class BufferLine implements IBufferLine {
425
449
 
426
450
  /** alter to a full copy of line */
427
451
  public copyFrom(line: BufferLine): void {
452
+ this._invalidateStringCache();
428
453
  if (this.length !== line.length) {
429
454
  this._data = new Uint32Array(line._data);
430
455
  } else {
@@ -445,7 +470,7 @@ export class BufferLine implements IBufferLine {
445
470
 
446
471
  /** create a new clone */
447
472
  public clone(): IBufferLine {
448
- const newLine = new BufferLine(0);
473
+ const newLine = new BufferLine(this._stringCache, 0, undefined, false);
449
474
  newLine._data = new Uint32Array(this._data);
450
475
  newLine.length = this.length;
451
476
  for (const el in this._combined) {
@@ -460,8 +485,8 @@ export class BufferLine implements IBufferLine {
460
485
 
461
486
  public getTrimmedLength(): number {
462
487
  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);
488
+ if ((this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] & Content.HAS_CONTENT_MASK)) {
489
+ return i + (this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] >> Content.WIDTH_SHIFT);
465
490
  }
466
491
  }
467
492
  return 0;
@@ -469,30 +494,31 @@ export class BufferLine implements IBufferLine {
469
494
 
470
495
  public getNoBgTrimmedLength(): number {
471
496
  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);
497
+ if ((this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] & Content.HAS_CONTENT_MASK) || (this._data[i * Constants.CELL_INDICIES + Cell.BG] & Attributes.CM_MASK)) {
498
+ return i + (this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] >> Content.WIDTH_SHIFT);
474
499
  }
475
500
  }
476
501
  return 0;
477
502
  }
478
503
 
479
504
  public copyCellsFrom(src: BufferLine, srcCol: number, destCol: number, length: number, applyInReverse: boolean): void {
505
+ this._invalidateStringCache();
480
506
  const srcData = src._data;
481
507
  if (applyInReverse) {
482
508
  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];
509
+ for (let i = 0; i < Constants.CELL_INDICIES; i++) {
510
+ this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i];
485
511
  }
486
- if (srcData[(srcCol + cell) * CELL_SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
512
+ if (srcData[(srcCol + cell) * Constants.CELL_INDICIES + Cell.BG] & BgFlags.HAS_EXTENDED) {
487
513
  this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell];
488
514
  }
489
515
  }
490
516
  } else {
491
517
  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];
518
+ for (let i = 0; i < Constants.CELL_INDICIES; i++) {
519
+ this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i];
494
520
  }
495
- if (srcData[(srcCol + cell) * CELL_SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
521
+ if (srcData[(srcCol + cell) * Constants.CELL_INDICIES + Cell.BG] & BgFlags.HAS_EXTENDED) {
496
522
  this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell];
497
523
  }
498
524
  }
@@ -509,7 +535,8 @@ export class BufferLine implements IBufferLine {
509
535
  }
510
536
 
511
537
  /**
512
- * Translates the buffer line to a string.
538
+ * Translates the buffer line to a string. Caching only applies to canonical full-line translation
539
+ * requests (regardless of `trimRight` value).
513
540
  *
514
541
  * @param trimRight Whether to trim any empty cells on the right.
515
542
  * @param startCol The column to start the string (0-based inclusive).
@@ -522,6 +549,19 @@ export class BufferLine implements IBufferLine {
522
549
  * returned string, the corresponding entries in `outColumns` will have the same column number.
523
550
  */
524
551
  public translateToString(trimRight?: boolean, startCol?: number, endCol?: number, outColumns?: number[]): string {
552
+ const isCanonicalRequest = (startCol === undefined || startCol === 0) && endCol === undefined && outColumns === undefined;
553
+ if (isCanonicalRequest) {
554
+ this._stringCache.touch?.();
555
+ }
556
+ const stringCacheEntry = isCanonicalRequest ? this._getStringCacheEntry(false) : undefined;
557
+ if (isCanonicalRequest && stringCacheEntry?.value !== undefined) {
558
+ if (trimRight) {
559
+ return stringCacheEntry.isTrimmed ? stringCacheEntry.value : stringCacheEntry.value.trimEnd();
560
+ }
561
+ if (!stringCacheEntry.isTrimmed) {
562
+ return stringCacheEntry.value;
563
+ }
564
+ }
525
565
  startCol = startCol ?? 0;
526
566
  endCol = endCol ?? this.length;
527
567
  if (trimRight) {
@@ -532,7 +572,7 @@ export class BufferLine implements IBufferLine {
532
572
  }
533
573
  let result = '';
534
574
  while (startCol < endCol) {
535
- const content = this._data[startCol * CELL_SIZE + Cell.CONTENT];
575
+ const content = this._data[startCol * Constants.CELL_INDICIES + Cell.CONTENT];
536
576
  const cp = content & Content.CODEPOINT_MASK;
537
577
  const chars = (content & Content.IS_COMBINED_MASK) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR;
538
578
  result += chars;
@@ -546,6 +586,34 @@ export class BufferLine implements IBufferLine {
546
586
  if (outColumns) {
547
587
  outColumns.push(startCol);
548
588
  }
589
+ if (isCanonicalRequest) {
590
+ const cacheEntry = this._getStringCacheEntry(true)!;
591
+ cacheEntry.value = result;
592
+ cacheEntry.isTrimmed = !!trimRight;
593
+ }
549
594
  return result;
550
595
  }
596
+
597
+ protected _getStringCacheEntry(createIfNeeded: boolean): IBufferLineStringCacheEntry | undefined {
598
+ const cachedEntry = this._stringCacheEntryRef?.deref();
599
+ if (cachedEntry) {
600
+ if (cachedEntry.generation === this._stringCache.generation) {
601
+ return cachedEntry;
602
+ }
603
+ }
604
+ if (!createIfNeeded) {
605
+ return undefined;
606
+ }
607
+ const cacheEntry = this._stringCache.allocateEntry();
608
+ this._stringCacheEntryRef = new WeakRef(cacheEntry);
609
+ return cacheEntry;
610
+ }
611
+
612
+ private _invalidateStringCache(): void {
613
+ const cacheEntry = this._getStringCacheEntry(false);
614
+ if (cacheEntry) {
615
+ cacheEntry.value = undefined;
616
+ cacheEntry.isTrimmed = false;
617
+ }
618
+ }
551
619
  }