@pierre/diffs 1.3.0-beta.9 → 1.3.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/dist/components/CodeView.d.ts +12 -12
  2. package/dist/components/CodeView.d.ts.map +1 -1
  3. package/dist/components/CodeView.js +49 -42
  4. package/dist/components/CodeView.js.map +1 -1
  5. package/dist/components/File.d.ts +7 -2
  6. package/dist/components/File.d.ts.map +1 -1
  7. package/dist/components/File.js +36 -8
  8. package/dist/components/File.js.map +1 -1
  9. package/dist/components/FileDiff.d.ts +34 -6
  10. package/dist/components/FileDiff.d.ts.map +1 -1
  11. package/dist/components/FileDiff.js +198 -17
  12. package/dist/components/FileDiff.js.map +1 -1
  13. package/dist/components/VirtualizedFile.d.ts +2 -1
  14. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  15. package/dist/components/VirtualizedFile.js +9 -5
  16. package/dist/components/VirtualizedFile.js.map +1 -1
  17. package/dist/components/VirtualizedFileDiff.d.ts +14 -0
  18. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  19. package/dist/components/VirtualizedFileDiff.js +61 -3
  20. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  21. package/dist/components/Virtualizer.d.ts +14 -2
  22. package/dist/components/Virtualizer.d.ts.map +1 -1
  23. package/dist/components/Virtualizer.js +27 -2
  24. package/dist/components/Virtualizer.js.map +1 -1
  25. package/dist/edit/index.d.ts +5 -0
  26. package/dist/edit/index.js +3 -0
  27. package/dist/editor/command.d.ts +1 -1
  28. package/dist/editor/command.d.ts.map +1 -1
  29. package/dist/editor/command.js +11 -0
  30. package/dist/editor/command.js.map +1 -1
  31. package/dist/editor/editStack.d.ts +6 -2
  32. package/dist/editor/editStack.d.ts.map +1 -1
  33. package/dist/editor/editStack.js +53 -12
  34. package/dist/editor/editStack.js.map +1 -1
  35. package/dist/editor/editor.d.ts +51 -23
  36. package/dist/editor/editor.d.ts.map +1 -1
  37. package/dist/editor/editor.js +978 -238
  38. package/dist/editor/editor.js.map +1 -1
  39. package/dist/editor/editor2.js +1 -1
  40. package/dist/editor/languages.d.ts +26 -0
  41. package/dist/editor/languages.d.ts.map +1 -0
  42. package/dist/editor/languages.js +363 -0
  43. package/dist/editor/languages.js.map +1 -0
  44. package/dist/editor/lineAnnotations.d.ts.map +1 -1
  45. package/dist/editor/lineAnnotations.js +25 -26
  46. package/dist/editor/lineAnnotations.js.map +1 -1
  47. package/dist/editor/marker.d.ts +5 -3
  48. package/dist/editor/marker.d.ts.map +1 -1
  49. package/dist/editor/marker.js +111 -91
  50. package/dist/editor/marker.js.map +1 -1
  51. package/dist/editor/matchBrackets.d.ts +3 -2
  52. package/dist/editor/matchBrackets.d.ts.map +1 -1
  53. package/dist/editor/matchBrackets.js +2 -10
  54. package/dist/editor/matchBrackets.js.map +1 -1
  55. package/dist/editor/pieceTable.d.ts +2 -1
  56. package/dist/editor/pieceTable.d.ts.map +1 -1
  57. package/dist/editor/pieceTable.js +103 -39
  58. package/dist/editor/pieceTable.js.map +1 -1
  59. package/dist/editor/platform.js +2 -0
  60. package/dist/editor/platform.js.map +1 -1
  61. package/dist/editor/popover.d.ts +25 -5
  62. package/dist/editor/popover.d.ts.map +1 -1
  63. package/dist/editor/popover.js +60 -14
  64. package/dist/editor/popover.js.map +1 -1
  65. package/dist/editor/searchPanel.d.ts.map +1 -1
  66. package/dist/editor/searchPanel.js +9 -2
  67. package/dist/editor/searchPanel.js.map +1 -1
  68. package/dist/editor/selection.d.ts +31 -11
  69. package/dist/editor/selection.d.ts.map +1 -1
  70. package/dist/editor/selection.js +198 -70
  71. package/dist/editor/selection.js.map +1 -1
  72. package/dist/editor/selectionAction.d.ts +4 -3
  73. package/dist/editor/selectionAction.d.ts.map +1 -1
  74. package/dist/editor/selectionAction.js +9 -5
  75. package/dist/editor/selectionAction.js.map +1 -1
  76. package/dist/editor/stateStorage.d.ts +15 -0
  77. package/dist/editor/stateStorage.d.ts.map +1 -0
  78. package/dist/editor/stateStorage.js +65 -0
  79. package/dist/editor/stateStorage.js.map +1 -0
  80. package/dist/editor/textDocument.d.ts +12 -77
  81. package/dist/editor/textDocument.d.ts.map +1 -1
  82. package/dist/editor/textDocument.js +76 -34
  83. package/dist/editor/textDocument.js.map +1 -1
  84. package/dist/editor/textMeasure.d.ts +7 -12
  85. package/dist/editor/textMeasure.d.ts.map +1 -1
  86. package/dist/editor/textMeasure.js +40 -32
  87. package/dist/editor/textMeasure.js.map +1 -1
  88. package/dist/editor/{tokenzier.d.ts → tokenizer.d.ts} +3 -3
  89. package/dist/editor/tokenizer.d.ts.map +1 -0
  90. package/dist/editor/{tokenzier.js → tokenizer.js} +91 -26
  91. package/dist/editor/tokenizer.js.map +1 -0
  92. package/dist/editor/utils.js +1 -1
  93. package/dist/editor/utils.js.map +1 -1
  94. package/dist/index.d.ts +4 -3
  95. package/dist/index.js +6 -5
  96. package/dist/managers/InteractionManager.d.ts +18 -7
  97. package/dist/managers/InteractionManager.d.ts.map +1 -1
  98. package/dist/managers/InteractionManager.js +154 -99
  99. package/dist/managers/InteractionManager.js.map +1 -1
  100. package/dist/managers/UniversalRenderingManager.d.ts.map +1 -1
  101. package/dist/managers/UniversalRenderingManager.js +1 -2
  102. package/dist/managers/UniversalRenderingManager.js.map +1 -1
  103. package/dist/react/CodeView.d.ts +13 -13
  104. package/dist/react/CodeView.d.ts.map +1 -1
  105. package/dist/react/CodeView.js +38 -29
  106. package/dist/react/CodeView.js.map +1 -1
  107. package/dist/react/EditContext.d.ts +20 -0
  108. package/dist/react/EditContext.d.ts.map +1 -0
  109. package/dist/react/EditContext.js +20 -0
  110. package/dist/react/EditContext.js.map +1 -0
  111. package/dist/react/File.d.ts +2 -1
  112. package/dist/react/File.d.ts.map +1 -1
  113. package/dist/react/File.js +3 -2
  114. package/dist/react/File.js.map +1 -1
  115. package/dist/react/FileDiff.d.ts +2 -2
  116. package/dist/react/FileDiff.d.ts.map +1 -1
  117. package/dist/react/FileDiff.js +3 -2
  118. package/dist/react/FileDiff.js.map +1 -1
  119. package/dist/react/MultiFileDiff.d.ts +2 -2
  120. package/dist/react/MultiFileDiff.d.ts.map +1 -1
  121. package/dist/react/MultiFileDiff.js +3 -2
  122. package/dist/react/MultiFileDiff.js.map +1 -1
  123. package/dist/react/PatchDiff.d.ts +2 -2
  124. package/dist/react/PatchDiff.d.ts.map +1 -1
  125. package/dist/react/PatchDiff.js +3 -2
  126. package/dist/react/PatchDiff.js.map +1 -1
  127. package/dist/react/UnresolvedFile.d.ts +1 -1
  128. package/dist/react/UnresolvedFile.js.map +1 -1
  129. package/dist/react/index.d.ts +4 -4
  130. package/dist/react/index.js +3 -3
  131. package/dist/react/types.d.ts +9 -1
  132. package/dist/react/types.d.ts.map +1 -1
  133. package/dist/react/utils/useFileDiffInstance.d.ts +5 -3
  134. package/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
  135. package/dist/react/utils/useFileDiffInstance.js +28 -27
  136. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  137. package/dist/react/utils/useFileInstance.d.ts +5 -3
  138. package/dist/react/utils/useFileInstance.d.ts.map +1 -1
  139. package/dist/react/utils/useFileInstance.js +29 -25
  140. package/dist/react/utils/useFileInstance.js.map +1 -1
  141. package/dist/react/utils/useUnresolvedFileInstance.js +3 -3
  142. package/dist/react/utils/useUnresolvedFileInstance.js.map +1 -1
  143. package/dist/renderers/DiffHunksRenderer.d.ts +33 -1
  144. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  145. package/dist/renderers/DiffHunksRenderer.js +134 -21
  146. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  147. package/dist/renderers/FileRenderer.d.ts +2 -0
  148. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  149. package/dist/renderers/FileRenderer.js +33 -11
  150. package/dist/renderers/FileRenderer.js.map +1 -1
  151. package/dist/ssr/FileDiffReact.js +1 -1
  152. package/dist/ssr/index.d.ts +2 -2
  153. package/dist/ssr/preloadDiffs.js +1 -1
  154. package/dist/style.js +1 -1
  155. package/dist/types.d.ts +185 -42
  156. package/dist/types.d.ts.map +1 -1
  157. package/dist/utils/annotationHelpers.d.ts +20 -0
  158. package/dist/utils/annotationHelpers.d.ts.map +1 -0
  159. package/dist/utils/annotationHelpers.js +29 -0
  160. package/dist/utils/annotationHelpers.js.map +1 -0
  161. package/dist/utils/computeFileOffsets.d.ts.map +1 -1
  162. package/dist/utils/computeFileOffsets.js +34 -5
  163. package/dist/utils/computeFileOffsets.js.map +1 -1
  164. package/dist/utils/editSessionHunks.d.ts +66 -0
  165. package/dist/utils/editSessionHunks.d.ts.map +1 -0
  166. package/dist/utils/editSessionHunks.js +437 -0
  167. package/dist/utils/editSessionHunks.js.map +1 -0
  168. package/dist/utils/getHunkSideBoundaries.d.ts +7 -0
  169. package/dist/utils/getHunkSideBoundaries.d.ts.map +1 -0
  170. package/dist/utils/getHunkSideBoundaries.js +12 -0
  171. package/dist/utils/getHunkSideBoundaries.js.map +1 -0
  172. package/dist/utils/getTotalLineCountFromHunks.d.ts.map +1 -1
  173. package/dist/utils/getTotalLineCountFromHunks.js +2 -1
  174. package/dist/utils/getTotalLineCountFromHunks.js.map +1 -1
  175. package/dist/utils/hydratePartialDiff.d.ts.map +1 -1
  176. package/dist/utils/hydratePartialDiff.js +4 -3
  177. package/dist/utils/hydratePartialDiff.js.map +1 -1
  178. package/dist/utils/isGutterUtilityPath.d.ts +5 -0
  179. package/dist/utils/isGutterUtilityPath.d.ts.map +1 -0
  180. package/dist/utils/isGutterUtilityPath.js +12 -0
  181. package/dist/utils/isGutterUtilityPath.js.map +1 -0
  182. package/dist/utils/iterateOverDiff.d.ts.map +1 -1
  183. package/dist/utils/iterateOverDiff.js +17 -12
  184. package/dist/utils/iterateOverDiff.js.map +1 -1
  185. package/dist/utils/parseMergeConflictDiffFromFile.d.ts.map +1 -1
  186. package/dist/utils/parseMergeConflictDiffFromFile.js +9 -6
  187. package/dist/utils/parseMergeConflictDiffFromFile.js.map +1 -1
  188. package/dist/utils/parsePatchFiles.d.ts.map +1 -1
  189. package/dist/utils/parsePatchFiles.js +6 -3
  190. package/dist/utils/parsePatchFiles.js.map +1 -1
  191. package/dist/utils/realignChangeContent.d.ts +33 -0
  192. package/dist/utils/realignChangeContent.d.ts.map +1 -0
  193. package/dist/utils/realignChangeContent.js +148 -0
  194. package/dist/utils/realignChangeContent.js.map +1 -0
  195. package/dist/utils/renderDiffWithHighlighter.js +1 -1
  196. package/dist/utils/resolveRegion.d.ts.map +1 -1
  197. package/dist/utils/resolveRegion.js +18 -2
  198. package/dist/utils/resolveRegion.js.map +1 -1
  199. package/dist/utils/updateDiffHunks.d.ts +8 -2
  200. package/dist/utils/updateDiffHunks.d.ts.map +1 -1
  201. package/dist/utils/updateDiffHunks.js +9 -7
  202. package/dist/utils/updateDiffHunks.js.map +1 -1
  203. package/dist/utils/virtualDiffLayout.d.ts +42 -2
  204. package/dist/utils/virtualDiffLayout.d.ts.map +1 -1
  205. package/dist/utils/virtualDiffLayout.js +101 -7
  206. package/dist/utils/virtualDiffLayout.js.map +1 -1
  207. package/dist/worker/worker-portable.js +60 -20
  208. package/dist/worker/worker-portable.js.map +1 -1
  209. package/dist/worker/worker.js +61 -19
  210. package/dist/worker/worker.js.map +1 -1
  211. package/package.json +10 -7
  212. package/dist/editor/index.d.ts +0 -3
  213. package/dist/editor/index.js +0 -3
  214. package/dist/editor/tokenzier.d.ts.map +0 -1
  215. package/dist/editor/tokenzier.js.map +0 -1
  216. package/dist/react/EditorContext.d.ts +0 -15
  217. package/dist/react/EditorContext.d.ts.map +0 -1
  218. package/dist/react/EditorContext.js +0 -23
  219. package/dist/react/EditorContext.js.map +0 -1
@@ -1,6 +1,8 @@
1
1
  import { computeLineOffsets } from "../utils/computeFileOffsets.js";
2
2
  //#region src/editor/pieceTable.ts
3
3
  const MAX_FIND_MATCHES = 1e5;
4
+ const LINE_FEED = 10;
5
+ const CARRIAGE_RETURN = 13;
4
6
  const WORD_SEPARATORS = "`~!@#$%^&*()-=+[{]}\\|;:'\",.<>/?";
5
7
  var Piece = class {
6
8
  source;
@@ -8,17 +10,23 @@ var Piece = class {
8
10
  length;
9
11
  lineOffsetStart;
10
12
  lineOffsetEnd;
13
+ hasVirtualTrailingCRBreak;
14
+ firstCharCode;
15
+ lastCharCode;
11
16
  static Original = 0;
12
17
  static Added = 1;
13
- constructor(source, offset, length, lineOffsetStart, lineOffsetEnd) {
18
+ constructor(source, offset, length, lineOffsetStart, lineOffsetEnd, hasVirtualTrailingCRBreak, firstCharCode, lastCharCode) {
14
19
  this.source = source;
15
20
  this.offset = offset;
16
21
  this.length = length;
17
22
  this.lineOffsetStart = lineOffsetStart;
18
23
  this.lineOffsetEnd = lineOffsetEnd;
24
+ this.hasVirtualTrailingCRBreak = hasVirtualTrailingCRBreak;
25
+ this.firstCharCode = firstCharCode;
26
+ this.lastCharCode = lastCharCode;
19
27
  }
20
28
  get lineBreakCount() {
21
- return this.lineOffsetEnd - this.lineOffsetStart;
29
+ return this.lineOffsetEnd - this.lineOffsetStart + (this.hasVirtualTrailingCRBreak ? 1 : 0);
22
30
  }
23
31
  };
24
32
  var TextBuffer = class {
@@ -30,8 +38,12 @@ var TextBuffer = class {
30
38
  }
31
39
  append(text) {
32
40
  const offset = this.text.length;
33
- const appendedLineOffsets = computeLineOffsets(text);
34
- for (let i = 1; i < appendedLineOffsets.length; i++) this.lineOffsets.push(offset + appendedLineOffsets[i]);
41
+ for (let i = 0; i < text.length; i++) {
42
+ const charCode = text.charCodeAt(i);
43
+ if (charCode !== LINE_FEED && charCode !== CARRIAGE_RETURN) continue;
44
+ if (charCode === CARRIAGE_RETURN && text.charCodeAt(i + 1) === LINE_FEED) i++;
45
+ this.lineOffsets.push(offset + i + 1);
46
+ }
35
47
  this.text += text;
36
48
  return offset;
37
49
  }
@@ -40,18 +52,24 @@ var PieceNode = class {
40
52
  piece;
41
53
  subtreeLength;
42
54
  subtreeLineBreakCount;
55
+ subtreeFirstCharCode;
56
+ subtreeLastCharCode;
43
57
  left = null;
44
58
  right = null;
45
59
  parent = null;
46
60
  priority = 0;
47
- constructor(piece, subtreeLength = piece.length, subtreeLineBreakCount = piece.lineBreakCount) {
61
+ constructor(piece, subtreeLength = piece.length, subtreeLineBreakCount = piece.lineBreakCount, subtreeFirstCharCode = piece.firstCharCode, subtreeLastCharCode = piece.lastCharCode) {
48
62
  this.piece = piece;
49
63
  this.subtreeLength = subtreeLength;
50
64
  this.subtreeLineBreakCount = subtreeLineBreakCount;
65
+ this.subtreeFirstCharCode = subtreeFirstCharCode;
66
+ this.subtreeLastCharCode = subtreeLastCharCode;
51
67
  }
52
68
  updateSubtreeLength() {
53
69
  this.subtreeLength = (this.left?.subtreeLength ?? 0) + this.piece.length + (this.right?.subtreeLength ?? 0);
54
- this.subtreeLineBreakCount = (this.left?.subtreeLineBreakCount ?? 0) + this.piece.lineBreakCount + (this.right?.subtreeLineBreakCount ?? 0);
70
+ this.subtreeLineBreakCount = (this.left?.subtreeLineBreakCount ?? 0) + this.piece.lineBreakCount + (this.right?.subtreeLineBreakCount ?? 0) - (formsCRLF(this.left?.subtreeLastCharCode, this.piece.firstCharCode) ? 1 : 0) - (formsCRLF(this.piece.lastCharCode, this.right?.subtreeFirstCharCode) ? 1 : 0);
71
+ this.subtreeFirstCharCode = this.left?.subtreeFirstCharCode ?? this.piece.firstCharCode;
72
+ this.subtreeLastCharCode = this.right?.subtreeLastCharCode ?? this.piece.lastCharCode;
55
73
  }
56
74
  };
57
75
  /**
@@ -71,6 +89,7 @@ var PieceTable = class {
71
89
  #lineCount = 0;
72
90
  #lastVisitedLine = null;
73
91
  #lastVisitedLineLength = null;
92
+ #lastPosition = null;
74
93
  #priorityState = 2654435769;
75
94
  constructor(originalText) {
76
95
  this.#original = new TextBuffer(originalText);
@@ -222,14 +241,14 @@ var PieceTable = class {
222
241
  }
223
242
  #collectSearchMatchesLineByLine(pattern, wholeWord, limit) {
224
243
  const out = [];
225
- const docLength = this.#length;
226
- const charAt = (offset) => this.charAt(offset);
227
- for (let line = 0; line < this.#lineCount; line++) {
228
- const lineText = this.getLineText(line);
229
- const lineStart = this.offsetAt({
230
- line,
231
- character: 0
232
- });
244
+ const documentText = this.#textFromPieces();
245
+ const docLength = documentText.length;
246
+ const lineOffsets = computeLineOffsets(documentText);
247
+ for (let line = 0; line < lineOffsets.length; line++) {
248
+ const lineStart = lineOffsets[line];
249
+ let lineEnd = lineOffsets[line + 1] ?? docLength;
250
+ while (lineEnd > lineStart && isEOL(documentText.charCodeAt(lineEnd - 1))) lineEnd--;
251
+ const lineText = documentText.slice(lineStart, lineEnd);
233
252
  pattern.lastIndex = 0;
234
253
  let match;
235
254
  while ((match = pattern.exec(lineText)) !== null) {
@@ -240,7 +259,7 @@ var PieceTable = class {
240
259
  continue;
241
260
  }
242
261
  const docStart = lineStart + rel;
243
- if (!wholeWord || isWholeWordAtDocOffsets(docStart, fragment.length, docLength, charAt)) {
262
+ if (!wholeWord || isWholeWordAtDocOffsets(documentText, docStart, fragment.length)) {
244
263
  out.push([docStart, docStart + fragment.length]);
245
264
  if (out.length >= limit) return out;
246
265
  }
@@ -253,6 +272,7 @@ var PieceTable = class {
253
272
  if (text.length === 0) return;
254
273
  const start = clamp(offset, 0, this.#length);
255
274
  this.#replaceRangeIncremental(start, start, text);
275
+ this.#invalidateCaches();
256
276
  }
257
277
  delete(offset, length) {
258
278
  if (length <= 0 || this.#length === 0) return;
@@ -260,6 +280,7 @@ var PieceTable = class {
260
280
  const end = clamp(start + length, start, this.#length);
261
281
  if (start === end) return;
262
282
  this.#replaceRangeIncremental(start, end, "");
283
+ this.#invalidateCaches();
263
284
  }
264
285
  applyEdits(edits) {
265
286
  if (edits.length === 0) return;
@@ -269,6 +290,7 @@ var PieceTable = class {
269
290
  const end = clamp(edit.end, start, this.#length);
270
291
  this.#replaceRangeIncremental(start, end, edit.text);
271
292
  }
293
+ this.#invalidateCaches();
272
294
  }
273
295
  positionAt(offset) {
274
296
  const clampedOffset = clamp(offset, 0, this.#length);
@@ -277,9 +299,15 @@ var PieceTable = class {
277
299
  character: 0
278
300
  };
279
301
  const line = this.#lineAtOffset(clampedOffset);
302
+ const character = clampedOffset - (line === 0 ? 0 : this.#lineBreakOffset(line - 1));
303
+ this.#lastPosition = [
304
+ line,
305
+ character,
306
+ clampedOffset
307
+ ];
280
308
  return {
281
309
  line,
282
- character: clampedOffset - (line === 0 ? 0 : this.#lineBreakOffset(line - 1))
310
+ character
283
311
  };
284
312
  }
285
313
  positionsAt(offsets) {
@@ -295,6 +323,8 @@ var PieceTable = class {
295
323
  offsetAt(position) {
296
324
  if (position.line < 0 || this.#length === 0) return 0;
297
325
  if (position.line >= this.#lineCount) throw new Error(`Line index out of range: ${position.line}`);
326
+ const lastPosition = this.#lastPosition;
327
+ if (lastPosition !== null && lastPosition[0] === position.line && lastPosition[1] === position.character) return lastPosition[2];
298
328
  const offset = this.#getLineOffset(position.line);
299
329
  if (offset === void 0) throw new Error(`Line index out of range: ${position.line}`);
300
330
  const character = clamp(position.character, 0, offset[1] - offset[0]);
@@ -345,14 +375,17 @@ var PieceTable = class {
345
375
  node = node.left;
346
376
  continue;
347
377
  }
348
- line += node.left?.subtreeLineBreakCount ?? 0;
378
+ line += (node.left?.subtreeLineBreakCount ?? 0) - (formsCRLF(node.left?.subtreeLastCharCode, node.piece.firstCharCode) ? 1 : 0);
349
379
  remaining -= leftLength;
350
380
  if (remaining <= node.piece.length) {
351
381
  const buffer = this.#bufferFor(node.piece.source);
352
- line += upperBound(buffer.lineOffsets, node.piece.offset + remaining) - node.piece.lineOffsetStart;
382
+ const lineOffsetEnd = Math.min(upperBound(buffer.lineOffsets, node.piece.offset + remaining), node.piece.lineOffsetEnd);
383
+ line += lineOffsetEnd - node.piece.lineOffsetStart;
384
+ if (remaining === node.piece.length && node.piece.hasVirtualTrailingCRBreak) line++;
385
+ if (remaining === node.piece.length && formsCRLF(node.piece.lastCharCode, this.#nextNode(node)?.piece.firstCharCode)) line--;
353
386
  return line;
354
387
  }
355
- line += node.piece.lineBreakCount;
388
+ line += node.piece.lineBreakCount - (formsCRLF(node.piece.lastCharCode, node.right?.subtreeFirstCharCode) ? 1 : 0);
356
389
  remaining -= node.piece.length;
357
390
  node = node.right;
358
391
  }
@@ -362,25 +395,30 @@ var PieceTable = class {
362
395
  let node = this.#root;
363
396
  let remaining = lineBreakIndex;
364
397
  let documentOffset = 0;
398
+ let followingCharCode;
365
399
  while (node !== null) {
366
- const leftLineBreakCount = node.left?.subtreeLineBreakCount ?? 0;
400
+ const leftLineBreakCount = (node.left?.subtreeLineBreakCount ?? 0) - (formsCRLF(node.left?.subtreeLastCharCode, node.piece.firstCharCode) ? 1 : 0);
367
401
  if (remaining < leftLineBreakCount) {
402
+ followingCharCode = node.piece.firstCharCode;
368
403
  node = node.left;
369
404
  continue;
370
405
  }
371
406
  const leftLength = node.left?.subtreeLength ?? 0;
372
407
  documentOffset += leftLength;
373
408
  remaining -= leftLineBreakCount;
374
- if (remaining < node.piece.lineBreakCount) {
375
- const bufferLineOffset = this.#bufferFor(node.piece.source).lineOffsets[node.piece.lineOffsetStart + remaining];
376
- return documentOffset + (bufferLineOffset - node.piece.offset);
377
- }
409
+ const nextCharCode = node.right?.subtreeFirstCharCode ?? followingCharCode;
410
+ const pieceLineBreakCount = node.piece.lineBreakCount - (formsCRLF(node.piece.lastCharCode, nextCharCode) ? 1 : 0);
411
+ if (remaining < pieceLineBreakCount) return documentOffset + this.#pieceLineBreakOffset(node.piece, remaining);
378
412
  documentOffset += node.piece.length;
379
- remaining -= node.piece.lineBreakCount;
413
+ remaining -= pieceLineBreakCount;
380
414
  node = node.right;
381
415
  }
382
416
  return this.#length;
383
417
  }
418
+ #pieceLineBreakOffset(piece, lineBreakIndex) {
419
+ if (lineBreakIndex < piece.lineOffsetEnd - piece.lineOffsetStart) return this.#bufferFor(piece.source).lineOffsets[piece.lineOffsetStart + lineBreakIndex] - piece.offset;
420
+ return piece.length;
421
+ }
384
422
  #textFromPieces() {
385
423
  const chunks = [];
386
424
  this.#forEachPieceSegment((segment) => {
@@ -406,12 +444,16 @@ var PieceTable = class {
406
444
  }
407
445
  #createPiece(source, offset, length) {
408
446
  const buffer = this.#bufferFor(source);
409
- return new Piece(source, offset, length, upperBound(buffer.lineOffsets, offset), upperBound(buffer.lineOffsets, offset + length));
447
+ const end = offset + length;
448
+ const lineOffsetStart = upperBound(buffer.lineOffsets, offset);
449
+ const lineOffsetEnd = upperBound(buffer.lineOffsets, end);
450
+ const lastCharCode = buffer.text.charCodeAt(end - 1);
451
+ return new Piece(source, offset, length, lineOffsetStart, lineOffsetEnd, lastCharCode === CARRIAGE_RETURN && buffer.lineOffsets[lineOffsetEnd - 1] !== end, buffer.text.charCodeAt(offset), lastCharCode);
410
452
  }
411
453
  #replaceRangeIncremental(start, end, text) {
412
454
  if (start === end && text.length === 0) return;
413
455
  const [left, rest] = this.#split(this.#root, start);
414
- const [, right] = this.#split(rest, end - start);
456
+ const right = this.#dropPrefix(rest, end - start);
415
457
  let root;
416
458
  if (text.length > 0) {
417
459
  const insertedPiece = this.#createPiece(Piece.Added, this.#add.append(text), text.length);
@@ -424,8 +466,11 @@ var PieceTable = class {
424
466
  this.#root = root;
425
467
  this.#length = root?.subtreeLength ?? 0;
426
468
  this.#lineCount = (root?.subtreeLineBreakCount ?? 0) + 1;
469
+ }
470
+ #invalidateCaches() {
427
471
  this.#lastVisitedLine = null;
428
472
  this.#lastVisitedLineLength = null;
473
+ this.#lastPosition = null;
429
474
  }
430
475
  #nextPriority() {
431
476
  this.#priorityState = Math.imul(this.#priorityState, 1664525) + 1013904223 >>> 0;
@@ -446,6 +491,8 @@ var PieceTable = class {
446
491
  }
447
492
  #split(node, offset) {
448
493
  if (node === null) return [null, null];
494
+ if (offset <= 0) return [null, node];
495
+ if (offset >= node.subtreeLength) return [node, null];
449
496
  const leftLength = node.left?.subtreeLength ?? 0;
450
497
  if (offset <= leftLength) {
451
498
  const [left, right] = this.#split(node.left, offset);
@@ -471,6 +518,24 @@ var PieceTable = class {
471
518
  rightNode.updateSubtreeLength();
472
519
  return [leftNode, rightNode];
473
520
  }
521
+ #dropPrefix(node, offset) {
522
+ if (node === null || offset >= node.subtreeLength) return null;
523
+ if (offset <= 0) return node;
524
+ const leftLength = node.left?.subtreeLength ?? 0;
525
+ if (offset <= leftLength) {
526
+ this.#setLeft(node, this.#dropPrefix(node.left, offset));
527
+ node.updateSubtreeLength();
528
+ return node;
529
+ }
530
+ const pieceEnd = leftLength + node.piece.length;
531
+ if (offset >= pieceEnd) return this.#dropPrefix(node.right, offset - pieceEnd);
532
+ const inPiece = offset - leftLength;
533
+ const rightNode = new PieceNode(this.#createPiece(node.piece.source, node.piece.offset + inPiece, node.piece.length - inPiece));
534
+ rightNode.priority = node.priority;
535
+ this.#setRight(rightNode, node.right);
536
+ rightNode.updateSubtreeLength();
537
+ return rightNode;
538
+ }
474
539
  #mergeNodes(left, right) {
475
540
  if (left === null) return right;
476
541
  if (right === null) return left;
@@ -561,10 +626,13 @@ function rangeOverlaps(ranges, start, end) {
561
626
  return range !== void 0 && range[0] < end;
562
627
  }
563
628
  function canCoalescePieces(prev, next) {
564
- return prev.source === next.source && prev.offset + prev.length === next.offset;
629
+ return prev.source === next.source && prev.offset + prev.length === next.offset && !formsCRLF(prev.lastCharCode, next.firstCharCode);
565
630
  }
566
631
  function coalesceTwoPieces(prev, next) {
567
- return new Piece(prev.source, prev.offset, prev.length + next.length, prev.lineOffsetStart, next.lineOffsetEnd);
632
+ return new Piece(prev.source, prev.offset, prev.length + next.length, prev.lineOffsetStart, next.lineOffsetEnd, next.hasVirtualTrailingCRBreak, prev.firstCharCode, next.lastCharCode);
633
+ }
634
+ function formsCRLF(leftCharCode, rightCharCode) {
635
+ return leftCharCode === CARRIAGE_RETURN && rightCharCode === LINE_FEED;
568
636
  }
569
637
  function upperBound(values, target) {
570
638
  let lo = 0;
@@ -584,15 +652,11 @@ function isWordSeparatorCharCode(charCode) {
584
652
  const ch = String.fromCharCode(charCode);
585
653
  return WORD_SEPARATORS.includes(ch);
586
654
  }
587
- function isWholeWordAtDocOffsets(docStart, length, docLength, charAt) {
588
- const beforeOk = docStart <= 0 || isWordSeparatorCharCode(charCodeUnitAt(charAt, docStart - 1));
589
- const afterOk = docStart + length >= docLength || isWordSeparatorCharCode(charCodeUnitAt(charAt, docStart + length));
655
+ function isWholeWordAtDocOffsets(text, docStart, length) {
656
+ const beforeOk = docStart <= 0 || isWordSeparatorCharCode(text.charCodeAt(docStart - 1));
657
+ const afterOk = docStart + length >= text.length || isWordSeparatorCharCode(text.charCodeAt(docStart + length));
590
658
  return beforeOk && afterOk;
591
659
  }
592
- function charCodeUnitAt(charAt, offset) {
593
- const unit = charAt(offset);
594
- return unit.length === 0 ? 0 : unit.charCodeAt(0);
595
- }
596
660
  function compileSearchRegExp(source, isRegex, caseSensitive) {
597
661
  const body = isRegex ? source : escapeRegExp(source);
598
662
  return new RegExp(body, `g${caseSensitive ? "" : "i"}${isRegex ? "m" : ""}`);
@@ -617,15 +681,15 @@ function buildSearchReplacementText(positionAt, offsetAt, getLineText, searchPar
617
681
  line: position.line,
618
682
  character: 0
619
683
  });
620
- const matched = lineText.slice(relStart, relStart + (matchEnd - matchStart));
621
684
  let pattern;
622
685
  try {
623
686
  pattern = compileSearchRegExp(searchParams.text, true, searchParams.caseSensitive);
624
687
  } catch {
625
688
  return searchParams.replaceText;
626
689
  }
627
- const match = new RegExp(pattern.source, pattern.flags.replace("g", "")).exec(matched);
628
- if (match === null || match[0].length !== matched.length) return searchParams.replaceText;
690
+ pattern.lastIndex = relStart;
691
+ const match = pattern.exec(lineText);
692
+ if (match === null || match.index !== relStart || match[0].length !== matchEnd - matchStart) return searchParams.replaceText;
629
693
  return expandReplaceString(searchParams.replaceText, match);
630
694
  }
631
695
  function advancePastEmptyMatch(text, index) {
@@ -1 +1 @@
1
- {"version":3,"file":"pieceTable.js","names":["#original","#createPiece","#root","#createNode","#length","#lineCount","#textFromPieces","#lastVisitedLine","#getLineOffset","#lastVisitedLineLength","#findPieceAtOffset","#bufferFor","#nextNode","#forEachPieceSegment","#collectSearchMatchesLineByLine","#replaceRangeIncremental","#lineAtOffset","#lineBreakOffset","#walk","#add","#split","#mergeNodes","#appendCoalescing","#popLeftmost","#priorityState","#nextPriority","#setLeft","#setRight"],"sources":["../../src/editor/pieceTable.ts"],"sourcesContent":["import { computeLineOffsets } from '../utils/computeFileOffsets';\nimport type { SearchParams } from './searchPanel';\nimport type { Position, Range, ResolvedTextEdit } from './textDocument';\n\nconst MAX_FIND_MATCHES = 100000;\n// TODO(ije): use Intl.Segmenter instead of regex for word separators\nconst WORD_SEPARATORS = '`~!@#$%^&*()-=+[{]}\\\\|;:\\'\",.<>/?' as const;\n\n// A piece is a segment of text that is either original or added.\nclass Piece {\n static Original = 0;\n static Added = 1;\n\n constructor(\n public readonly source: number,\n public readonly offset: number,\n public readonly length: number,\n public readonly lineOffsetStart: number,\n public readonly lineOffsetEnd: number\n ) {}\n\n get lineBreakCount(): number {\n return this.lineOffsetEnd - this.lineOffsetStart;\n }\n}\n\n// A text buffer is a string with its line offsets.\nclass TextBuffer {\n lineOffsets: number[];\n\n constructor(public text: string) {\n this.lineOffsets = computeLineOffsets(text);\n }\n\n // the append operation is efficient because it only appends\n // elements to the lineOffsets array in the end\n append(text: string): number {\n const offset = this.text.length;\n const appendedLineOffsets = computeLineOffsets(text);\n for (let i = 1; i < appendedLineOffsets.length; i++) {\n this.lineOffsets.push(offset + appendedLineOffsets[i]);\n }\n this.text += text;\n return offset;\n }\n}\n\n// A node in the balanced piece tree. `priority` keeps the tree balanced as a\n// treap (see PieceTable): the tree is a max-heap on priority while staying a\n// binary search tree on document offset.\nclass PieceNode {\n left: PieceNode | null = null;\n right: PieceNode | null = null;\n parent: PieceNode | null = null;\n priority = 0;\n\n constructor(\n public piece: Piece,\n public subtreeLength: number = piece.length,\n public subtreeLineBreakCount: number = piece.lineBreakCount\n ) {}\n\n updateSubtreeLength(): void {\n this.subtreeLength =\n (this.left?.subtreeLength ?? 0) +\n this.piece.length +\n (this.right?.subtreeLength ?? 0);\n this.subtreeLineBreakCount =\n (this.left?.subtreeLineBreakCount ?? 0) +\n this.piece.lineBreakCount +\n (this.right?.subtreeLineBreakCount ?? 0);\n }\n}\n\n/**\n * A piece table is a data structure that allows for efficient insertion and deletion of text.\n * It is a tree of pieces, where each piece is a segment of text that is either original or added.\n * The tree is a treap (a binary search tree that also keeps each node's random priority in heap\n * order, which keeps the tree balanced without an explicit rebalancing pass). Each edit reshapes\n * only the nodes along one root-to-leaf path via split and merge in O(log P), instead of\n * rebuilding all P pieces.\n * Inspired by https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation\n */\nexport class PieceTable {\n #original: TextBuffer;\n #add = new TextBuffer('');\n #root: PieceNode | null = null;\n #length = 0;\n #lineCount = 0;\n #lastVisitedLine: [number, boolean, string] | null = null;\n #lastVisitedLineLength: [number, boolean, number] | null = null;\n // Seeds the treap priorities. 0x9e3779b9 is the 32-bit golden-ratio\n // constant (2^32 / phi), a well-mixed value commonly used to seed hashes\n // and PRNGs; any fixed nonzero seed works here. A fixed seed keeps the\n // tree shape, and thus performance, deterministic across runs.\n #priorityState = 0x9e3779b9;\n\n constructor(originalText: string) {\n this.#original = new TextBuffer(originalText);\n const piece = this.#createPiece(Piece.Original, 0, originalText.length);\n this.#root = piece.length > 0 ? this.#createNode(piece) : null;\n this.#length = this.#root?.subtreeLength ?? 0;\n this.#lineCount = (this.#root?.subtreeLineBreakCount ?? 0) + 1;\n }\n\n get lineCount(): number {\n return this.#lineCount;\n }\n\n getText(range?: Range): string {\n if (range === undefined) {\n return this.#textFromPieces();\n }\n const start = this.offsetAt(range.start);\n const end = this.offsetAt(range.end);\n return this.getTextSlice(start, end);\n }\n\n getLineText(line: number, includeLineBreak = false): string {\n if (\n this.#lastVisitedLine !== null &&\n this.#lastVisitedLine[0] === line &&\n this.#lastVisitedLine[1] === includeLineBreak\n ) {\n return this.#lastVisitedLine[2];\n }\n const offset = this.#getLineOffset(line);\n if (offset === undefined) {\n throw new Error(`Line index out of range: ${line}`);\n }\n const text = this.getTextSlice(offset[0], offset[1], !includeLineBreak);\n this.#lastVisitedLine = [line, includeLineBreak, text];\n this.#lastVisitedLineLength = [line, includeLineBreak, text.length];\n return text;\n }\n\n getLineLength(line: number, includeLineBreak = false): number {\n const lastVisitedLineLength = this.#lastVisitedLineLength;\n const lastVisitedLine = this.#lastVisitedLine;\n if (\n lastVisitedLineLength !== null &&\n lastVisitedLineLength[0] === line &&\n lastVisitedLineLength[1] === includeLineBreak\n ) {\n return lastVisitedLineLength[2];\n }\n if (\n lastVisitedLine !== null &&\n lastVisitedLine[0] === line &&\n lastVisitedLine[1] === includeLineBreak\n ) {\n const length = lastVisitedLine[2].length;\n this.#lastVisitedLineLength = [line, includeLineBreak, length];\n return length;\n }\n const offset = this.#getLineOffset(line);\n if (offset === undefined) {\n throw new Error(`Line index out of range: ${line}`);\n }\n const [start, end] = offset;\n let length = end - start;\n if (!includeLineBreak) {\n while (\n length > 0 &&\n isEOL(this.charAt(start + length - 1).charCodeAt(0))\n ) {\n length--;\n }\n }\n this.#lastVisitedLineLength = [line, includeLineBreak, length];\n return length;\n }\n\n getTextSlice(start: number, end: number, trimEOF = false): string {\n if (start >= end) {\n return '';\n }\n\n const sliceStart = clamp(start, 0, this.#length);\n const sliceEnd = clamp(end, sliceStart, this.#length);\n if (sliceStart >= sliceEnd) {\n return '';\n }\n\n const location = this.#findPieceAtOffset(sliceStart);\n if (location === undefined) {\n return '';\n }\n\n const chunks: string[] = [];\n let [node, offsetInPiece] = location as [PieceNode | null, number];\n let remaining = sliceEnd - sliceStart;\n while (node !== null && remaining > 0) {\n const takeLength = Math.min(node.piece.length - offsetInPiece, remaining);\n const buffer = this.#bufferFor(node.piece.source);\n const start = node.piece.offset + offsetInPiece;\n let end = start + takeLength;\n if (trimEOF) {\n while (end > start && isEOL(buffer.text.charCodeAt(end - 1))) {\n end--;\n }\n }\n chunks.push(buffer.text.slice(start, end));\n remaining -= takeLength;\n offsetInPiece = 0;\n node = this.#nextNode(node);\n }\n\n return chunks.join('');\n }\n\n charAt(offset: number): string {\n const location = this.#findPieceAtOffset(offset);\n if (location === undefined) {\n return '';\n }\n\n const [node, offsetInPiece] = location;\n const buffer = this.#bufferFor(node.piece.source);\n return buffer.text.charAt(node.piece.offset + offsetInPiece);\n }\n\n includes(needle: string): boolean {\n if (needle.length === 0) {\n return true;\n }\n\n const prefixTable = createPrefixTable(needle);\n let matched = 0;\n let found = false;\n this.#forEachPieceSegment((segment) => {\n for (let offset = segment.start; offset < segment.end; offset++) {\n const charCode = segment.text.charCodeAt(offset);\n while (matched > 0 && charCode !== needle.charCodeAt(matched)) {\n matched = prefixTable[matched - 1];\n }\n if (charCode === needle.charCodeAt(matched)) {\n matched++;\n }\n if (matched === needle.length) {\n found = true;\n return false;\n }\n }\n return true;\n });\n return found;\n }\n\n findNextNonOverlappingSubstring(\n needle: string,\n occupied: readonly [start: number, end: number][]\n ): number | undefined {\n if (needle.length === 0 || needle.length > this.#length) {\n return undefined;\n }\n\n const ranges = normalizeRanges(occupied, this.#length);\n const pivot = ranges.reduce((max, [, end]) => Math.max(max, end), 0);\n const prefixTable = createPrefixTable(needle);\n let matched = 0;\n let documentOffset = 0;\n let wrappedOffset: number | undefined;\n let foundOffset: number | undefined;\n\n this.#forEachPieceSegment((segment) => {\n for (let offset = segment.start; offset < segment.end; offset++) {\n const charCode = segment.text.charCodeAt(offset);\n while (matched > 0 && charCode !== needle.charCodeAt(matched)) {\n matched = prefixTable[matched - 1];\n }\n if (charCode === needle.charCodeAt(matched)) {\n matched++;\n }\n if (matched === needle.length) {\n const start = documentOffset - needle.length + 1;\n if (!rangeOverlaps(ranges, start, start + needle.length)) {\n if (start >= pivot) {\n foundOffset = start;\n return false;\n }\n wrappedOffset ??= start;\n }\n matched = prefixTable[matched - 1];\n }\n documentOffset++;\n }\n return true;\n });\n\n return foundOffset ?? wrappedOffset;\n }\n\n search(searchParams: SearchParams): [start: number, end: number][] {\n if (searchParams.text.length === 0 || this.#length === 0) {\n return [];\n }\n\n // Search currently operates line-by-line, so newline-spanning patterns are unsupported.\n if (\n searchParams.text.includes('\\n') ||\n searchParams.text.includes('\\r') ||\n (searchParams.regex &&\n (searchParams.text.includes('\\\\n') ||\n searchParams.text.includes('\\\\r')))\n ) {\n return [];\n }\n\n let pattern: RegExp;\n try {\n pattern = compileSearchRegExp(\n searchParams.text,\n searchParams.regex,\n searchParams.caseSensitive\n );\n } catch {\n return [];\n }\n\n return this.#collectSearchMatchesLineByLine(\n pattern,\n searchParams.wholeWord,\n MAX_FIND_MATCHES\n );\n }\n\n #collectSearchMatchesLineByLine(\n pattern: RegExp,\n wholeWord: boolean,\n limit: number\n ): [number, number][] {\n const out: [number, number][] = [];\n const docLength = this.#length;\n const charAt = (offset: number) => this.charAt(offset);\n\n // Reuse the single compiled pattern across every line, resetting its\n // lastIndex before each line, instead of allocating a fresh RegExp per\n // line. The pattern is global, so lastIndex tracks progress within a line.\n for (let line = 0; line < this.#lineCount; line++) {\n const lineText = this.getLineText(line);\n const lineStart = this.offsetAt({ line, character: 0 });\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(lineText)) !== null) {\n const rel = match.index;\n const fragment = match[0];\n if (fragment.length === 0) {\n pattern.lastIndex = advancePastEmptyMatch(lineText, rel);\n continue;\n }\n const docStart = lineStart + rel;\n if (\n !wholeWord ||\n isWholeWordAtDocOffsets(docStart, fragment.length, docLength, charAt)\n ) {\n out.push([docStart, docStart + fragment.length]);\n if (out.length >= limit) {\n return out;\n }\n }\n if (rel === pattern.lastIndex) {\n pattern.lastIndex = advancePastEmptyMatch(lineText, rel);\n }\n }\n }\n return out;\n }\n\n insert(text: string, offset: number): void {\n if (text.length === 0) {\n return;\n }\n const start = clamp(offset, 0, this.#length);\n this.#replaceRangeIncremental(start, start, text);\n }\n\n delete(offset: number, length: number): void {\n if (length <= 0 || this.#length === 0) {\n return;\n }\n const start = clamp(offset, 0, this.#length);\n const end = clamp(start + length, start, this.#length);\n if (start === end) {\n return;\n }\n this.#replaceRangeIncremental(start, end, '');\n }\n\n applyEdits(edits: readonly ResolvedTextEdit[]): void {\n if (edits.length === 0) {\n return;\n }\n // Edits arrive sorted ascending and non-overlapping (TextDocument enforces\n // this). Applying them from last to first keeps each remaining edit's\n // offsets valid, since edits at higher offsets never shift lower ones, and\n // every edit becomes an O(log P) incremental replace.\n for (let i = edits.length - 1; i >= 0; i--) {\n const edit = edits[i];\n const start = clamp(edit.start, 0, this.#length);\n const end = clamp(edit.end, start, this.#length);\n this.#replaceRangeIncremental(start, end, edit.text);\n }\n }\n\n positionAt(offset: number): Position {\n const clampedOffset = clamp(offset, 0, this.#length);\n if (this.#length === 0) {\n return { line: 0, character: 0 };\n }\n const line = this.#lineAtOffset(clampedOffset);\n const lineStart = line === 0 ? 0 : this.#lineBreakOffset(line - 1);\n return {\n line,\n character: clampedOffset - lineStart,\n };\n }\n\n positionsAt(offsets: readonly number[]): Position[] {\n const positions: Position[] = Array.from({ length: offsets.length });\n if (offsets.length === 0) {\n return positions;\n }\n if (this.#length === 0) {\n return positions.fill({ line: 0, character: 0 });\n }\n\n for (let i = 0; i < offsets.length; i++) {\n positions[i] = this.positionAt(offsets[i]);\n }\n\n return positions;\n }\n\n offsetAt(position: Position): number {\n if (position.line < 0 || this.#length === 0) {\n return 0;\n }\n if (position.line >= this.#lineCount) {\n throw new Error(`Line index out of range: ${position.line}`);\n }\n const offset = this.#getLineOffset(position.line);\n if (offset === undefined) {\n throw new Error(`Line index out of range: ${position.line}`);\n }\n const character = clamp(position.character, 0, offset[1] - offset[0]);\n return offset[0] + character;\n }\n\n #findPieceAtOffset(\n offset: number\n ): [node: PieceNode, offsetInPiece: number] | undefined {\n if (offset < 0 || offset >= this.#length) {\n return undefined;\n }\n\n let node = this.#root;\n let remaining = offset;\n while (node !== null) {\n const leftLength = node.left?.subtreeLength ?? 0;\n if (remaining < leftLength) {\n node = node.left;\n continue;\n }\n\n remaining -= leftLength;\n if (remaining < node.piece.length) {\n return [node, remaining];\n }\n\n remaining -= node.piece.length;\n node = node.right;\n }\n\n return undefined;\n }\n\n #nextNode(node: PieceNode): PieceNode | null {\n if (node.right !== null) {\n let next = node.right;\n while (next.left !== null) {\n next = next.left;\n }\n return next;\n }\n\n let current = node;\n while (current.parent !== null && current === current.parent.right) {\n current = current.parent;\n }\n return current.parent;\n }\n\n #getLineOffset(line: number): [start: number, end: number] | undefined {\n if (line < 0) {\n throw new Error(`Line index out of range: ${line}`);\n }\n if (this.#length === 0) {\n if (line === 0) {\n return [0, 0];\n }\n throw new Error(`Line index out of range: ${line}`);\n }\n if (line >= this.#lineCount) {\n throw new Error(`Line index out of range: ${line}`);\n }\n\n const start = line === 0 ? 0 : this.#lineBreakOffset(line - 1);\n const end =\n line < this.#lineCount - 1 ? this.#lineBreakOffset(line) : this.#length;\n return [start, end];\n }\n\n #lineAtOffset(offset: number): number {\n let node = this.#root;\n let remaining = clamp(offset, 0, this.#length);\n let line = 0;\n\n while (node !== null) {\n const leftLength = node.left?.subtreeLength ?? 0;\n if (remaining < leftLength) {\n node = node.left;\n continue;\n }\n\n line += node.left?.subtreeLineBreakCount ?? 0;\n remaining -= leftLength;\n if (remaining <= node.piece.length) {\n const buffer = this.#bufferFor(node.piece.source);\n line +=\n upperBound(buffer.lineOffsets, node.piece.offset + remaining) -\n node.piece.lineOffsetStart;\n return line;\n }\n\n line += node.piece.lineBreakCount;\n remaining -= node.piece.length;\n node = node.right;\n }\n\n return this.#lineCount - 1;\n }\n\n #lineBreakOffset(lineBreakIndex: number): number {\n let node = this.#root;\n let remaining = lineBreakIndex;\n let documentOffset = 0;\n\n while (node !== null) {\n const leftLineBreakCount = node.left?.subtreeLineBreakCount ?? 0;\n if (remaining < leftLineBreakCount) {\n node = node.left;\n continue;\n }\n\n const leftLength = node.left?.subtreeLength ?? 0;\n documentOffset += leftLength;\n remaining -= leftLineBreakCount;\n\n if (remaining < node.piece.lineBreakCount) {\n const bufferLineOffset = this.#bufferFor(node.piece.source).lineOffsets[\n node.piece.lineOffsetStart + remaining\n ];\n return documentOffset + (bufferLineOffset - node.piece.offset);\n }\n\n documentOffset += node.piece.length;\n remaining -= node.piece.lineBreakCount;\n node = node.right;\n }\n\n return this.#length;\n }\n\n #textFromPieces(): string {\n const chunks: string[] = [];\n this.#forEachPieceSegment((segment) => {\n chunks.push(segment.text.slice(segment.start, segment.end));\n });\n return chunks.join('');\n }\n\n #forEachPieceSegment(\n callback: (segment: {\n readonly start: number;\n readonly end: number;\n readonly text: string;\n readonly lineOffsets: number[];\n readonly lineOffsetStart: number;\n readonly lineOffsetEnd: number;\n }) => boolean | void\n ): void {\n this.#walk(this.#root, (node) => {\n const buffer = this.#bufferFor(node.piece.source);\n return callback({\n text: buffer.text,\n lineOffsets: buffer.lineOffsets,\n lineOffsetStart: node.piece.lineOffsetStart,\n lineOffsetEnd: node.piece.lineOffsetEnd,\n start: node.piece.offset,\n end: node.piece.offset + node.piece.length,\n });\n });\n }\n\n #bufferFor(source: number): TextBuffer {\n return source === Piece.Original ? this.#original : this.#add;\n }\n\n #createPiece(source: number, offset: number, length: number): Piece {\n const buffer = this.#bufferFor(source);\n return new Piece(\n source,\n offset,\n length,\n upperBound(buffer.lineOffsets, offset),\n upperBound(buffer.lineOffsets, offset + length)\n );\n }\n\n // Replaces document range [start, end) with `text` by splitting the tree at\n // both ends, dropping the middle (the deleted span), and joining the inserted\n // piece back in. Each split/merge touches only one root-to-leaf path, so the\n // edit is O(log P) instead of rebuilding all P pieces. `start`/`end` must be\n // clamped into [0, length] with start <= end.\n #replaceRangeIncremental(start: number, end: number, text: string): void {\n if (start === end && text.length === 0) {\n return;\n }\n\n const [left, rest] = this.#split(this.#root, start);\n const [, right] = this.#split(rest, end - start);\n\n let root: PieceNode | null;\n if (text.length > 0) {\n const insertedPiece = this.#createPiece(\n Piece.Added,\n this.#add.append(text),\n text.length\n );\n // The inserted text is fresh at the end of the add buffer, so it can only\n // sit next to the piece on its left, never the one on its right.\n root = this.#mergeNodes(\n this.#appendCoalescing(left, insertedPiece),\n right\n );\n } else {\n // Pure deletion: the pieces now meeting at the seam may be contiguous in\n // the same buffer, so re-attach the right side's first piece through the\n // coalescing append to merge them when possible.\n const [seamPiece, restRight] = this.#popLeftmost(right);\n root =\n seamPiece === undefined\n ? left\n : this.#mergeNodes(\n this.#appendCoalescing(left, seamPiece),\n restRight\n );\n }\n\n if (root !== null) {\n root.parent = null;\n }\n this.#root = root;\n this.#length = root?.subtreeLength ?? 0;\n this.#lineCount = (root?.subtreeLineBreakCount ?? 0) + 1;\n this.#lastVisitedLine = null;\n this.#lastVisitedLineLength = null;\n }\n\n #nextPriority(): number {\n this.#priorityState =\n (Math.imul(this.#priorityState, 1664525) + 1013904223) >>> 0;\n return this.#priorityState;\n }\n\n #createNode(piece: Piece): PieceNode {\n const node = new PieceNode(piece);\n node.priority = this.#nextPriority();\n return node;\n }\n\n // Attaches `child` as a side of `node`, keeping the parent pointer in sync so\n // read-side iteration (#nextNode) can still walk back up the tree.\n #setLeft(node: PieceNode, child: PieceNode | null): void {\n node.left = child;\n if (child !== null) {\n child.parent = node;\n }\n }\n\n #setRight(node: PieceNode, child: PieceNode | null): void {\n node.right = child;\n if (child !== null) {\n child.parent = node;\n }\n }\n\n // Splits the subtree into [0, offset) and [offset, length). When the offset\n // falls inside a piece, that piece is sliced in two so the split lands on a\n // clean boundary. Internal parent pointers stay correct; the caller fixes the\n // returned roots' own parents when it reattaches or stores them.\n #split(\n node: PieceNode | null,\n offset: number\n ): [left: PieceNode | null, right: PieceNode | null] {\n if (node === null) {\n return [null, null];\n }\n\n const leftLength = node.left?.subtreeLength ?? 0;\n if (offset <= leftLength) {\n const [left, right] = this.#split(node.left, offset);\n this.#setLeft(node, right);\n node.updateSubtreeLength();\n return [left, node];\n }\n\n const pieceLength = node.piece.length;\n if (offset >= leftLength + pieceLength) {\n const [left, right] = this.#split(\n node.right,\n offset - leftLength - pieceLength\n );\n this.#setRight(node, left);\n node.updateSubtreeLength();\n return [node, right];\n }\n\n // The offset is strictly inside this node's piece: slice it in two. Both\n // halves inherit this node's priority, which keeps the heap valid because\n // this node already outranked both of its children.\n const inPiece = offset - leftLength;\n const leftNode = new PieceNode(\n this.#createPiece(node.piece.source, node.piece.offset, inPiece)\n );\n const rightNode = new PieceNode(\n this.#createPiece(\n node.piece.source,\n node.piece.offset + inPiece,\n pieceLength - inPiece\n )\n );\n leftNode.priority = node.priority;\n rightNode.priority = node.priority;\n this.#setLeft(leftNode, node.left);\n this.#setRight(rightNode, node.right);\n leftNode.updateSubtreeLength();\n rightNode.updateSubtreeLength();\n return [leftNode, rightNode];\n }\n\n // Joins two subtrees where every offset in `left` precedes every offset in\n // `right`, taking the higher-priority root so the result stays balanced.\n #mergeNodes(\n left: PieceNode | null,\n right: PieceNode | null\n ): PieceNode | null {\n if (left === null) {\n return right;\n }\n if (right === null) {\n return left;\n }\n if (left.priority >= right.priority) {\n this.#setRight(left, this.#mergeNodes(left.right, right));\n left.updateSubtreeLength();\n return left;\n }\n this.#setLeft(right, this.#mergeNodes(left, right.left));\n right.updateSubtreeLength();\n return right;\n }\n\n // Appends `piece` after every node in `tree`. If the tree's last piece and\n // `piece` point at adjacent text in the same buffer, they are merged in place\n // (no new node) — this is what keeps sequential typing a single piece and\n // stops the piece count from growing without bound.\n #appendCoalescing(tree: PieceNode | null, piece: Piece): PieceNode {\n if (tree === null) {\n return this.#createNode(piece);\n }\n let last = tree;\n while (last.right !== null) {\n last = last.right;\n }\n if (canCoalescePieces(last.piece, piece)) {\n last.piece = coalesceTwoPieces(last.piece, piece);\n // The last piece grew, so refresh aggregates from it up to the root.\n for (let node: PieceNode | null = last; node !== null; ) {\n node.updateSubtreeLength();\n if (node === tree) {\n break;\n }\n node = node.parent;\n }\n return tree;\n }\n // `tree` is non-null here, so merging in the new node always yields a node.\n return this.#mergeNodes(tree, this.#createNode(piece)) as PieceNode;\n }\n\n // Removes and returns the first piece of the tree along with the remaining\n // tree, so a deletion seam can re-merge the piece that becomes adjacent to\n // the one on the other side of the deletion.\n #popLeftmost(\n tree: PieceNode | null\n ): [piece: Piece | undefined, rest: PieceNode | null] {\n if (tree === null) {\n return [undefined, null];\n }\n if (tree.left === null) {\n return [tree.piece, tree.right];\n }\n const [piece, newLeft] = this.#popLeftmost(tree.left);\n this.#setLeft(tree, newLeft);\n tree.updateSubtreeLength();\n return [piece, tree];\n }\n\n #walk(\n node: PieceNode | null,\n visit: (node: PieceNode) => boolean | void\n ): boolean {\n if (node === null) {\n return true;\n }\n if (!this.#walk(node.left, visit)) {\n return false;\n }\n if (visit(node) === false) {\n return false;\n }\n return this.#walk(node.right, visit);\n }\n}\n\nfunction isEOL(charCode: number): boolean {\n return charCode === /* \\n */ 10 || charCode === /* \\r */ 13;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\nfunction createPrefixTable(text: string): number[] {\n const table = Array.from<number>({ length: text.length }).fill(0);\n let matched = 0;\n for (let i = 1; i < text.length; i++) {\n const charCode = text.charCodeAt(i);\n while (matched > 0 && charCode !== text.charCodeAt(matched)) {\n matched = table[matched - 1];\n }\n if (charCode === text.charCodeAt(matched)) {\n matched++;\n }\n table[i] = matched;\n }\n return table;\n}\n\nfunction normalizeRanges(\n ranges: readonly [start: number, end: number][],\n length: number\n): [start: number, end: number][] {\n const normalized: [start: number, end: number][] = [];\n for (const [rawStart, rawEnd] of ranges) {\n const start = clamp(rawStart, 0, length);\n const end = clamp(rawEnd, start, length);\n if (start < end) {\n normalized.push([start, end]);\n }\n }\n normalized.sort((a, b) => a[0] - b[0]);\n\n const merged: [start: number, end: number][] = [];\n for (const range of normalized) {\n const previous = merged[merged.length - 1];\n if (previous !== undefined && range[0] <= previous[1]) {\n previous[1] = Math.max(previous[1], range[1]);\n continue;\n }\n merged.push(range);\n }\n return merged;\n}\n\nfunction rangeOverlaps(\n ranges: readonly [start: number, end: number][],\n start: number,\n end: number\n): boolean {\n let low = 0;\n let high = ranges.length;\n while (low < high) {\n const mid = low + Math.floor((high - low) / 2);\n if (ranges[mid][1] <= start) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n\n const range = ranges[low];\n return range !== undefined && range[0] < end;\n}\n\n// True when two pieces point at directly adjacent text in the same backing\n// buffer, so they can be represented as one piece.\nfunction canCoalescePieces(prev: Piece, next: Piece): boolean {\n return (\n prev.source === next.source && prev.offset + prev.length === next.offset\n );\n}\n\n// Joins two adjacent pieces (see canCoalescePieces) into one. Keeping the table\n// compact after every edit is what stops the piece count from growing without\n// bound during normal typing.\nfunction coalesceTwoPieces(prev: Piece, next: Piece): Piece {\n return new Piece(\n prev.source,\n prev.offset,\n prev.length + next.length,\n prev.lineOffsetStart,\n next.lineOffsetEnd\n );\n}\n\n// Returns the index of the first element in the array that is greater than the target.\nfunction upperBound(values: number[], target: number): number {\n let lo = 0;\n let hi = values.length;\n while (lo < hi) {\n const mid = lo + Math.floor((hi - lo) / 2);\n if (values[mid] <= target) {\n lo = mid + 1;\n } else {\n hi = mid;\n }\n }\n return lo;\n}\n\nfunction escapeRegExp(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction isWordSeparatorCharCode(charCode: number): boolean {\n if (charCode <= 32 || charCode === 127) {\n return true;\n }\n const ch = String.fromCharCode(charCode);\n return WORD_SEPARATORS.includes(ch);\n}\n\n// Checks if the given text is a whole word by checking if the\n// characters before and after are word separators.\nfunction isWholeWordAtDocOffsets(\n docStart: number,\n length: number,\n docLength: number,\n charAt: (offset: number) => string\n): boolean {\n const beforeOk =\n docStart <= 0 ||\n isWordSeparatorCharCode(charCodeUnitAt(charAt, docStart - 1));\n const afterOk =\n docStart + length >= docLength ||\n isWordSeparatorCharCode(charCodeUnitAt(charAt, docStart + length));\n return beforeOk && afterOk;\n}\n\nfunction charCodeUnitAt(\n charAt: (offset: number) => string,\n offset: number\n): number {\n const unit = charAt(offset);\n return unit.length === 0 ? 0 : unit.charCodeAt(0);\n}\n\nfunction compileSearchRegExp(\n source: string,\n isRegex: boolean,\n caseSensitive: boolean\n): RegExp {\n const body = isRegex ? source : escapeRegExp(source);\n const flags = `g${caseSensitive ? '' : 'i'}${isRegex ? 'm' : ''}`;\n return new RegExp(body, flags);\n}\n\n/** Expands `$&`, `$1`, `$$`, etc. in a regex replace string using a match. */\nfunction expandReplaceString(\n replacement: string,\n match: RegExpExecArray\n): string {\n return replacement.replace(/\\$([$&]|\\d+)/g, (_token, group: string) => {\n if (group === '$') {\n return '$';\n }\n if (group === '&') {\n return match[0] ?? '';\n }\n const index = Number(group);\n return match[index] ?? '';\n });\n}\n\n/**\n * Builds the text to insert for one search match, including regex capture\n * substitution when regex mode is enabled.\n */\nexport function buildSearchReplacementText(\n positionAt: (offset: number) => Position,\n offsetAt: (position: Position) => number,\n getLineText: (line: number) => string,\n searchParams: SearchParams,\n matchStart: number,\n matchEnd: number\n): string {\n if (!searchParams.regex) {\n return searchParams.replaceText;\n }\n\n const position = positionAt(matchStart);\n const lineText = getLineText(position.line);\n const lineStart = offsetAt({ line: position.line, character: 0 });\n const relStart = matchStart - lineStart;\n const matched = lineText.slice(relStart, relStart + (matchEnd - matchStart));\n\n let pattern: RegExp;\n try {\n pattern = compileSearchRegExp(\n searchParams.text,\n true,\n searchParams.caseSensitive\n );\n } catch {\n return searchParams.replaceText;\n }\n\n const re = new RegExp(pattern.source, pattern.flags.replace('g', ''));\n const match = re.exec(matched);\n if (match === null || match[0].length !== matched.length) {\n return searchParams.replaceText;\n }\n return expandReplaceString(searchParams.replaceText, match);\n}\n\nfunction advancePastEmptyMatch(text: string, index: number): number {\n if (index + 1 < text.length) {\n const first = text.charCodeAt(index);\n const second = text.charCodeAt(index + 1);\n if (\n first >= 0xd800 &&\n first <= 0xdbff &&\n second >= 0xdc00 &&\n second <= 0xdfff\n ) {\n return index + 2;\n }\n }\n return index + 1;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pieceTable.js","names":["#original","#createPiece","#root","#createNode","#length","#lineCount","#textFromPieces","#lastVisitedLine","#getLineOffset","#lastVisitedLineLength","#findPieceAtOffset","#bufferFor","#nextNode","#forEachPieceSegment","#collectSearchMatchesLineByLine","#replaceRangeIncremental","#invalidateCaches","#lineAtOffset","#lineBreakOffset","#lastPosition","#pieceLineBreakOffset","#walk","#add","#split","#dropPrefix","#mergeNodes","#appendCoalescing","#popLeftmost","#priorityState","#nextPriority","#setLeft","#setRight"],"sources":["../../src/editor/pieceTable.ts"],"sourcesContent":["import type { Position, Range } from '../types';\nimport { computeLineOffsets } from '../utils/computeFileOffsets';\nimport type { SearchParams } from './searchPanel';\nimport type { ResolvedTextEdit } from './textDocument';\n\nconst MAX_FIND_MATCHES = 100000;\nconst LINE_FEED = 10;\nconst CARRIAGE_RETURN = 13;\n// TODO(ije): use Intl.Segmenter instead of regex for word separators\nconst WORD_SEPARATORS = '`~!@#$%^&*()-=+[{]}\\\\|;:\\'\",.<>/?' as const;\n\n// A piece is a segment of text that is either original or added.\nclass Piece {\n static Original = 0;\n static Added = 1;\n\n constructor(\n public readonly source: number,\n public readonly offset: number,\n public readonly length: number,\n public readonly lineOffsetStart: number,\n public readonly lineOffsetEnd: number,\n public readonly hasVirtualTrailingCRBreak: boolean,\n public readonly firstCharCode: number,\n public readonly lastCharCode: number\n ) {}\n\n // A slice ending between a backing-buffer CRLF needs its own lone-CR break\n // until the tree reconnects it to a piece beginning with LF.\n get lineBreakCount(): number {\n return (\n this.lineOffsetEnd -\n this.lineOffsetStart +\n (this.hasVirtualTrailingCRBreak ? 1 : 0)\n );\n }\n}\n\n// A text buffer is a string with its line offsets.\nclass TextBuffer {\n lineOffsets: number[];\n\n constructor(public text: string) {\n this.lineOffsets = computeLineOffsets(text);\n }\n\n // the append operation is efficient because it only appends\n // elements to the lineOffsets array in the end\n append(text: string): number {\n const offset = this.text.length;\n for (let i = 0; i < text.length; i++) {\n const charCode = text.charCodeAt(i);\n if (charCode !== LINE_FEED && charCode !== CARRIAGE_RETURN) {\n continue;\n }\n if (\n charCode === CARRIAGE_RETURN &&\n text.charCodeAt(i + 1) === LINE_FEED\n ) {\n i++;\n }\n this.lineOffsets.push(offset + i + 1);\n }\n this.text += text;\n return offset;\n }\n}\n\n// A node in the balanced piece tree. `priority` keeps the tree balanced as a\n// treap (see PieceTable): the tree is a max-heap on priority while staying a\n// binary search tree on document offset.\nclass PieceNode {\n left: PieceNode | null = null;\n right: PieceNode | null = null;\n parent: PieceNode | null = null;\n priority = 0;\n\n constructor(\n public piece: Piece,\n public subtreeLength: number = piece.length,\n public subtreeLineBreakCount: number = piece.lineBreakCount,\n public subtreeFirstCharCode: number = piece.firstCharCode,\n public subtreeLastCharCode: number = piece.lastCharCode\n ) {}\n\n updateSubtreeLength(): void {\n this.subtreeLength =\n (this.left?.subtreeLength ?? 0) +\n this.piece.length +\n (this.right?.subtreeLength ?? 0);\n // Pieces count their edge characters in isolation. Collapse a CRLF only\n // when the two characters are actually adjacent in this subtree.\n this.subtreeLineBreakCount =\n (this.left?.subtreeLineBreakCount ?? 0) +\n this.piece.lineBreakCount +\n (this.right?.subtreeLineBreakCount ?? 0) -\n (formsCRLF(this.left?.subtreeLastCharCode, this.piece.firstCharCode)\n ? 1\n : 0) -\n (formsCRLF(this.piece.lastCharCode, this.right?.subtreeFirstCharCode)\n ? 1\n : 0);\n this.subtreeFirstCharCode =\n this.left?.subtreeFirstCharCode ?? this.piece.firstCharCode;\n this.subtreeLastCharCode =\n this.right?.subtreeLastCharCode ?? this.piece.lastCharCode;\n }\n}\n\n/**\n * A piece table is a data structure that allows for efficient insertion and deletion of text.\n * It is a tree of pieces, where each piece is a segment of text that is either original or added.\n * The tree is a treap (a binary search tree that also keeps each node's random priority in heap\n * order, which keeps the tree balanced without an explicit rebalancing pass). Each edit reshapes\n * only the nodes along one root-to-leaf path via split and merge in O(log P), instead of\n * rebuilding all P pieces.\n * Inspired by https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation\n */\nexport class PieceTable {\n #original: TextBuffer;\n #add = new TextBuffer('');\n #root: PieceNode | null = null;\n #length = 0;\n #lineCount = 0;\n #lastVisitedLine: [number, boolean, string] | null = null;\n #lastVisitedLineLength: [number, boolean, number] | null = null;\n // Exact inverse of the latest positionAt lookup; edits invalidate it.\n #lastPosition: [line: number, character: number, offset: number] | null =\n null;\n // Seeds the treap priorities. 0x9e3779b9 is the 32-bit golden-ratio\n // constant (2^32 / phi), a well-mixed value commonly used to seed hashes\n // and PRNGs; any fixed nonzero seed works here. A fixed seed keeps the\n // tree shape, and thus performance, deterministic across runs.\n #priorityState = 0x9e3779b9;\n\n constructor(originalText: string) {\n this.#original = new TextBuffer(originalText);\n const piece = this.#createPiece(Piece.Original, 0, originalText.length);\n this.#root = piece.length > 0 ? this.#createNode(piece) : null;\n this.#length = this.#root?.subtreeLength ?? 0;\n this.#lineCount = (this.#root?.subtreeLineBreakCount ?? 0) + 1;\n }\n\n get lineCount(): number {\n return this.#lineCount;\n }\n\n getText(range?: Range): string {\n if (range === undefined) {\n return this.#textFromPieces();\n }\n const start = this.offsetAt(range.start);\n const end = this.offsetAt(range.end);\n return this.getTextSlice(start, end);\n }\n\n getLineText(line: number, includeLineBreak = false): string {\n if (\n this.#lastVisitedLine !== null &&\n this.#lastVisitedLine[0] === line &&\n this.#lastVisitedLine[1] === includeLineBreak\n ) {\n return this.#lastVisitedLine[2];\n }\n const offset = this.#getLineOffset(line);\n if (offset === undefined) {\n throw new Error(`Line index out of range: ${line}`);\n }\n const text = this.getTextSlice(offset[0], offset[1], !includeLineBreak);\n this.#lastVisitedLine = [line, includeLineBreak, text];\n this.#lastVisitedLineLength = [line, includeLineBreak, text.length];\n return text;\n }\n\n getLineLength(line: number, includeLineBreak = false): number {\n const lastVisitedLineLength = this.#lastVisitedLineLength;\n const lastVisitedLine = this.#lastVisitedLine;\n if (\n lastVisitedLineLength !== null &&\n lastVisitedLineLength[0] === line &&\n lastVisitedLineLength[1] === includeLineBreak\n ) {\n return lastVisitedLineLength[2];\n }\n if (\n lastVisitedLine !== null &&\n lastVisitedLine[0] === line &&\n lastVisitedLine[1] === includeLineBreak\n ) {\n const length = lastVisitedLine[2].length;\n this.#lastVisitedLineLength = [line, includeLineBreak, length];\n return length;\n }\n const offset = this.#getLineOffset(line);\n if (offset === undefined) {\n throw new Error(`Line index out of range: ${line}`);\n }\n const [start, end] = offset;\n let length = end - start;\n if (!includeLineBreak) {\n while (\n length > 0 &&\n isEOL(this.charAt(start + length - 1).charCodeAt(0))\n ) {\n length--;\n }\n }\n this.#lastVisitedLineLength = [line, includeLineBreak, length];\n return length;\n }\n\n getTextSlice(start: number, end: number, trimEOF = false): string {\n if (start >= end) {\n return '';\n }\n\n const sliceStart = clamp(start, 0, this.#length);\n const sliceEnd = clamp(end, sliceStart, this.#length);\n if (sliceStart >= sliceEnd) {\n return '';\n }\n\n const location = this.#findPieceAtOffset(sliceStart);\n if (location === undefined) {\n return '';\n }\n\n const chunks: string[] = [];\n let [node, offsetInPiece] = location as [PieceNode | null, number];\n let remaining = sliceEnd - sliceStart;\n while (node !== null && remaining > 0) {\n const takeLength = Math.min(node.piece.length - offsetInPiece, remaining);\n const buffer = this.#bufferFor(node.piece.source);\n const start = node.piece.offset + offsetInPiece;\n let end = start + takeLength;\n if (trimEOF) {\n while (end > start && isEOL(buffer.text.charCodeAt(end - 1))) {\n end--;\n }\n }\n chunks.push(buffer.text.slice(start, end));\n remaining -= takeLength;\n offsetInPiece = 0;\n node = this.#nextNode(node);\n }\n\n return chunks.join('');\n }\n\n charAt(offset: number): string {\n const location = this.#findPieceAtOffset(offset);\n if (location === undefined) {\n return '';\n }\n\n const [node, offsetInPiece] = location;\n const buffer = this.#bufferFor(node.piece.source);\n return buffer.text.charAt(node.piece.offset + offsetInPiece);\n }\n\n includes(needle: string): boolean {\n if (needle.length === 0) {\n return true;\n }\n\n const prefixTable = createPrefixTable(needle);\n let matched = 0;\n let found = false;\n this.#forEachPieceSegment((segment) => {\n for (let offset = segment.start; offset < segment.end; offset++) {\n const charCode = segment.text.charCodeAt(offset);\n while (matched > 0 && charCode !== needle.charCodeAt(matched)) {\n matched = prefixTable[matched - 1];\n }\n if (charCode === needle.charCodeAt(matched)) {\n matched++;\n }\n if (matched === needle.length) {\n found = true;\n return false;\n }\n }\n return true;\n });\n return found;\n }\n\n findNextNonOverlappingSubstring(\n needle: string,\n occupied: readonly [start: number, end: number][]\n ): number | undefined {\n if (needle.length === 0 || needle.length > this.#length) {\n return undefined;\n }\n\n const ranges = normalizeRanges(occupied, this.#length);\n const pivot = ranges.reduce((max, [, end]) => Math.max(max, end), 0);\n const prefixTable = createPrefixTable(needle);\n let matched = 0;\n let documentOffset = 0;\n let wrappedOffset: number | undefined;\n let foundOffset: number | undefined;\n\n this.#forEachPieceSegment((segment) => {\n for (let offset = segment.start; offset < segment.end; offset++) {\n const charCode = segment.text.charCodeAt(offset);\n while (matched > 0 && charCode !== needle.charCodeAt(matched)) {\n matched = prefixTable[matched - 1];\n }\n if (charCode === needle.charCodeAt(matched)) {\n matched++;\n }\n if (matched === needle.length) {\n const start = documentOffset - needle.length + 1;\n if (!rangeOverlaps(ranges, start, start + needle.length)) {\n if (start >= pivot) {\n foundOffset = start;\n return false;\n }\n wrappedOffset ??= start;\n }\n matched = prefixTable[matched - 1];\n }\n documentOffset++;\n }\n return true;\n });\n\n return foundOffset ?? wrappedOffset;\n }\n\n search(searchParams: SearchParams): [start: number, end: number][] {\n if (searchParams.text.length === 0 || this.#length === 0) {\n return [];\n }\n\n // Search currently operates line-by-line, so newline-spanning patterns are unsupported.\n if (\n searchParams.text.includes('\\n') ||\n searchParams.text.includes('\\r') ||\n (searchParams.regex &&\n (searchParams.text.includes('\\\\n') ||\n searchParams.text.includes('\\\\r')))\n ) {\n return [];\n }\n\n let pattern: RegExp;\n try {\n pattern = compileSearchRegExp(\n searchParams.text,\n searchParams.regex,\n searchParams.caseSensitive\n );\n } catch {\n return [];\n }\n\n return this.#collectSearchMatchesLineByLine(\n pattern,\n searchParams.wholeWord,\n MAX_FIND_MATCHES\n );\n }\n\n #collectSearchMatchesLineByLine(\n pattern: RegExp,\n wholeWord: boolean,\n limit: number\n ): [number, number][] {\n const out: [number, number][] = [];\n // Search visits the whole document, so flatten it once instead of making\n // several treap descents for every line and whole-word boundary.\n const documentText = this.#textFromPieces();\n const docLength = documentText.length;\n const lineOffsets = computeLineOffsets(documentText);\n\n // Reuse the single compiled pattern across every line, resetting its\n // lastIndex before each line, instead of allocating a fresh RegExp per\n // line. The pattern is global, so lastIndex tracks progress within a line.\n for (let line = 0; line < lineOffsets.length; line++) {\n const lineStart = lineOffsets[line];\n let lineEnd = lineOffsets[line + 1] ?? docLength;\n while (\n lineEnd > lineStart &&\n isEOL(documentText.charCodeAt(lineEnd - 1))\n ) {\n lineEnd--;\n }\n const lineText = documentText.slice(lineStart, lineEnd);\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(lineText)) !== null) {\n const rel = match.index;\n const fragment = match[0];\n if (fragment.length === 0) {\n pattern.lastIndex = advancePastEmptyMatch(lineText, rel);\n continue;\n }\n const docStart = lineStart + rel;\n if (\n !wholeWord ||\n isWholeWordAtDocOffsets(documentText, docStart, fragment.length)\n ) {\n out.push([docStart, docStart + fragment.length]);\n if (out.length >= limit) {\n return out;\n }\n }\n if (rel === pattern.lastIndex) {\n pattern.lastIndex = advancePastEmptyMatch(lineText, rel);\n }\n }\n }\n return out;\n }\n\n insert(text: string, offset: number): void {\n if (text.length === 0) {\n return;\n }\n const start = clamp(offset, 0, this.#length);\n this.#replaceRangeIncremental(start, start, text);\n this.#invalidateCaches();\n }\n\n delete(offset: number, length: number): void {\n if (length <= 0 || this.#length === 0) {\n return;\n }\n const start = clamp(offset, 0, this.#length);\n const end = clamp(start + length, start, this.#length);\n if (start === end) {\n return;\n }\n this.#replaceRangeIncremental(start, end, '');\n this.#invalidateCaches();\n }\n\n applyEdits(edits: readonly ResolvedTextEdit[]): void {\n if (edits.length === 0) {\n return;\n }\n // Edits arrive sorted ascending and non-overlapping (TextDocument enforces\n // this). Applying them from last to first keeps each remaining edit's\n // offsets valid, since edits at higher offsets never shift lower ones, and\n // every edit becomes an O(log P) incremental replace.\n for (let i = edits.length - 1; i >= 0; i--) {\n const edit = edits[i];\n const start = clamp(edit.start, 0, this.#length);\n const end = clamp(edit.end, start, this.#length);\n this.#replaceRangeIncremental(start, end, edit.text);\n }\n this.#invalidateCaches();\n }\n\n positionAt(offset: number): Position {\n const clampedOffset = clamp(offset, 0, this.#length);\n if (this.#length === 0) {\n return { line: 0, character: 0 };\n }\n const line = this.#lineAtOffset(clampedOffset);\n const lineStart = line === 0 ? 0 : this.#lineBreakOffset(line - 1);\n const character = clampedOffset - lineStart;\n this.#lastPosition = [line, character, clampedOffset];\n return { line, character };\n }\n\n positionsAt(offsets: readonly number[]): Position[] {\n const positions: Position[] = Array.from({ length: offsets.length });\n if (offsets.length === 0) {\n return positions;\n }\n if (this.#length === 0) {\n return positions.fill({ line: 0, character: 0 });\n }\n\n for (let i = 0; i < offsets.length; i++) {\n positions[i] = this.positionAt(offsets[i]);\n }\n\n return positions;\n }\n\n offsetAt(position: Position): number {\n if (position.line < 0 || this.#length === 0) {\n return 0;\n }\n if (position.line >= this.#lineCount) {\n throw new Error(`Line index out of range: ${position.line}`);\n }\n const lastPosition = this.#lastPosition;\n if (\n lastPosition !== null &&\n lastPosition[0] === position.line &&\n lastPosition[1] === position.character\n ) {\n return lastPosition[2];\n }\n const offset = this.#getLineOffset(position.line);\n if (offset === undefined) {\n throw new Error(`Line index out of range: ${position.line}`);\n }\n const character = clamp(position.character, 0, offset[1] - offset[0]);\n return offset[0] + character;\n }\n\n #findPieceAtOffset(\n offset: number\n ): [node: PieceNode, offsetInPiece: number] | undefined {\n if (offset < 0 || offset >= this.#length) {\n return undefined;\n }\n\n let node = this.#root;\n let remaining = offset;\n while (node !== null) {\n const leftLength = node.left?.subtreeLength ?? 0;\n if (remaining < leftLength) {\n node = node.left;\n continue;\n }\n\n remaining -= leftLength;\n if (remaining < node.piece.length) {\n return [node, remaining];\n }\n\n remaining -= node.piece.length;\n node = node.right;\n }\n\n return undefined;\n }\n\n #nextNode(node: PieceNode): PieceNode | null {\n if (node.right !== null) {\n let next = node.right;\n while (next.left !== null) {\n next = next.left;\n }\n return next;\n }\n\n let current = node;\n while (current.parent !== null && current === current.parent.right) {\n current = current.parent;\n }\n return current.parent;\n }\n\n #getLineOffset(line: number): [start: number, end: number] | undefined {\n if (line < 0) {\n throw new Error(`Line index out of range: ${line}`);\n }\n if (this.#length === 0) {\n if (line === 0) {\n return [0, 0];\n }\n throw new Error(`Line index out of range: ${line}`);\n }\n if (line >= this.#lineCount) {\n throw new Error(`Line index out of range: ${line}`);\n }\n\n const start = line === 0 ? 0 : this.#lineBreakOffset(line - 1);\n const end =\n line < this.#lineCount - 1 ? this.#lineBreakOffset(line) : this.#length;\n return [start, end];\n }\n\n #lineAtOffset(offset: number): number {\n let node = this.#root;\n let remaining = clamp(offset, 0, this.#length);\n let line = 0;\n\n while (node !== null) {\n const leftLength = node.left?.subtreeLength ?? 0;\n if (remaining < leftLength) {\n node = node.left;\n continue;\n }\n\n line +=\n (node.left?.subtreeLineBreakCount ?? 0) -\n (formsCRLF(node.left?.subtreeLastCharCode, node.piece.firstCharCode)\n ? 1\n : 0);\n remaining -= leftLength;\n if (remaining <= node.piece.length) {\n const buffer = this.#bufferFor(node.piece.source);\n const lineOffsetEnd = Math.min(\n upperBound(buffer.lineOffsets, node.piece.offset + remaining),\n node.piece.lineOffsetEnd\n );\n line += lineOffsetEnd - node.piece.lineOffsetStart;\n if (\n remaining === node.piece.length &&\n node.piece.hasVirtualTrailingCRBreak\n ) {\n line++;\n }\n if (\n remaining === node.piece.length &&\n formsCRLF(\n node.piece.lastCharCode,\n this.#nextNode(node)?.piece.firstCharCode\n )\n ) {\n line--;\n }\n return line;\n }\n\n line +=\n node.piece.lineBreakCount -\n (formsCRLF(node.piece.lastCharCode, node.right?.subtreeFirstCharCode)\n ? 1\n : 0);\n remaining -= node.piece.length;\n node = node.right;\n }\n\n return this.#lineCount - 1;\n }\n\n #lineBreakOffset(lineBreakIndex: number): number {\n let node = this.#root;\n let remaining = lineBreakIndex;\n let documentOffset = 0;\n let followingCharCode: number | undefined;\n\n while (node !== null) {\n const leftLineBreakCount =\n (node.left?.subtreeLineBreakCount ?? 0) -\n (formsCRLF(node.left?.subtreeLastCharCode, node.piece.firstCharCode)\n ? 1\n : 0);\n if (remaining < leftLineBreakCount) {\n followingCharCode = node.piece.firstCharCode;\n node = node.left;\n continue;\n }\n\n const leftLength = node.left?.subtreeLength ?? 0;\n documentOffset += leftLength;\n remaining -= leftLineBreakCount;\n\n const nextCharCode =\n node.right?.subtreeFirstCharCode ?? followingCharCode;\n const pieceLineBreakCount =\n node.piece.lineBreakCount -\n (formsCRLF(node.piece.lastCharCode, nextCharCode) ? 1 : 0);\n if (remaining < pieceLineBreakCount) {\n return (\n documentOffset + this.#pieceLineBreakOffset(node.piece, remaining)\n );\n }\n\n documentOffset += node.piece.length;\n remaining -= pieceLineBreakCount;\n node = node.right;\n }\n\n return this.#length;\n }\n\n #pieceLineBreakOffset(piece: Piece, lineBreakIndex: number): number {\n const bufferLineBreakCount = piece.lineOffsetEnd - piece.lineOffsetStart;\n if (lineBreakIndex < bufferLineBreakCount) {\n const bufferLineOffset = this.#bufferFor(piece.source).lineOffsets[\n piece.lineOffsetStart + lineBreakIndex\n ];\n return bufferLineOffset - piece.offset;\n }\n return piece.length;\n }\n\n #textFromPieces(): string {\n const chunks: string[] = [];\n this.#forEachPieceSegment((segment) => {\n chunks.push(segment.text.slice(segment.start, segment.end));\n });\n return chunks.join('');\n }\n\n #forEachPieceSegment(\n callback: (segment: {\n readonly start: number;\n readonly end: number;\n readonly text: string;\n readonly lineOffsets: number[];\n readonly lineOffsetStart: number;\n readonly lineOffsetEnd: number;\n }) => boolean | void\n ): void {\n this.#walk(this.#root, (node) => {\n const buffer = this.#bufferFor(node.piece.source);\n return callback({\n text: buffer.text,\n lineOffsets: buffer.lineOffsets,\n lineOffsetStart: node.piece.lineOffsetStart,\n lineOffsetEnd: node.piece.lineOffsetEnd,\n start: node.piece.offset,\n end: node.piece.offset + node.piece.length,\n });\n });\n }\n\n #bufferFor(source: number): TextBuffer {\n return source === Piece.Original ? this.#original : this.#add;\n }\n\n #createPiece(source: number, offset: number, length: number): Piece {\n const buffer = this.#bufferFor(source);\n const end = offset + length;\n const lineOffsetStart = upperBound(buffer.lineOffsets, offset);\n const lineOffsetEnd = upperBound(buffer.lineOffsets, end);\n const lastCharCode = buffer.text.charCodeAt(end - 1);\n return new Piece(\n source,\n offset,\n length,\n lineOffsetStart,\n lineOffsetEnd,\n lastCharCode === CARRIAGE_RETURN &&\n buffer.lineOffsets[lineOffsetEnd - 1] !== end,\n buffer.text.charCodeAt(offset),\n lastCharCode\n );\n }\n\n // Replaces document range [start, end) with `text` by splitting at the start,\n // dropping the deleted prefix from the remainder, and joining the inserted\n // piece back in. Each tree operation touches only one root-to-leaf path, so\n // the edit is O(log P) instead of rebuilding all P pieces. `start`/`end` must\n // be clamped into [0, length] with start <= end.\n #replaceRangeIncremental(start: number, end: number, text: string): void {\n if (start === end && text.length === 0) {\n return;\n }\n\n const [left, rest] = this.#split(this.#root, start);\n const right = this.#dropPrefix(rest, end - start);\n\n let root: PieceNode | null;\n if (text.length > 0) {\n const insertedPiece = this.#createPiece(\n Piece.Added,\n this.#add.append(text),\n text.length\n );\n // The inserted text is fresh at the end of the add buffer, so it can only\n // sit next to the piece on its left, never the one on its right.\n root = this.#mergeNodes(\n this.#appendCoalescing(left, insertedPiece),\n right\n );\n } else {\n // Pure deletion: the pieces now meeting at the seam may be contiguous in\n // the same buffer, so re-attach the right side's first piece through the\n // coalescing append to merge them when possible.\n const [seamPiece, restRight] = this.#popLeftmost(right);\n root =\n seamPiece === undefined\n ? left\n : this.#mergeNodes(\n this.#appendCoalescing(left, seamPiece),\n restRight\n );\n }\n\n if (root !== null) {\n root.parent = null;\n }\n this.#root = root;\n this.#length = root?.subtreeLength ?? 0;\n this.#lineCount = (root?.subtreeLineBreakCount ?? 0) + 1;\n }\n\n // Public mutations clear read caches once, including multi-edit batches.\n #invalidateCaches(): void {\n this.#lastVisitedLine = null;\n this.#lastVisitedLineLength = null;\n this.#lastPosition = null;\n }\n\n #nextPriority(): number {\n this.#priorityState =\n (Math.imul(this.#priorityState, 1664525) + 1013904223) >>> 0;\n return this.#priorityState;\n }\n\n #createNode(piece: Piece): PieceNode {\n const node = new PieceNode(piece);\n node.priority = this.#nextPriority();\n return node;\n }\n\n // Attaches `child` as a side of `node`, keeping the parent pointer in sync so\n // read-side iteration (#nextNode) can still walk back up the tree.\n #setLeft(node: PieceNode, child: PieceNode | null): void {\n node.left = child;\n if (child !== null) {\n child.parent = node;\n }\n }\n\n #setRight(node: PieceNode, child: PieceNode | null): void {\n node.right = child;\n if (child !== null) {\n child.parent = node;\n }\n }\n\n // Splits the subtree into [0, offset) and [offset, length). When the offset\n // falls inside a piece, that piece is sliced in two so the split lands on a\n // clean boundary. Internal parent pointers stay correct; the caller fixes the\n // returned roots' own parents when it reattaches or stores them.\n #split(\n node: PieceNode | null,\n offset: number\n ): [left: PieceNode | null, right: PieceNode | null] {\n if (node === null) {\n return [null, null];\n }\n if (offset <= 0) {\n return [null, node];\n }\n if (offset >= node.subtreeLength) {\n return [node, null];\n }\n\n const leftLength = node.left?.subtreeLength ?? 0;\n if (offset <= leftLength) {\n const [left, right] = this.#split(node.left, offset);\n this.#setLeft(node, right);\n node.updateSubtreeLength();\n return [left, node];\n }\n\n const pieceLength = node.piece.length;\n if (offset >= leftLength + pieceLength) {\n const [left, right] = this.#split(\n node.right,\n offset - leftLength - pieceLength\n );\n this.#setRight(node, left);\n node.updateSubtreeLength();\n return [node, right];\n }\n\n // The offset is strictly inside this node's piece: slice it in two. Both\n // halves inherit this node's priority, which keeps the heap valid because\n // this node already outranked both of its children.\n const inPiece = offset - leftLength;\n const leftNode = new PieceNode(\n this.#createPiece(node.piece.source, node.piece.offset, inPiece)\n );\n const rightNode = new PieceNode(\n this.#createPiece(\n node.piece.source,\n node.piece.offset + inPiece,\n pieceLength - inPiece\n )\n );\n leftNode.priority = node.priority;\n rightNode.priority = node.priority;\n this.#setLeft(leftNode, node.left);\n this.#setRight(rightNode, node.right);\n leftNode.updateSubtreeLength();\n rightNode.updateSubtreeLength();\n return [leftNode, rightNode];\n }\n\n // Discards [0, offset), allocating only the surviving half of a cut piece.\n #dropPrefix(node: PieceNode | null, offset: number): PieceNode | null {\n if (node === null || offset >= node.subtreeLength) {\n return null;\n }\n if (offset <= 0) {\n return node;\n }\n\n const leftLength = node.left?.subtreeLength ?? 0;\n if (offset <= leftLength) {\n this.#setLeft(node, this.#dropPrefix(node.left, offset));\n node.updateSubtreeLength();\n return node;\n }\n\n const pieceEnd = leftLength + node.piece.length;\n if (offset >= pieceEnd) {\n return this.#dropPrefix(node.right, offset - pieceEnd);\n }\n\n const inPiece = offset - leftLength;\n const rightNode = new PieceNode(\n this.#createPiece(\n node.piece.source,\n node.piece.offset + inPiece,\n node.piece.length - inPiece\n )\n );\n rightNode.priority = node.priority;\n this.#setRight(rightNode, node.right);\n rightNode.updateSubtreeLength();\n return rightNode;\n }\n\n // Joins two subtrees where every offset in `left` precedes every offset in\n // `right`, taking the higher-priority root so the result stays balanced.\n #mergeNodes(\n left: PieceNode | null,\n right: PieceNode | null\n ): PieceNode | null {\n if (left === null) {\n return right;\n }\n if (right === null) {\n return left;\n }\n if (left.priority >= right.priority) {\n this.#setRight(left, this.#mergeNodes(left.right, right));\n left.updateSubtreeLength();\n return left;\n }\n this.#setLeft(right, this.#mergeNodes(left, right.left));\n right.updateSubtreeLength();\n return right;\n }\n\n // Appends `piece` after every node in `tree`. Compatible adjacent pieces in\n // the same buffer are merged in place, keeping sequential typing compact.\n #appendCoalescing(tree: PieceNode | null, piece: Piece): PieceNode {\n if (tree === null) {\n return this.#createNode(piece);\n }\n let last = tree;\n while (last.right !== null) {\n last = last.right;\n }\n if (canCoalescePieces(last.piece, piece)) {\n last.piece = coalesceTwoPieces(last.piece, piece);\n // The last piece grew, so refresh aggregates from it up to the root.\n for (let node: PieceNode | null = last; node !== null; ) {\n node.updateSubtreeLength();\n if (node === tree) {\n break;\n }\n node = node.parent;\n }\n return tree;\n }\n // `tree` is non-null here, so merging in the new node always yields a node.\n return this.#mergeNodes(tree, this.#createNode(piece)) as PieceNode;\n }\n\n // Removes and returns the first piece of the tree along with the remaining\n // tree, so a deletion seam can re-merge the piece that becomes adjacent to\n // the one on the other side of the deletion.\n #popLeftmost(\n tree: PieceNode | null\n ): [piece: Piece | undefined, rest: PieceNode | null] {\n if (tree === null) {\n return [undefined, null];\n }\n if (tree.left === null) {\n return [tree.piece, tree.right];\n }\n const [piece, newLeft] = this.#popLeftmost(tree.left);\n this.#setLeft(tree, newLeft);\n tree.updateSubtreeLength();\n return [piece, tree];\n }\n\n #walk(\n node: PieceNode | null,\n visit: (node: PieceNode) => boolean | void\n ): boolean {\n if (node === null) {\n return true;\n }\n if (!this.#walk(node.left, visit)) {\n return false;\n }\n if (visit(node) === false) {\n return false;\n }\n return this.#walk(node.right, visit);\n }\n}\n\nfunction isEOL(charCode: number): boolean {\n return charCode === /* \\n */ 10 || charCode === /* \\r */ 13;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\nfunction createPrefixTable(text: string): number[] {\n const table = Array.from<number>({ length: text.length }).fill(0);\n let matched = 0;\n for (let i = 1; i < text.length; i++) {\n const charCode = text.charCodeAt(i);\n while (matched > 0 && charCode !== text.charCodeAt(matched)) {\n matched = table[matched - 1];\n }\n if (charCode === text.charCodeAt(matched)) {\n matched++;\n }\n table[i] = matched;\n }\n return table;\n}\n\nfunction normalizeRanges(\n ranges: readonly [start: number, end: number][],\n length: number\n): [start: number, end: number][] {\n const normalized: [start: number, end: number][] = [];\n for (const [rawStart, rawEnd] of ranges) {\n const start = clamp(rawStart, 0, length);\n const end = clamp(rawEnd, start, length);\n if (start < end) {\n normalized.push([start, end]);\n }\n }\n normalized.sort((a, b) => a[0] - b[0]);\n\n const merged: [start: number, end: number][] = [];\n for (const range of normalized) {\n const previous = merged[merged.length - 1];\n if (previous !== undefined && range[0] <= previous[1]) {\n previous[1] = Math.max(previous[1], range[1]);\n continue;\n }\n merged.push(range);\n }\n return merged;\n}\n\nfunction rangeOverlaps(\n ranges: readonly [start: number, end: number][],\n start: number,\n end: number\n): boolean {\n let low = 0;\n let high = ranges.length;\n while (low < high) {\n const mid = low + Math.floor((high - low) / 2);\n if (ranges[mid][1] <= start) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n\n const range = ranges[low];\n return range !== undefined && range[0] < end;\n}\n\n// CRLF seams stay explicit because separately appended buffer chunks may each\n// record a break there; the tree aggregate reconciles the document-level pair.\nfunction canCoalescePieces(prev: Piece, next: Piece): boolean {\n return (\n prev.source === next.source &&\n prev.offset + prev.length === next.offset &&\n !formsCRLF(prev.lastCharCode, next.firstCharCode)\n );\n}\n\n// Joins two adjacent pieces (see canCoalescePieces) into one. Keeping the table\n// compact after every edit is what stops the piece count from growing without\n// bound during normal typing.\nfunction coalesceTwoPieces(prev: Piece, next: Piece): Piece {\n return new Piece(\n prev.source,\n prev.offset,\n prev.length + next.length,\n prev.lineOffsetStart,\n next.lineOffsetEnd,\n next.hasVirtualTrailingCRBreak,\n prev.firstCharCode,\n next.lastCharCode\n );\n}\n\nfunction formsCRLF(\n leftCharCode: number | undefined,\n rightCharCode: number | undefined\n): boolean {\n return leftCharCode === CARRIAGE_RETURN && rightCharCode === LINE_FEED;\n}\n\n// Returns the index of the first element in the array that is greater than the target.\nfunction upperBound(values: number[], target: number): number {\n let lo = 0;\n let hi = values.length;\n while (lo < hi) {\n const mid = lo + Math.floor((hi - lo) / 2);\n if (values[mid] <= target) {\n lo = mid + 1;\n } else {\n hi = mid;\n }\n }\n return lo;\n}\n\nfunction escapeRegExp(text: string): string {\n return text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction isWordSeparatorCharCode(charCode: number): boolean {\n if (charCode <= 32 || charCode === 127) {\n return true;\n }\n const ch = String.fromCharCode(charCode);\n return WORD_SEPARATORS.includes(ch);\n}\n\n// Checks if the given text is a whole word by checking if the\n// characters before and after are word separators.\nfunction isWholeWordAtDocOffsets(\n text: string,\n docStart: number,\n length: number\n): boolean {\n const beforeOk =\n docStart <= 0 || isWordSeparatorCharCode(text.charCodeAt(docStart - 1));\n const afterOk =\n docStart + length >= text.length ||\n isWordSeparatorCharCode(text.charCodeAt(docStart + length));\n return beforeOk && afterOk;\n}\n\nfunction compileSearchRegExp(\n source: string,\n isRegex: boolean,\n caseSensitive: boolean\n): RegExp {\n const body = isRegex ? source : escapeRegExp(source);\n const flags = `g${caseSensitive ? '' : 'i'}${isRegex ? 'm' : ''}`;\n return new RegExp(body, flags);\n}\n\n/** Expands `$&`, `$1`, `$$`, etc. in a regex replace string using a match. */\nfunction expandReplaceString(\n replacement: string,\n match: RegExpExecArray\n): string {\n return replacement.replace(/\\$([$&]|\\d+)/g, (_token, group: string) => {\n if (group === '$') {\n return '$';\n }\n if (group === '&') {\n return match[0] ?? '';\n }\n const index = Number(group);\n return match[index] ?? '';\n });\n}\n\n/**\n * Builds the text to insert for one search match, including regex capture\n * substitution when regex mode is enabled.\n */\nexport function buildSearchReplacementText(\n positionAt: (offset: number) => Position,\n offsetAt: (position: Position) => number,\n getLineText: (line: number) => string,\n searchParams: SearchParams,\n matchStart: number,\n matchEnd: number\n): string {\n if (!searchParams.regex) {\n return searchParams.replaceText;\n }\n\n const position = positionAt(matchStart);\n const lineText = getLineText(position.line);\n const lineStart = offsetAt({ line: position.line, character: 0 });\n const relStart = matchStart - lineStart;\n\n let pattern: RegExp;\n try {\n pattern = compileSearchRegExp(\n searchParams.text,\n true,\n searchParams.caseSensitive\n );\n } catch {\n return searchParams.replaceText;\n }\n\n // Re-run at the original line offset so lookaround can inspect context\n // outside the matched range while captures still come from this exact hit.\n pattern.lastIndex = relStart;\n const match = pattern.exec(lineText);\n if (\n match === null ||\n match.index !== relStart ||\n match[0].length !== matchEnd - matchStart\n ) {\n return searchParams.replaceText;\n }\n return expandReplaceString(searchParams.replaceText, match);\n}\n\nfunction advancePastEmptyMatch(text: string, index: number): number {\n if (index + 1 < text.length) {\n const first = text.charCodeAt(index);\n const second = text.charCodeAt(index + 1);\n if (\n first >= 0xd800 &&\n first <= 0xdbff &&\n second >= 0xdc00 &&\n second <= 0xdfff\n ) {\n return index + 2;\n }\n }\n return index + 1;\n}\n"],"mappings":"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"}
@@ -39,6 +39,8 @@ function isMoveCursorShortcut(e) {
39
39
  else if (e.key === "ArrowDown") return "down";
40
40
  else if (e.key === "ArrowLeft") return "left";
41
41
  else if (e.key === "ArrowRight") return "right";
42
+ else if (e.key === "Home") return "start";
43
+ else if (e.key === "End") return "end";
42
44
  }
43
45
  if (isPrimaryModifier(e)) {
44
46
  if (e.key === "ArrowLeft") return "textStart";
@@ -1 +1 @@
1
- {"version":3,"file":"platform.js","names":[],"sources":["../../src/editor/platform.ts"],"sourcesContent":["let _isMacLike: boolean | undefined = undefined;\nlet _isLinux: boolean | undefined = undefined;\nlet _isSafari: boolean | undefined = undefined;\n\n/**\n * Clears the cached platform/browser detection. Detection is memoized on first\n * call, so a test process that swaps `navigator` (e.g. to exercise Linux or\n * Safari behavior) must reset it; otherwise the value cached by an earlier test\n * leaks across tests and no longer matches the active navigator.\n */\nexport function resetPlatformDetectionForTests(): void {\n _isMacLike = undefined;\n _isLinux = undefined;\n _isSafari = undefined;\n}\n\nexport function isMacLike(): boolean {\n return (_isMacLike ??= /macOS|MacIntel|iPhone|iPad|iPod/i.test(\n getPlatform()\n ));\n}\n\nexport function isLinux(): boolean {\n return (_isLinux ??= /Linux/i.test(getPlatform()));\n}\n\nexport function isSafari(): boolean {\n return (_isSafari ??=\n ('safari' in window && 'pushNotification' in (window as any).safari) ||\n /^((?!chrome|android).)*safari/i.test(navigator.userAgent));\n}\n\nexport function isPrimaryModifier(\n { metaKey, ctrlKey }: MouseEvent | KeyboardEvent,\n isMac: boolean = isMacLike()\n): boolean {\n return isMac ? metaKey && !ctrlKey : ctrlKey && !metaKey;\n}\n\nexport function isMoveCursorShortcut(\n e: KeyboardEvent\n):\n | 'up'\n | 'down'\n | 'left'\n | 'right'\n | 'start'\n | 'textStart'\n | 'end'\n | undefined {\n // emacs key bindings\n if (isMacLike() && e.ctrlKey && !e.altKey && !e.metaKey) {\n switch (e.key) {\n case 'a':\n return 'start';\n case 'e':\n return 'end';\n case 'p':\n return 'up';\n case 'n':\n return 'down';\n case 'f':\n return 'right';\n case 'b':\n return 'left';\n }\n }\n\n if (!e.altKey && !e.ctrlKey && !e.metaKey) {\n if (e.key === 'ArrowUp') {\n return 'up';\n } else if (e.key === 'ArrowDown') {\n return 'down';\n } else if (e.key === 'ArrowLeft') {\n return 'left';\n } else if (e.key === 'ArrowRight') {\n return 'right';\n }\n }\n\n if (isPrimaryModifier(e)) {\n if (e.key === 'ArrowLeft') {\n return 'textStart';\n } else if (e.key === 'ArrowRight') {\n return 'end';\n }\n }\n\n return undefined;\n}\n\nfunction getPlatform(): string {\n const navigator = globalThis.navigator as Navigator & {\n userAgentData?: { platform?: string };\n };\n return navigator?.platform ?? navigator?.userAgentData?.platform ?? 'unknown';\n}\n"],"mappings":";AAAA,IAAI,aAAkC,KAAA;AACtC,IAAI,WAAgC,KAAA;AACpC,IAAI,YAAiC,KAAA;;;;;;;AAQrC,SAAgB,iCAAuC;CACrD,aAAa,KAAA;CACb,WAAW,KAAA;CACX,YAAY,KAAA;AACd;AAEA,SAAgB,YAAqB;CACnC,OAAQ,eAAe,mCAAmC,KACxD,YAAY,CACd;AACF;AAEA,SAAgB,UAAmB;CACjC,OAAQ,aAAa,SAAS,KAAK,YAAY,CAAC;AAClD;AAEA,SAAgB,WAAoB;CAClC,OAAQ,cACL,YAAY,UAAU,sBAAuB,OAAe,UAC7D,iCAAiC,KAAK,UAAU,SAAS;AAC7D;AAEA,SAAgB,kBACd,EAAE,SAAS,WACX,QAAiB,UAAU,GAClB;CACT,OAAO,QAAQ,WAAW,CAAC,UAAU,WAAW,CAAC;AACnD;AAEA,SAAgB,qBACd,GASY;CAEZ,IAAI,UAAU,KAAK,EAAE,WAAW,CAAC,EAAE,UAAU,CAAC,EAAE,SAC9C,QAAQ,EAAE,KAAV;EACE,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;CACX;CAGF,IAAI,CAAC,EAAE,UAAU,CAAC,EAAE,WAAW,CAAC,EAAE;MAC5B,EAAE,QAAQ,WACZ,OAAO;OACF,IAAI,EAAE,QAAQ,aACnB,OAAO;OACF,IAAI,EAAE,QAAQ,aACnB,OAAO;OACF,IAAI,EAAE,QAAQ,cACnB,OAAO;CAAA;CAIX,IAAI,kBAAkB,CAAC;MACjB,EAAE,QAAQ,aACZ,OAAO;OACF,IAAI,EAAE,QAAQ,cACnB,OAAO;CAAA;AAKb;AAEA,SAAS,cAAsB;CAC7B,MAAM,YAAY,WAAW;CAG7B,OAAO,WAAW,YAAY,WAAW,eAAe,YAAY;AACtE"}
1
+ {"version":3,"file":"platform.js","names":[],"sources":["../../src/editor/platform.ts"],"sourcesContent":["let _isMacLike: boolean | undefined = undefined;\nlet _isLinux: boolean | undefined = undefined;\nlet _isSafari: boolean | undefined = undefined;\n\n/**\n * Clears the cached platform/browser detection. Detection is memoized on first\n * call, so a test process that swaps `navigator` (e.g. to exercise Linux or\n * Safari behavior) must reset it; otherwise the value cached by an earlier test\n * leaks across tests and no longer matches the active navigator.\n */\nexport function resetPlatformDetectionForTests(): void {\n _isMacLike = undefined;\n _isLinux = undefined;\n _isSafari = undefined;\n}\n\nexport function isMacLike(): boolean {\n return (_isMacLike ??= /macOS|MacIntel|iPhone|iPad|iPod/i.test(\n getPlatform()\n ));\n}\n\nexport function isLinux(): boolean {\n return (_isLinux ??= /Linux/i.test(getPlatform()));\n}\n\nexport function isSafari(): boolean {\n return (_isSafari ??=\n ('safari' in window && 'pushNotification' in (window as any).safari) ||\n /^((?!chrome|android).)*safari/i.test(navigator.userAgent));\n}\n\nexport function isPrimaryModifier(\n { metaKey, ctrlKey }: MouseEvent | KeyboardEvent,\n isMac: boolean = isMacLike()\n): boolean {\n return isMac ? metaKey && !ctrlKey : ctrlKey && !metaKey;\n}\n\nexport function isMoveCursorShortcut(\n e: KeyboardEvent\n):\n | 'up'\n | 'down'\n | 'left'\n | 'right'\n | 'start'\n | 'textStart'\n | 'end'\n | undefined {\n // emacs key bindings\n if (isMacLike() && e.ctrlKey && !e.altKey && !e.metaKey) {\n switch (e.key) {\n case 'a':\n return 'start';\n case 'e':\n return 'end';\n case 'p':\n return 'up';\n case 'n':\n return 'down';\n case 'f':\n return 'right';\n case 'b':\n return 'left';\n }\n }\n\n if (!e.altKey && !e.ctrlKey && !e.metaKey) {\n if (e.key === 'ArrowUp') {\n return 'up';\n } else if (e.key === 'ArrowDown') {\n return 'down';\n } else if (e.key === 'ArrowLeft') {\n return 'left';\n } else if (e.key === 'ArrowRight') {\n return 'right';\n } else if (e.key === 'Home') {\n return 'start';\n } else if (e.key === 'End') {\n return 'end';\n }\n }\n\n if (isPrimaryModifier(e)) {\n if (e.key === 'ArrowLeft') {\n return 'textStart';\n } else if (e.key === 'ArrowRight') {\n return 'end';\n }\n }\n\n return undefined;\n}\n\nfunction getPlatform(): string {\n const navigator = globalThis.navigator as Navigator & {\n userAgentData?: { platform?: string };\n };\n return navigator?.platform ?? navigator?.userAgentData?.platform ?? 'unknown';\n}\n"],"mappings":";AAAA,IAAI,aAAkC,KAAA;AACtC,IAAI,WAAgC,KAAA;AACpC,IAAI,YAAiC,KAAA;;;;;;;AAQrC,SAAgB,iCAAuC;CACrD,aAAa,KAAA;CACb,WAAW,KAAA;CACX,YAAY,KAAA;AACd;AAEA,SAAgB,YAAqB;CACnC,OAAQ,eAAe,mCAAmC,KACxD,YAAY,CACd;AACF;AAEA,SAAgB,UAAmB;CACjC,OAAQ,aAAa,SAAS,KAAK,YAAY,CAAC;AAClD;AAEA,SAAgB,WAAoB;CAClC,OAAQ,cACL,YAAY,UAAU,sBAAuB,OAAe,UAC7D,iCAAiC,KAAK,UAAU,SAAS;AAC7D;AAEA,SAAgB,kBACd,EAAE,SAAS,WACX,QAAiB,UAAU,GAClB;CACT,OAAO,QAAQ,WAAW,CAAC,UAAU,WAAW,CAAC;AACnD;AAEA,SAAgB,qBACd,GASY;CAEZ,IAAI,UAAU,KAAK,EAAE,WAAW,CAAC,EAAE,UAAU,CAAC,EAAE,SAC9C,QAAQ,EAAE,KAAV;EACE,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;EACT,KAAK,KACH,OAAO;CACX;CAGF,IAAI,CAAC,EAAE,UAAU,CAAC,EAAE,WAAW,CAAC,EAAE;MAC5B,EAAE,QAAQ,WACZ,OAAO;OACF,IAAI,EAAE,QAAQ,aACnB,OAAO;OACF,IAAI,EAAE,QAAQ,aACnB,OAAO;OACF,IAAI,EAAE,QAAQ,cACnB,OAAO;OACF,IAAI,EAAE,QAAQ,QACnB,OAAO;OACF,IAAI,EAAE,QAAQ,OACnB,OAAO;CAAA;CAIX,IAAI,kBAAkB,CAAC;MACjB,EAAE,QAAQ,aACZ,OAAO;OACF,IAAI,EAAE,QAAQ,cACnB,OAAO;CAAA;AAKb;AAEA,SAAS,cAAsB;CAC7B,MAAM,YAAY,WAAW;CAG7B,OAAO,WAAW,YAAY,WAAW,eAAe,YAAY;AACtE"}