@xterm/xterm 6.1.0-beta.28 → 6.1.0-beta.281

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 (181) 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 +56 -45
  8. package/src/browser/AccessibilityManager.ts +18 -13
  9. package/src/browser/Clipboard.ts +8 -5
  10. package/src/browser/ColorContrastCache.ts +3 -3
  11. package/src/browser/CoreBrowserTerminal.ts +179 -348
  12. package/src/browser/Dom.ts +178 -0
  13. package/src/browser/Linkifier.ts +14 -14
  14. package/src/browser/OscLinkProvider.ts +86 -18
  15. package/src/browser/RenderDebouncer.ts +7 -9
  16. package/src/browser/TimeBasedDebouncer.ts +12 -5
  17. package/src/browser/Types.ts +16 -14
  18. package/src/browser/Viewport.ts +58 -23
  19. package/src/browser/decorations/BufferDecorationRenderer.ts +3 -3
  20. package/src/browser/decorations/ColorZoneStore.ts +1 -1
  21. package/src/browser/decorations/OverviewRulerRenderer.ts +36 -20
  22. package/src/browser/input/CompositionHelper.ts +47 -11
  23. package/src/browser/input/Mouse.ts +5 -9
  24. package/src/browser/input/MoveToCell.ts +3 -3
  25. package/src/browser/public/Terminal.ts +33 -36
  26. package/src/browser/renderer/dom/DomRenderer.ts +265 -89
  27. package/src/browser/renderer/dom/DomRendererRowFactory.ts +34 -27
  28. package/src/browser/renderer/dom/WidthCache.ts +57 -55
  29. package/src/browser/renderer/shared/Constants.ts +7 -0
  30. package/src/browser/renderer/shared/RendererUtils.ts +1 -1
  31. package/src/browser/renderer/shared/SelectionRenderModel.ts +2 -2
  32. package/src/browser/renderer/shared/TextBlinkStateManager.ts +97 -0
  33. package/src/browser/renderer/shared/Types.ts +10 -4
  34. package/src/browser/scrollable/abstractScrollbar.ts +300 -0
  35. package/src/browser/scrollable/fastDomNode.ts +126 -0
  36. package/src/browser/scrollable/globalPointerMoveMonitor.ts +90 -0
  37. package/src/browser/scrollable/horizontalScrollbar.ts +85 -0
  38. package/src/browser/scrollable/mouseEvent.ts +292 -0
  39. package/src/browser/scrollable/scrollable.ts +486 -0
  40. package/src/browser/scrollable/scrollableElement.ts +581 -0
  41. package/src/browser/scrollable/scrollableElementOptions.ts +161 -0
  42. package/src/browser/scrollable/scrollbarArrow.ts +110 -0
  43. package/src/browser/scrollable/scrollbarState.ts +246 -0
  44. package/src/browser/scrollable/scrollbarVisibilityController.ts +113 -0
  45. package/src/browser/scrollable/touch.ts +485 -0
  46. package/src/browser/scrollable/verticalScrollbar.ts +143 -0
  47. package/src/browser/scrollable/widget.ts +23 -0
  48. package/src/browser/selection/SelectionModel.ts +1 -1
  49. package/src/browser/services/CharSizeService.ts +4 -4
  50. package/src/browser/services/CharacterJoinerService.ts +13 -11
  51. package/src/browser/services/CoreBrowserService.ts +7 -5
  52. package/src/browser/services/KeyboardService.ts +67 -0
  53. package/src/browser/services/LinkProviderService.ts +3 -3
  54. package/src/browser/services/MouseCoordsService.ts +47 -0
  55. package/src/browser/services/MouseService.ts +619 -23
  56. package/src/browser/services/RenderService.ts +33 -21
  57. package/src/browser/services/SelectionService.ts +72 -54
  58. package/src/browser/services/Services.ts +44 -21
  59. package/src/browser/services/ThemeService.ts +9 -9
  60. package/src/common/Async.ts +141 -0
  61. package/src/common/CircularList.ts +24 -3
  62. package/src/common/Color.ts +13 -5
  63. package/src/common/CoreTerminal.ts +61 -35
  64. package/src/common/Event.ts +118 -0
  65. package/src/common/InputHandler.ts +286 -105
  66. package/src/common/Lifecycle.ts +113 -0
  67. package/src/common/Platform.ts +15 -5
  68. package/src/common/SortedList.ts +8 -4
  69. package/src/common/StringBuilder.ts +67 -0
  70. package/src/common/TaskQueue.ts +17 -8
  71. package/src/common/Types.ts +68 -206
  72. package/src/common/Version.ts +1 -1
  73. package/src/common/WindowsMode.ts +2 -2
  74. package/src/common/buffer/AttributeData.ts +3 -2
  75. package/src/common/buffer/Buffer.ts +47 -32
  76. package/src/common/buffer/BufferLine.ts +175 -100
  77. package/src/common/buffer/BufferLineStringCache.ts +69 -0
  78. package/src/common/buffer/BufferReflow.ts +7 -4
  79. package/src/common/buffer/BufferSet.ts +13 -9
  80. package/src/common/buffer/CellData.ts +61 -4
  81. package/src/common/buffer/Constants.ts +13 -13
  82. package/src/common/buffer/Marker.ts +4 -4
  83. package/src/common/buffer/Types.ts +153 -3
  84. package/src/common/data/Charsets.ts +4 -7
  85. package/src/common/data/EscapeSequences.ts +71 -70
  86. package/src/common/input/Keyboard.ts +17 -10
  87. package/src/common/input/KittyKeyboard.ts +526 -0
  88. package/src/common/input/TextDecoder.ts +3 -3
  89. package/src/common/input/UnicodeV6.ts +3 -2
  90. package/src/common/input/Win32InputMode.ts +297 -0
  91. package/src/common/input/WriteBuffer.ts +108 -39
  92. package/src/common/input/XParseColor.ts +2 -2
  93. package/src/common/parser/ApcParser.ts +196 -0
  94. package/src/common/parser/Constants.ts +14 -4
  95. package/src/common/parser/DcsParser.ts +15 -16
  96. package/src/common/parser/EscapeSequenceParser.ts +214 -72
  97. package/src/common/parser/OscParser.ts +14 -15
  98. package/src/common/parser/Params.ts +31 -12
  99. package/src/common/parser/Types.ts +40 -30
  100. package/src/common/public/BufferApiView.ts +3 -3
  101. package/src/common/public/BufferLineApiView.ts +4 -4
  102. package/src/common/public/BufferNamespaceApi.ts +5 -5
  103. package/src/common/public/ParserApi.ts +5 -2
  104. package/src/common/public/UnicodeApi.ts +1 -1
  105. package/src/common/services/BufferService.ts +17 -13
  106. package/src/common/services/CharsetService.ts +6 -2
  107. package/src/common/services/CoreService.ts +23 -10
  108. package/src/common/services/DecorationService.ts +260 -15
  109. package/src/common/services/InstantiationService.ts +2 -2
  110. package/src/common/services/LogService.ts +2 -32
  111. package/src/common/services/{CoreMouseService.ts → MouseStateService.ts} +22 -133
  112. package/src/common/services/OptionsService.ts +17 -7
  113. package/src/common/services/OscLinkService.ts +3 -2
  114. package/src/common/services/ServiceRegistry.ts +14 -8
  115. package/src/common/services/Services.ts +54 -50
  116. package/src/common/services/UnicodeService.ts +6 -11
  117. package/typings/xterm.d.ts +329 -34
  118. package/src/common/Clone.ts +0 -23
  119. package/src/common/TypedArrayUtils.ts +0 -17
  120. package/src/vs/base/browser/browser.ts +0 -141
  121. package/src/vs/base/browser/canIUse.ts +0 -49
  122. package/src/vs/base/browser/dom.ts +0 -2369
  123. package/src/vs/base/browser/fastDomNode.ts +0 -316
  124. package/src/vs/base/browser/globalPointerMoveMonitor.ts +0 -112
  125. package/src/vs/base/browser/iframe.ts +0 -135
  126. package/src/vs/base/browser/keyboardEvent.ts +0 -213
  127. package/src/vs/base/browser/mouseEvent.ts +0 -229
  128. package/src/vs/base/browser/touch.ts +0 -372
  129. package/src/vs/base/browser/ui/scrollbar/abstractScrollbar.ts +0 -303
  130. package/src/vs/base/browser/ui/scrollbar/horizontalScrollbar.ts +0 -114
  131. package/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +0 -720
  132. package/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +0 -165
  133. package/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +0 -114
  134. package/src/vs/base/browser/ui/scrollbar/scrollbarState.ts +0 -243
  135. package/src/vs/base/browser/ui/scrollbar/scrollbarVisibilityController.ts +0 -118
  136. package/src/vs/base/browser/ui/scrollbar/verticalScrollbar.ts +0 -116
  137. package/src/vs/base/browser/ui/widget.ts +0 -57
  138. package/src/vs/base/browser/window.ts +0 -14
  139. package/src/vs/base/common/arrays.ts +0 -887
  140. package/src/vs/base/common/arraysFind.ts +0 -202
  141. package/src/vs/base/common/assert.ts +0 -71
  142. package/src/vs/base/common/async.ts +0 -1992
  143. package/src/vs/base/common/cancellation.ts +0 -148
  144. package/src/vs/base/common/charCode.ts +0 -450
  145. package/src/vs/base/common/collections.ts +0 -140
  146. package/src/vs/base/common/decorators.ts +0 -130
  147. package/src/vs/base/common/equals.ts +0 -146
  148. package/src/vs/base/common/errors.ts +0 -303
  149. package/src/vs/base/common/event.ts +0 -1778
  150. package/src/vs/base/common/functional.ts +0 -32
  151. package/src/vs/base/common/hash.ts +0 -316
  152. package/src/vs/base/common/iterator.ts +0 -159
  153. package/src/vs/base/common/keyCodes.ts +0 -526
  154. package/src/vs/base/common/keybindings.ts +0 -284
  155. package/src/vs/base/common/lazy.ts +0 -47
  156. package/src/vs/base/common/lifecycle.ts +0 -801
  157. package/src/vs/base/common/linkedList.ts +0 -142
  158. package/src/vs/base/common/map.ts +0 -202
  159. package/src/vs/base/common/numbers.ts +0 -98
  160. package/src/vs/base/common/observable.ts +0 -76
  161. package/src/vs/base/common/observableInternal/api.ts +0 -31
  162. package/src/vs/base/common/observableInternal/autorun.ts +0 -281
  163. package/src/vs/base/common/observableInternal/base.ts +0 -489
  164. package/src/vs/base/common/observableInternal/debugName.ts +0 -145
  165. package/src/vs/base/common/observableInternal/derived.ts +0 -428
  166. package/src/vs/base/common/observableInternal/lazyObservableValue.ts +0 -146
  167. package/src/vs/base/common/observableInternal/logging.ts +0 -328
  168. package/src/vs/base/common/observableInternal/promise.ts +0 -209
  169. package/src/vs/base/common/observableInternal/utils.ts +0 -610
  170. package/src/vs/base/common/platform.ts +0 -281
  171. package/src/vs/base/common/scrollable.ts +0 -522
  172. package/src/vs/base/common/sequence.ts +0 -34
  173. package/src/vs/base/common/stopwatch.ts +0 -43
  174. package/src/vs/base/common/strings.ts +0 -557
  175. package/src/vs/base/common/symbols.ts +0 -9
  176. package/src/vs/base/common/uint.ts +0 -59
  177. package/src/vs/patches/nls.ts +0 -90
  178. package/src/vs/typings/base-common.d.ts +0 -20
  179. package/src/vs/typings/require.d.ts +0 -42
  180. package/src/vs/typings/vscode-globals-nls.d.ts +0 -36
  181. package/src/vs/typings/vscode-globals-product.d.ts +0 -33
@@ -3,31 +3,33 @@
3
3
  * @license MIT
4
4
  */
5
5
 
6
- import { CharData, IAttributeData, IBufferLine, ICellData, IExtendedAttrs } from 'common/Types';
7
- import { AttributeData } from 'common/buffer/AttributeData';
8
- import { CellData } from 'common/buffer/CellData';
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
- 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;
6
+ import { CharData, IAttributeData, IBufferLine, ICellData, IExtendedAttrs } from './Types';
7
+ import { AttributeData } from './AttributeData';
8
+ import { CellData } from './CellData';
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 './Constants';
10
+ import { stringFromCodePoint } from '../input/TextDecoder';
11
+ import { StringBuilder } from '../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.
@@ -60,13 +73,21 @@ const CLEANUP_THRESHOLD = 2;
60
73
  */
61
74
  export class BufferLine implements IBufferLine {
62
75
  protected _data: Uint32Array;
76
+ /** Sparse cache; only read when `IS_COMBINED_MASK` is set in `_data`. */
63
77
  protected _combined: {[index: number]: string} = {};
78
+ /** Sparse cache; only read when `HAS_EXTENDED` is set in `_data`. */
64
79
  protected _extendedAttrs: {[index: number]: IExtendedAttrs | undefined} = {};
80
+ protected _stringCacheEntryRef: WeakRef<IBufferLineStringCacheEntry> | undefined;
65
81
  public length: number;
66
82
 
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]);
83
+ constructor(
84
+ protected readonly _stringCache: IBufferLineStringCache,
85
+ cols: number,
86
+ fillCellData?: ICellData,
87
+ public isWrapped: boolean = false
88
+ ) {
89
+ this._data = new Uint32Array(cols * Constants.CELL_INDICIES);
90
+ const cell = fillCellData ?? CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
70
91
  for (let i = 0; i < cols; ++i) {
71
92
  this.setCell(i, cell);
72
93
  }
@@ -78,10 +99,10 @@ export class BufferLine implements IBufferLine {
78
99
  * @deprecated
79
100
  */
80
101
  public get(index: number): CharData {
81
- const content = this._data[index * CELL_SIZE + Cell.CONTENT];
102
+ const content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
82
103
  const cp = content & Content.CODEPOINT_MASK;
83
104
  return [
84
- this._data[index * CELL_SIZE + Cell.FG],
105
+ this._data[index * Constants.CELL_INDICIES + Cell.FG],
85
106
  (content & Content.IS_COMBINED_MASK)
86
107
  ? this._combined[index]
87
108
  : (cp) ? stringFromCodePoint(cp) : '',
@@ -97,12 +118,13 @@ export class BufferLine implements IBufferLine {
97
118
  * @deprecated
98
119
  */
99
120
  public set(index: number, value: CharData): void {
100
- this._data[index * CELL_SIZE + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];
121
+ this._invalidateStringCache();
122
+ this._data[index * Constants.CELL_INDICIES + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];
101
123
  if (value[CHAR_DATA_CHAR_INDEX].length > 1) {
102
124
  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);
125
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
104
126
  } else {
105
- this._data[index * CELL_SIZE + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
127
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
106
128
  }
107
129
  }
108
130
 
@@ -111,22 +133,22 @@ export class BufferLine implements IBufferLine {
111
133
  * use these when only one value is needed, otherwise use `loadCell`
112
134
  */
113
135
  public getWidth(index: number): number {
114
- return this._data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT;
136
+ return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] >> Content.WIDTH_SHIFT;
115
137
  }
116
138
 
117
139
  /** Test whether content has width. */
118
140
  public hasWidth(index: number): number {
119
- return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.WIDTH_MASK;
141
+ return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.WIDTH_MASK;
120
142
  }
121
143
 
122
144
  /** Get FG cell component. */
123
145
  public getFg(index: number): number {
124
- return this._data[index * CELL_SIZE + Cell.FG];
146
+ return this._data[index * Constants.CELL_INDICIES + Cell.FG];
125
147
  }
126
148
 
127
149
  /** Get BG cell component. */
128
150
  public getBg(index: number): number {
129
- return this._data[index * CELL_SIZE + Cell.BG];
151
+ return this._data[index * Constants.CELL_INDICIES + Cell.BG];
130
152
  }
131
153
 
132
154
  /**
@@ -135,7 +157,7 @@ export class BufferLine implements IBufferLine {
135
157
  * from real empty cells.
136
158
  */
137
159
  public hasContent(index: number): number {
138
- return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK;
160
+ return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.HAS_CONTENT_MASK;
139
161
  }
140
162
 
141
163
  /**
@@ -144,7 +166,7 @@ export class BufferLine implements IBufferLine {
144
166
  * a single UTF32 codepoint or the last codepoint of a combined string.
145
167
  */
146
168
  public getCodePoint(index: number): number {
147
- const content = this._data[index * CELL_SIZE + Cell.CONTENT];
169
+ const content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
148
170
  if (content & Content.IS_COMBINED_MASK) {
149
171
  return this._combined[index].charCodeAt(this._combined[index].length - 1);
150
172
  }
@@ -153,12 +175,12 @@ export class BufferLine implements IBufferLine {
153
175
 
154
176
  /** Test whether the cell contains a combined string. */
155
177
  public isCombined(index: number): number {
156
- return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.IS_COMBINED_MASK;
178
+ return this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] & Content.IS_COMBINED_MASK;
157
179
  }
158
180
 
159
181
  /** Returns the string content of the cell. */
160
182
  public getString(index: number): string {
161
- const content = this._data[index * CELL_SIZE + Cell.CONTENT];
183
+ const content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
162
184
  if (content & Content.IS_COMBINED_MASK) {
163
185
  return this._combined[index];
164
186
  }
@@ -171,7 +193,7 @@ export class BufferLine implements IBufferLine {
171
193
 
172
194
  /** Get state of protected flag. */
173
195
  public isProtected(index: number): number {
174
- return this._data[index * CELL_SIZE + Cell.BG] & BgFlags.PROTECTED;
196
+ return this._data[index * Constants.CELL_INDICIES + Cell.BG] & BgFlags.PROTECTED;
175
197
  }
176
198
 
177
199
  /**
@@ -179,15 +201,21 @@ export class BufferLine implements IBufferLine {
179
201
  * to GC as it significantly reduced the amount of new objects/references needed.
180
202
  */
181
203
  public loadCell(index: number, cell: ICellData): ICellData {
182
- $startIndex = index * CELL_SIZE;
204
+ $startIndex = index * Constants.CELL_INDICIES;
183
205
  cell.content = this._data[$startIndex + Cell.CONTENT];
184
206
  cell.fg = this._data[$startIndex + Cell.FG];
185
207
  cell.bg = this._data[$startIndex + Cell.BG];
186
208
  if (cell.content & Content.IS_COMBINED_MASK) {
187
209
  cell.combinedData = this._combined[index];
210
+ } else {
211
+ cell.combinedData = '';
188
212
  }
189
213
  if (cell.bg & BgFlags.HAS_EXTENDED) {
190
214
  cell.extended = this._extendedAttrs[index]!;
215
+ } else {
216
+ // Do not mutate cell.extended in place: it may still reference this line's map entry from a
217
+ // prior loadCell into a reused CellData (e.g. $workCell during insert/delete).
218
+ cell.extended = DEFAULT_ATTR_DATA.extended.clone();
191
219
  }
192
220
  return cell;
193
221
  }
@@ -196,15 +224,16 @@ export class BufferLine implements IBufferLine {
196
224
  * Set data at `index` to `cell`.
197
225
  */
198
226
  public setCell(index: number, cell: ICellData): void {
227
+ this._invalidateStringCache();
199
228
  if (cell.content & Content.IS_COMBINED_MASK) {
200
229
  this._combined[index] = cell.combinedData;
201
230
  }
202
231
  if (cell.bg & BgFlags.HAS_EXTENDED) {
203
232
  this._extendedAttrs[index] = cell.extended;
204
233
  }
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;
234
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = cell.content;
235
+ this._data[index * Constants.CELL_INDICIES + Cell.FG] = cell.fg;
236
+ this._data[index * Constants.CELL_INDICIES + Cell.BG] = cell.bg;
208
237
  }
209
238
 
210
239
  /**
@@ -213,12 +242,13 @@ export class BufferLine implements IBufferLine {
213
242
  * it gets an optimized access method.
214
243
  */
215
244
  public setCellFromCodepoint(index: number, codePoint: number, width: number, attrs: IAttributeData): void {
245
+ this._invalidateStringCache();
216
246
  if (attrs.bg & BgFlags.HAS_EXTENDED) {
217
247
  this._extendedAttrs[index] = attrs.extended;
218
248
  }
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;
249
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
250
+ this._data[index * Constants.CELL_INDICIES + Cell.FG] = attrs.fg;
251
+ this._data[index * Constants.CELL_INDICIES + Cell.BG] = attrs.bg;
222
252
  }
223
253
 
224
254
  /**
@@ -228,7 +258,8 @@ export class BufferLine implements IBufferLine {
228
258
  * by the previous `setDataFromCodePoint` call, we can omit it here.
229
259
  */
230
260
  public addCodepointToCell(index: number, codePoint: number, width: number): void {
231
- let content = this._data[index * CELL_SIZE + Cell.CONTENT];
261
+ this._invalidateStringCache();
262
+ let content = this._data[index * Constants.CELL_INDICIES + Cell.CONTENT];
232
263
  if (content & Content.IS_COMBINED_MASK) {
233
264
  // we already have a combined string, simply add
234
265
  this._combined[index] += stringFromCodePoint(codePoint);
@@ -250,10 +281,11 @@ export class BufferLine implements IBufferLine {
250
281
  content &= ~Content.WIDTH_MASK;
251
282
  content |= width << Content.WIDTH_SHIFT;
252
283
  }
253
- this._data[index * CELL_SIZE + Cell.CONTENT] = content;
284
+ this._data[index * Constants.CELL_INDICIES + Cell.CONTENT] = content;
254
285
  }
255
286
 
256
287
  public insertCells(pos: number, n: number, fillCellData: ICellData): void {
288
+ this._invalidateStringCache();
257
289
  pos %= this.length;
258
290
 
259
291
  // handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char
@@ -262,9 +294,8 @@ export class BufferLine implements IBufferLine {
262
294
  }
263
295
 
264
296
  if (n < this.length - pos) {
265
- const cell = new CellData();
266
297
  for (let i = this.length - pos - n - 1; i >= 0; --i) {
267
- this.setCell(pos + n + i, this.loadCell(pos + i, cell));
298
+ this.setCell(pos + n + i, this.loadCell(pos + i, $workCell));
268
299
  }
269
300
  for (let i = 0; i < n; ++i) {
270
301
  this.setCell(pos + i, fillCellData);
@@ -282,11 +313,11 @@ export class BufferLine implements IBufferLine {
282
313
  }
283
314
 
284
315
  public deleteCells(pos: number, n: number, fillCellData: ICellData): void {
316
+ this._invalidateStringCache();
285
317
  pos %= this.length;
286
318
  if (n < this.length - pos) {
287
- const cell = new CellData();
288
319
  for (let i = 0; i < this.length - pos - n; ++i) {
289
- this.setCell(pos + i, this.loadCell(pos + n + i, cell));
320
+ this.setCell(pos + i, this.loadCell(pos + n + i, $workCell));
290
321
  }
291
322
  for (let i = this.length - n; i < this.length; ++i) {
292
323
  this.setCell(i, fillCellData);
@@ -309,6 +340,7 @@ export class BufferLine implements IBufferLine {
309
340
  }
310
341
 
311
342
  public replaceCells(start: number, end: number, fillCellData: ICellData, respectProtect: boolean = false): void {
343
+ this._invalidateStringCache();
312
344
  // full branching on respectProtect==true, hopefully getting fast JIT for standard case
313
345
  if (respectProtect) {
314
346
  if (start && this.getWidth(start - 1) === 2 && !this.isProtected(start - 1)) {
@@ -345,13 +377,14 @@ export class BufferLine implements IBufferLine {
345
377
  * The underlying array buffer will not change if there is still enough space
346
378
  * to hold the new buffer line data.
347
379
  * Returns a boolean indicating, whether a `cleanupMemory` call would free
348
- * excess memory (true after shrinking > CLEANUP_THRESHOLD).
380
+ * excess memory (true after shrinking > Constants.CLEANUP_THRESHOLD).
349
381
  */
350
382
  public resize(cols: number, fillCellData: ICellData): boolean {
383
+ this._invalidateStringCache();
351
384
  if (cols === this.length) {
352
- return this._data.length * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength;
385
+ return this._data.length * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength;
353
386
  }
354
- const uint32Cells = cols * CELL_SIZE;
387
+ const uint32Cells = cols * Constants.CELL_INDICIES;
355
388
  if (cols > this.length) {
356
389
  if (this._data.buffer.byteLength >= uint32Cells * 4) {
357
390
  // optimization: avoid alloc and data copy if buffer has enough room
@@ -386,17 +419,17 @@ export class BufferLine implements IBufferLine {
386
419
  }
387
420
  }
388
421
  this.length = cols;
389
- return uint32Cells * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength;
422
+ return uint32Cells * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength;
390
423
  }
391
424
 
392
425
  /**
393
426
  * Cleanup underlying array buffer.
394
427
  * A cleanup will be triggered if the array buffer exceeds the actual used
395
- * memory by a factor of CLEANUP_THRESHOLD.
428
+ * memory by a factor of Constants.CLEANUP_THRESHOLD.
396
429
  * Returns 0 or 1 indicating whether a cleanup happened.
397
430
  */
398
431
  public cleanupMemory(): number {
399
- if (this._data.length * 4 * CLEANUP_THRESHOLD < this._data.buffer.byteLength) {
432
+ if (this._data.length * 4 * Constants.CLEANUP_THRESHOLD < this._data.buffer.byteLength) {
400
433
  const data = new Uint32Array(this._data.length);
401
434
  data.set(this._data);
402
435
  this._data = data;
@@ -407,6 +440,7 @@ export class BufferLine implements IBufferLine {
407
440
 
408
441
  /** fill a line with fillCharData */
409
442
  public fill(fillCellData: ICellData, respectProtect: boolean = false): void {
443
+ this._invalidateStringCache();
410
444
  // full branching on respectProtect==true, hopefully getting fast JIT for standard case
411
445
  if (respectProtect) {
412
446
  for (let i = 0; i < this.length; ++i) {
@@ -425,6 +459,7 @@ export class BufferLine implements IBufferLine {
425
459
 
426
460
  /** alter to a full copy of line */
427
461
  public copyFrom(line: BufferLine): void {
462
+ this._invalidateStringCache();
428
463
  if (this.length !== line.length) {
429
464
  this._data = new Uint32Array(line._data);
430
465
  } else {
@@ -432,36 +467,24 @@ export class BufferLine implements IBufferLine {
432
467
  this._data.set(line._data);
433
468
  }
434
469
  this.length = line.length;
435
- this._combined = {};
436
- for (const el in line._combined) {
437
- this._combined[el] = line._combined[el];
438
- }
439
- this._extendedAttrs = {};
440
- for (const el in line._extendedAttrs) {
441
- this._extendedAttrs[el] = line._extendedAttrs[el];
442
- }
470
+ this._copySparseMapsFrom(line);
443
471
  this.isWrapped = line.isWrapped;
444
472
  }
445
473
 
446
474
  /** create a new clone */
447
475
  public clone(): IBufferLine {
448
- const newLine = new BufferLine(0);
476
+ const newLine = new BufferLine(this._stringCache, 0, undefined, false);
449
477
  newLine._data = new Uint32Array(this._data);
450
478
  newLine.length = this.length;
451
- for (const el in this._combined) {
452
- newLine._combined[el] = this._combined[el];
453
- }
454
- for (const el in this._extendedAttrs) {
455
- newLine._extendedAttrs[el] = this._extendedAttrs[el];
456
- }
479
+ newLine._copySparseMapsFrom(this);
457
480
  newLine.isWrapped = this.isWrapped;
458
481
  return newLine;
459
482
  }
460
483
 
461
484
  public getTrimmedLength(): number {
462
485
  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);
486
+ if ((this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] & Content.HAS_CONTENT_MASK)) {
487
+ return i + (this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] >> Content.WIDTH_SHIFT);
465
488
  }
466
489
  }
467
490
  return 0;
@@ -469,47 +492,36 @@ export class BufferLine implements IBufferLine {
469
492
 
470
493
  public getNoBgTrimmedLength(): number {
471
494
  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);
495
+ if ((this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] & Content.HAS_CONTENT_MASK) || (this._data[i * Constants.CELL_INDICIES + Cell.BG] & Attributes.CM_MASK)) {
496
+ return i + (this._data[i * Constants.CELL_INDICIES + Cell.CONTENT] >> Content.WIDTH_SHIFT);
474
497
  }
475
498
  }
476
499
  return 0;
477
500
  }
478
501
 
479
502
  public copyCellsFrom(src: BufferLine, srcCol: number, destCol: number, length: number, applyInReverse: boolean): void {
503
+ this._invalidateStringCache();
480
504
  const srcData = src._data;
481
505
  if (applyInReverse) {
482
506
  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];
485
- }
486
- if (srcData[(srcCol + cell) * CELL_SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
487
- this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell];
507
+ for (let i = 0; i < Constants.CELL_INDICIES; i++) {
508
+ this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i];
488
509
  }
510
+ this._copyCellMapsFrom(src, srcCol + cell, destCol + cell);
489
511
  }
490
512
  } else {
491
513
  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];
494
- }
495
- if (srcData[(srcCol + cell) * CELL_SIZE + Cell.BG] & BgFlags.HAS_EXTENDED) {
496
- this._extendedAttrs[destCol + cell] = src._extendedAttrs[srcCol + cell];
514
+ for (let i = 0; i < Constants.CELL_INDICIES; i++) {
515
+ this._data[(destCol + cell) * Constants.CELL_INDICIES + i] = srcData[(srcCol + cell) * Constants.CELL_INDICIES + i];
497
516
  }
498
- }
499
- }
500
-
501
- // Move any combined data over as needed, FIXME: repeat for extended attrs
502
- const srcCombinedKeys = Object.keys(src._combined);
503
- for (let i = 0; i < srcCombinedKeys.length; i++) {
504
- const key = parseInt(srcCombinedKeys[i], 10);
505
- if (key >= srcCol) {
506
- this._combined[key - srcCol + destCol] = src._combined[key];
517
+ this._copyCellMapsFrom(src, srcCol + cell, destCol + cell);
507
518
  }
508
519
  }
509
520
  }
510
521
 
511
522
  /**
512
- * Translates the buffer line to a string.
523
+ * Translates the buffer line to a string. Caching only applies to canonical full-line translation
524
+ * requests (regardless of `trimRight` value).
513
525
  *
514
526
  * @param trimRight Whether to trim any empty cells on the right.
515
527
  * @param startCol The column to start the string (0-based inclusive).
@@ -522,6 +534,19 @@ export class BufferLine implements IBufferLine {
522
534
  * returned string, the corresponding entries in `outColumns` will have the same column number.
523
535
  */
524
536
  public translateToString(trimRight?: boolean, startCol?: number, endCol?: number, outColumns?: number[]): string {
537
+ const isCanonicalRequest = (startCol === undefined || startCol === 0) && endCol === undefined && outColumns === undefined;
538
+ if (isCanonicalRequest) {
539
+ this._stringCache.touch?.();
540
+ }
541
+ const stringCacheEntry = isCanonicalRequest ? this._getStringCacheEntry(false) : undefined;
542
+ if (isCanonicalRequest && stringCacheEntry?.value !== undefined) {
543
+ if (trimRight) {
544
+ return stringCacheEntry.isTrimmed ? stringCacheEntry.value : stringCacheEntry.value.trimEnd();
545
+ }
546
+ if (!stringCacheEntry.isTrimmed) {
547
+ return stringCacheEntry.value;
548
+ }
549
+ }
525
550
  startCol = startCol ?? 0;
526
551
  endCol = endCol ?? this.length;
527
552
  if (trimRight) {
@@ -530,12 +555,12 @@ export class BufferLine implements IBufferLine {
530
555
  if (outColumns) {
531
556
  outColumns.length = 0;
532
557
  }
533
- let result = '';
558
+ $translateToStringBuilder.reset();
534
559
  while (startCol < endCol) {
535
- const content = this._data[startCol * CELL_SIZE + Cell.CONTENT];
560
+ const content = this._data[startCol * Constants.CELL_INDICIES + Cell.CONTENT];
536
561
  const cp = content & Content.CODEPOINT_MASK;
537
562
  const chars = (content & Content.IS_COMBINED_MASK) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR;
538
- result += chars;
563
+ $translateToStringBuilder.append(chars);
539
564
  if (outColumns) {
540
565
  for (let i = 0; i < chars.length; ++i) {
541
566
  outColumns.push(startCol);
@@ -546,6 +571,56 @@ export class BufferLine implements IBufferLine {
546
571
  if (outColumns) {
547
572
  outColumns.push(startCol);
548
573
  }
574
+ const result = $translateToStringBuilder.toString();
575
+ $translateToStringBuilder.reset();
576
+ if (isCanonicalRequest) {
577
+ const cacheEntry = this._getStringCacheEntry(true)!;
578
+ cacheEntry.value = result;
579
+ cacheEntry.isTrimmed = !!trimRight;
580
+ }
549
581
  return result;
550
582
  }
583
+
584
+ protected _getStringCacheEntry(createIfNeeded: boolean): IBufferLineStringCacheEntry | undefined {
585
+ const cachedEntry = this._stringCacheEntryRef?.deref();
586
+ if (cachedEntry) {
587
+ if (cachedEntry.generation === this._stringCache.generation) {
588
+ return cachedEntry;
589
+ }
590
+ }
591
+ if (!createIfNeeded) {
592
+ return undefined;
593
+ }
594
+ const cacheEntry = this._stringCache.allocateEntry();
595
+ this._stringCacheEntryRef = new WeakRef(cacheEntry);
596
+ return cacheEntry;
597
+ }
598
+
599
+ private _invalidateStringCache(): void {
600
+ const cacheEntry = this._getStringCacheEntry(false);
601
+ if (cacheEntry) {
602
+ cacheEntry.value = undefined;
603
+ cacheEntry.isTrimmed = false;
604
+ }
605
+ }
606
+
607
+ /** Copy sparse map entries for a single cell when `_data` flags require them. */
608
+ private _copyCellMapsFrom(src: BufferLine, srcCol: number, destCol: number): void {
609
+ const srcStart = srcCol * Constants.CELL_INDICIES;
610
+ if (src._data[srcStart + Cell.CONTENT] & Content.IS_COMBINED_MASK) {
611
+ this._combined[destCol] = src._combined[srcCol];
612
+ }
613
+ if (src._data[srcStart + Cell.BG] & BgFlags.HAS_EXTENDED) {
614
+ this._extendedAttrs[destCol] = src._extendedAttrs[srcCol];
615
+ }
616
+ }
617
+
618
+ /** Rebuild sparse maps from another line, keyed only by `_data` flags. */
619
+ private _copySparseMapsFrom(line: BufferLine): void {
620
+ this._combined = {};
621
+ this._extendedAttrs = {};
622
+ for (let i = 0; i < line.length; i++) {
623
+ this._copyCellMapsFrom(line, i, i);
624
+ }
625
+ }
551
626
  }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Copyright (c) 2026 The xterm.js authors. All rights reserved.
3
+ * @license MIT
4
+ */
5
+
6
+ import type { IBufferLineStringCache, IBufferLineStringCacheEntry } from './BufferLine';
7
+ import { disposableTimeout } from '../Async';
8
+ import { Disposable, MutableDisposable, toDisposable, type IDisposable } from '../Lifecycle';
9
+
10
+ const enum Constants {
11
+ CACHE_TTL_MS = 15000
12
+ }
13
+
14
+ export class BufferLineStringCache extends Disposable implements IBufferLineStringCache {
15
+ public generation: number = 0;
16
+ public readonly entries: Set<IBufferLineStringCacheEntry> = new Set();
17
+ private readonly _clearTimeout = this._register(new MutableDisposable<IDisposable>());
18
+ private _lastAccessTimestamp: number = 0;
19
+
20
+ constructor() {
21
+ super();
22
+ this._register(toDisposable(() => this.entries.clear()));
23
+ }
24
+
25
+ public touch(): void {
26
+ this._scheduleClear();
27
+ }
28
+
29
+ public allocateEntry(): IBufferLineStringCacheEntry {
30
+ const entry: IBufferLineStringCacheEntry = {
31
+ value: undefined,
32
+ isTrimmed: false,
33
+ generation: this.generation
34
+ };
35
+ this.entries.add(entry);
36
+ this._scheduleClear();
37
+ return entry;
38
+ }
39
+
40
+ public clear(): void {
41
+ this._clearTimeout.clear();
42
+ this._lastAccessTimestamp = 0;
43
+ this.generation++;
44
+ for (const entry of this.entries) {
45
+ entry.value = undefined;
46
+ entry.isTrimmed = false;
47
+ }
48
+ this.entries.clear();
49
+ }
50
+
51
+ private _scheduleClear(): void {
52
+ this._lastAccessTimestamp = Date.now();
53
+ if (this._clearTimeout.value) {
54
+ return;
55
+ }
56
+ this._scheduleClearTimeout(Constants.CACHE_TTL_MS);
57
+ }
58
+
59
+ private _scheduleClearTimeout(timeoutMs: number): void {
60
+ this._clearTimeout.value = disposableTimeout(() => {
61
+ const elapsed = Date.now() - this._lastAccessTimestamp;
62
+ if (elapsed >= Constants.CACHE_TTL_MS) {
63
+ this.clear();
64
+ return;
65
+ }
66
+ this._scheduleClearTimeout(Constants.CACHE_TTL_MS - elapsed);
67
+ }, timeoutMs);
68
+ }
69
+ }
@@ -3,9 +3,9 @@
3
3
  * @license MIT
4
4
  */
5
5
 
6
- import { BufferLine } from 'common/buffer/BufferLine';
7
- import { CircularList } from 'common/CircularList';
8
- import { IBufferLine, ICellData } from 'common/Types';
6
+ import { BufferLine } from './BufferLine';
7
+ import { CircularList } from '../CircularList';
8
+ import { IBufferLine, ICellData } from './Types';
9
9
 
10
10
  export interface INewLayoutResult {
11
11
  layout: number[];
@@ -178,7 +178,10 @@ export function reflowLargerApplyNewLayout(lines: CircularList<IBufferLine>, new
178
178
  */
179
179
  export function reflowSmallerGetNewLineLengths(wrappedLines: BufferLine[], oldCols: number, newCols: number): number[] {
180
180
  const newLineLengths: number[] = [];
181
- const cellsNeeded = wrappedLines.map((l, i) => getWrappedLineTrimmedLength(wrappedLines, i, oldCols)).reduce((p, c) => p + c);
181
+ let cellsNeeded = 0;
182
+ for (let i = 0; i < wrappedLines.length; i++) {
183
+ cellsNeeded += getWrappedLineTrimmedLength(wrappedLines, i, oldCols);
184
+ }
182
185
 
183
186
  // Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and
184
187
  // linesNeeded