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

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 (241) hide show
  1. package/README.md +9 -0
  2. package/dist/components/CodeView.d.ts +12 -13
  3. package/dist/components/CodeView.d.ts.map +1 -1
  4. package/dist/components/CodeView.js +50 -58
  5. package/dist/components/CodeView.js.map +1 -1
  6. package/dist/components/File.d.ts +10 -3
  7. package/dist/components/File.d.ts.map +1 -1
  8. package/dist/components/File.js +41 -10
  9. package/dist/components/File.js.map +1 -1
  10. package/dist/components/FileDiff.d.ts +37 -6
  11. package/dist/components/FileDiff.d.ts.map +1 -1
  12. package/dist/components/FileDiff.js +202 -19
  13. package/dist/components/FileDiff.js.map +1 -1
  14. package/dist/components/UnresolvedFile.d.ts.map +1 -1
  15. package/dist/components/UnresolvedFile.js +7 -2
  16. package/dist/components/UnresolvedFile.js.map +1 -1
  17. package/dist/components/VirtualizedFile.d.ts +2 -1
  18. package/dist/components/VirtualizedFile.d.ts.map +1 -1
  19. package/dist/components/VirtualizedFile.js +9 -5
  20. package/dist/components/VirtualizedFile.js.map +1 -1
  21. package/dist/components/VirtualizedFileDiff.d.ts +14 -0
  22. package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
  23. package/dist/components/VirtualizedFileDiff.js +61 -3
  24. package/dist/components/VirtualizedFileDiff.js.map +1 -1
  25. package/dist/components/Virtualizer.d.ts +14 -2
  26. package/dist/components/Virtualizer.d.ts.map +1 -1
  27. package/dist/components/Virtualizer.js +27 -2
  28. package/dist/components/Virtualizer.js.map +1 -1
  29. package/dist/edit/index.d.ts +5 -0
  30. package/dist/edit/index.js +3 -0
  31. package/dist/editor/command.d.ts +1 -1
  32. package/dist/editor/command.d.ts.map +1 -1
  33. package/dist/editor/command.js +11 -0
  34. package/dist/editor/command.js.map +1 -1
  35. package/dist/editor/editStack.d.ts +6 -2
  36. package/dist/editor/editStack.d.ts.map +1 -1
  37. package/dist/editor/editStack.js +53 -12
  38. package/dist/editor/editStack.js.map +1 -1
  39. package/dist/editor/editor.d.ts +51 -23
  40. package/dist/editor/editor.d.ts.map +1 -1
  41. package/dist/editor/editor.js +977 -241
  42. package/dist/editor/editor.js.map +1 -1
  43. package/dist/editor/editor2.js +1 -1
  44. package/dist/editor/languages.d.ts +26 -0
  45. package/dist/editor/languages.d.ts.map +1 -0
  46. package/dist/editor/languages.js +363 -0
  47. package/dist/editor/languages.js.map +1 -0
  48. package/dist/editor/lineAnnotations.d.ts.map +1 -1
  49. package/dist/editor/lineAnnotations.js +25 -26
  50. package/dist/editor/lineAnnotations.js.map +1 -1
  51. package/dist/editor/marker.d.ts +5 -3
  52. package/dist/editor/marker.d.ts.map +1 -1
  53. package/dist/editor/marker.js +111 -91
  54. package/dist/editor/marker.js.map +1 -1
  55. package/dist/editor/matchBrackets.d.ts +3 -2
  56. package/dist/editor/matchBrackets.d.ts.map +1 -1
  57. package/dist/editor/matchBrackets.js +2 -10
  58. package/dist/editor/matchBrackets.js.map +1 -1
  59. package/dist/editor/pieceTable.d.ts +2 -1
  60. package/dist/editor/pieceTable.d.ts.map +1 -1
  61. package/dist/editor/pieceTable.js +103 -39
  62. package/dist/editor/pieceTable.js.map +1 -1
  63. package/dist/editor/platform.js +2 -0
  64. package/dist/editor/platform.js.map +1 -1
  65. package/dist/editor/popover.d.ts +25 -5
  66. package/dist/editor/popover.d.ts.map +1 -1
  67. package/dist/editor/popover.js +60 -14
  68. package/dist/editor/popover.js.map +1 -1
  69. package/dist/editor/searchPanel.d.ts.map +1 -1
  70. package/dist/editor/searchPanel.js +9 -2
  71. package/dist/editor/searchPanel.js.map +1 -1
  72. package/dist/editor/selection.d.ts +31 -11
  73. package/dist/editor/selection.d.ts.map +1 -1
  74. package/dist/editor/selection.js +198 -70
  75. package/dist/editor/selection.js.map +1 -1
  76. package/dist/editor/selectionAction.d.ts +4 -3
  77. package/dist/editor/selectionAction.d.ts.map +1 -1
  78. package/dist/editor/selectionAction.js +9 -5
  79. package/dist/editor/selectionAction.js.map +1 -1
  80. package/dist/editor/stateStorage.d.ts +15 -0
  81. package/dist/editor/stateStorage.d.ts.map +1 -0
  82. package/dist/editor/stateStorage.js +65 -0
  83. package/dist/editor/stateStorage.js.map +1 -0
  84. package/dist/editor/textDocument.d.ts +12 -77
  85. package/dist/editor/textDocument.d.ts.map +1 -1
  86. package/dist/editor/textDocument.js +76 -34
  87. package/dist/editor/textDocument.js.map +1 -1
  88. package/dist/editor/textMeasure.d.ts +7 -12
  89. package/dist/editor/textMeasure.d.ts.map +1 -1
  90. package/dist/editor/textMeasure.js +40 -32
  91. package/dist/editor/textMeasure.js.map +1 -1
  92. package/dist/editor/{tokenzier.d.ts → tokenizer.d.ts} +3 -3
  93. package/dist/editor/tokenizer.d.ts.map +1 -0
  94. package/dist/editor/{tokenzier.js → tokenizer.js} +170 -50
  95. package/dist/editor/tokenizer.js.map +1 -0
  96. package/dist/editor/utils.js +1 -1
  97. package/dist/editor/utils.js.map +1 -1
  98. package/dist/index.d.ts +4 -3
  99. package/dist/index.js +6 -5
  100. package/dist/managers/InteractionManager.d.ts +18 -7
  101. package/dist/managers/InteractionManager.d.ts.map +1 -1
  102. package/dist/managers/InteractionManager.js +154 -99
  103. package/dist/managers/InteractionManager.js.map +1 -1
  104. package/dist/managers/UniversalRenderingManager.d.ts.map +1 -1
  105. package/dist/managers/UniversalRenderingManager.js +1 -2
  106. package/dist/managers/UniversalRenderingManager.js.map +1 -1
  107. package/dist/react/CodeView.d.ts +13 -13
  108. package/dist/react/CodeView.d.ts.map +1 -1
  109. package/dist/react/CodeView.js +37 -28
  110. package/dist/react/CodeView.js.map +1 -1
  111. package/dist/react/EditContext.d.ts +20 -0
  112. package/dist/react/EditContext.d.ts.map +1 -0
  113. package/dist/react/EditContext.js +20 -0
  114. package/dist/react/EditContext.js.map +1 -0
  115. package/dist/react/File.d.ts +2 -1
  116. package/dist/react/File.d.ts.map +1 -1
  117. package/dist/react/File.js +3 -2
  118. package/dist/react/File.js.map +1 -1
  119. package/dist/react/FileDiff.d.ts +2 -2
  120. package/dist/react/FileDiff.d.ts.map +1 -1
  121. package/dist/react/FileDiff.js +3 -2
  122. package/dist/react/FileDiff.js.map +1 -1
  123. package/dist/react/MultiFileDiff.d.ts +2 -2
  124. package/dist/react/MultiFileDiff.d.ts.map +1 -1
  125. package/dist/react/MultiFileDiff.js +3 -2
  126. package/dist/react/MultiFileDiff.js.map +1 -1
  127. package/dist/react/PatchDiff.d.ts +2 -2
  128. package/dist/react/PatchDiff.d.ts.map +1 -1
  129. package/dist/react/PatchDiff.js +3 -2
  130. package/dist/react/PatchDiff.js.map +1 -1
  131. package/dist/react/UnresolvedFile.d.ts +1 -1
  132. package/dist/react/UnresolvedFile.js.map +1 -1
  133. package/dist/react/index.d.ts +4 -4
  134. package/dist/react/index.js +3 -3
  135. package/dist/react/types.d.ts +9 -1
  136. package/dist/react/types.d.ts.map +1 -1
  137. package/dist/react/utils/useFileDiffInstance.d.ts +5 -3
  138. package/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
  139. package/dist/react/utils/useFileDiffInstance.js +24 -34
  140. package/dist/react/utils/useFileDiffInstance.js.map +1 -1
  141. package/dist/react/utils/useFileInstance.d.ts +5 -3
  142. package/dist/react/utils/useFileInstance.d.ts.map +1 -1
  143. package/dist/react/utils/useFileInstance.js +23 -33
  144. package/dist/react/utils/useFileInstance.js.map +1 -1
  145. package/dist/react/utils/useUnresolvedFileInstance.js +4 -4
  146. package/dist/react/utils/useUnresolvedFileInstance.js.map +1 -1
  147. package/dist/renderers/DiffHunksRenderer.d.ts +41 -1
  148. package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
  149. package/dist/renderers/DiffHunksRenderer.js +167 -29
  150. package/dist/renderers/DiffHunksRenderer.js.map +1 -1
  151. package/dist/renderers/FileRenderer.d.ts +20 -1
  152. package/dist/renderers/FileRenderer.d.ts.map +1 -1
  153. package/dist/renderers/FileRenderer.js +119 -29
  154. package/dist/renderers/FileRenderer.js.map +1 -1
  155. package/dist/ssr/index.d.ts +2 -2
  156. package/dist/ssr/preloadDiffs.d.ts.map +1 -1
  157. package/dist/ssr/preloadDiffs.js +3 -1
  158. package/dist/ssr/preloadDiffs.js.map +1 -1
  159. package/dist/ssr/preloadFile.d.ts.map +1 -1
  160. package/dist/ssr/preloadFile.js +2 -0
  161. package/dist/ssr/preloadFile.js.map +1 -1
  162. package/dist/style.js +1 -1
  163. package/dist/types.d.ts +192 -42
  164. package/dist/types.d.ts.map +1 -1
  165. package/dist/utils/annotationHelpers.d.ts +20 -0
  166. package/dist/utils/annotationHelpers.d.ts.map +1 -0
  167. package/dist/utils/annotationHelpers.js +29 -0
  168. package/dist/utils/annotationHelpers.js.map +1 -0
  169. package/dist/utils/computeFileOffsets.d.ts.map +1 -1
  170. package/dist/utils/computeFileOffsets.js +34 -5
  171. package/dist/utils/computeFileOffsets.js.map +1 -1
  172. package/dist/utils/editSessionHunks.d.ts +66 -0
  173. package/dist/utils/editSessionHunks.d.ts.map +1 -0
  174. package/dist/utils/editSessionHunks.js +437 -0
  175. package/dist/utils/editSessionHunks.js.map +1 -0
  176. package/dist/utils/getDiffHunksRendererOptions.d.ts.map +1 -1
  177. package/dist/utils/getDiffHunksRendererOptions.js +2 -1
  178. package/dist/utils/getDiffHunksRendererOptions.js.map +1 -1
  179. package/dist/utils/getFileRendererOptions.d.ts.map +1 -1
  180. package/dist/utils/getFileRendererOptions.js +2 -1
  181. package/dist/utils/getFileRendererOptions.js.map +1 -1
  182. package/dist/utils/getHunkSideBoundaries.d.ts +7 -0
  183. package/dist/utils/getHunkSideBoundaries.d.ts.map +1 -0
  184. package/dist/utils/getHunkSideBoundaries.js +12 -0
  185. package/dist/utils/getHunkSideBoundaries.js.map +1 -0
  186. package/dist/utils/getTotalLineCountFromHunks.d.ts.map +1 -1
  187. package/dist/utils/getTotalLineCountFromHunks.js +2 -1
  188. package/dist/utils/getTotalLineCountFromHunks.js.map +1 -1
  189. package/dist/utils/hydratePartialDiff.d.ts.map +1 -1
  190. package/dist/utils/hydratePartialDiff.js +4 -3
  191. package/dist/utils/hydratePartialDiff.js.map +1 -1
  192. package/dist/utils/isGutterUtilityPath.d.ts +5 -0
  193. package/dist/utils/isGutterUtilityPath.d.ts.map +1 -0
  194. package/dist/utils/isGutterUtilityPath.js +12 -0
  195. package/dist/utils/isGutterUtilityPath.js.map +1 -0
  196. package/dist/utils/iterateOverDiff.d.ts.map +1 -1
  197. package/dist/utils/iterateOverDiff.js +17 -12
  198. package/dist/utils/iterateOverDiff.js.map +1 -1
  199. package/dist/utils/parseMergeConflictDiffFromFile.d.ts.map +1 -1
  200. package/dist/utils/parseMergeConflictDiffFromFile.js +9 -6
  201. package/dist/utils/parseMergeConflictDiffFromFile.js.map +1 -1
  202. package/dist/utils/parsePatchFiles.d.ts.map +1 -1
  203. package/dist/utils/parsePatchFiles.js +6 -3
  204. package/dist/utils/parsePatchFiles.js.map +1 -1
  205. package/dist/utils/realignChangeContent.d.ts +33 -0
  206. package/dist/utils/realignChangeContent.d.ts.map +1 -0
  207. package/dist/utils/realignChangeContent.js +148 -0
  208. package/dist/utils/realignChangeContent.js.map +1 -0
  209. package/dist/utils/renderDiffWithHighlighter.js +1 -1
  210. package/dist/utils/resolveRegion.d.ts.map +1 -1
  211. package/dist/utils/resolveRegion.js +18 -2
  212. package/dist/utils/resolveRegion.js.map +1 -1
  213. package/dist/utils/shouldUseTokenTransformer.d.ts +7 -3
  214. package/dist/utils/shouldUseTokenTransformer.d.ts.map +1 -1
  215. package/dist/utils/shouldUseTokenTransformer.js +1 -1
  216. package/dist/utils/shouldUseTokenTransformer.js.map +1 -1
  217. package/dist/utils/updateDiffHunks.d.ts +8 -2
  218. package/dist/utils/updateDiffHunks.d.ts.map +1 -1
  219. package/dist/utils/updateDiffHunks.js +9 -7
  220. package/dist/utils/updateDiffHunks.js.map +1 -1
  221. package/dist/utils/virtualDiffLayout.d.ts +42 -2
  222. package/dist/utils/virtualDiffLayout.d.ts.map +1 -1
  223. package/dist/utils/virtualDiffLayout.js +101 -7
  224. package/dist/utils/virtualDiffLayout.js.map +1 -1
  225. package/dist/worker/WorkerPoolManager.d.ts +2 -1
  226. package/dist/worker/WorkerPoolManager.d.ts.map +1 -1
  227. package/dist/worker/WorkerPoolManager.js +3 -0
  228. package/dist/worker/WorkerPoolManager.js.map +1 -1
  229. package/dist/worker/worker-portable.js +60 -20
  230. package/dist/worker/worker-portable.js.map +1 -1
  231. package/dist/worker/worker.js +61 -19
  232. package/dist/worker/worker.js.map +1 -1
  233. package/package.json +10 -7
  234. package/dist/editor/index.d.ts +0 -3
  235. package/dist/editor/index.js +0 -3
  236. package/dist/editor/tokenzier.d.ts.map +0 -1
  237. package/dist/editor/tokenzier.js.map +0 -1
  238. package/dist/react/EditorContext.d.ts +0 -15
  239. package/dist/react/EditorContext.d.ts.map +0 -1
  240. package/dist/react/EditorContext.js +0 -23
  241. package/dist/react/EditorContext.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  import { getGraphemeSegmenter, h, round } from "./utils.js";
2
2
  //#region src/editor/textMeasure.ts
3
3
  const TEXT_WIDTH_CACHE_LIMIT = 4096;
4
+ const COMBINING_MARK_PATTERN = /\p{Mark}/u;
4
5
  var Metrics = class {
5
6
  #root;
6
7
  #canvasCtx;
@@ -40,29 +41,52 @@ var Metrics = class {
40
41
  }
41
42
  /**
42
43
  * Re-measure the '0' character width against the font that is loaded right
43
- * now, returning true when it changed.
44
+ * now, returning true when measurement-dependent UI should refresh.
44
45
  *
45
46
  * A custom web font can finish loading after the editor first renders.
46
47
  * Until then canvas measureText reports the fallback font's width, and
47
48
  * getComputedStyle returns the same font-family string before and after the
48
49
  * file arrives, so init()'s font guard never re-measures on its own. Call
49
50
  * this once fonts have settled (e.g. on document.fonts.ready) to replace a
50
- * width measured against the fallback font with the real glyph width. The
51
- * boolean return lets the caller skip re-rendering when nothing changed.
51
+ * width measured against the fallback font with the real glyph width. Font
52
+ * completion can also change DOM-measured emoji/ZWJ widths while leaving the
53
+ * ASCII width unchanged, and the computed font string stays the same in both
54
+ * cases. The boolean return lets the caller skip re-rendering when there is no
55
+ * measured state to refresh.
52
56
  */
53
57
  remeasureCharacterWidth() {
54
58
  if (this.#canvasCtx === void 0 || this.#font === void 0) return false;
55
59
  this.#canvasCtx.font = this.#font;
56
60
  const ch = this.canvasMeasureTextWidth("0");
57
- if (ch === this.ch) return false;
58
- this.ch = ch;
59
- return true;
61
+ const characterWidthChanged = ch !== this.ch;
62
+ if (characterWidthChanged) this.ch = ch;
63
+ const textWidthCacheCleared = this.#clearTextWidthCache();
64
+ return characterWidthChanged || textWidthCacheCleared;
60
65
  }
61
66
  /** measure the width of the text */
62
67
  measureTextWidth(text) {
63
- const textWithExpandedTabs = expandTabsToSpaces(text, this.tabSize);
64
- if (needsDomTextMeasurement(textWithExpandedTabs)) return this.domMeasureTextWidth(textWithExpandedTabs);
65
- return this.canvasMeasureTextWidth(textWithExpandedTabs);
68
+ if (!text.includes(" ")) return this.#measureTextWidthWithoutTabs(text);
69
+ if (this.#canvasCtx === void 0) throw new Error("Metrics not initialized");
70
+ const asciiColumns = getExpandedAsciiTextColumns(text, this.tabSize);
71
+ if (asciiColumns !== -1) return asciiColumns * this.ch;
72
+ let width = 0;
73
+ let runStart = 0;
74
+ const tabStopWidth = this.tabSize * this.ch;
75
+ for (let i = 0; i < text.length; i++) {
76
+ if (text.charCodeAt(i) !== 9) continue;
77
+ if (i > runStart) width += this.#measureTextWidthWithoutTabs(text.slice(runStart, i));
78
+ if (tabStopWidth > 0) {
79
+ const remainder = width % tabStopWidth;
80
+ width += remainder === 0 ? tabStopWidth : tabStopWidth - remainder;
81
+ }
82
+ runStart = i + 1;
83
+ }
84
+ if (runStart < text.length) width += this.#measureTextWidthWithoutTabs(text.slice(runStart));
85
+ return round(width);
86
+ }
87
+ #measureTextWidthWithoutTabs(text) {
88
+ if (needsDomTextMeasurement(text)) return this.domMeasureTextWidth(text);
89
+ return this.canvasMeasureTextWidth(text);
66
90
  }
67
91
  /** measure the width of the text using the canvas measureText API */
68
92
  canvasMeasureTextWidth(text) {
@@ -110,14 +134,19 @@ var Metrics = class {
110
134
  * init(), e.g. on a layout reflow, so stale widths are not reused
111
135
  */
112
136
  clearTextWidthCache() {
137
+ this.#clearTextWidthCache();
138
+ }
139
+ #clearTextWidthCache() {
140
+ const hadCachedTextWidths = this.#textWidthCache.size > 0;
113
141
  this.#textWidthCache.clear();
142
+ return hadCachedTextWidths;
114
143
  }
115
144
  };
116
145
  /** Check if the text needs DOM text measurement. */
117
146
  function needsDomTextMeasurement(text) {
118
147
  for (let i = 0; i < text.length; i++) {
119
148
  const code = text.charCodeAt(i);
120
- if (code >= 55296 && code <= 57343 || code === 8205 || code === 65038 || code === 65039) return true;
149
+ if (code >= 55296 && code <= 57343 || code === 8205 || code === 65038 || code === 65039 || code > 127 && COMBINING_MARK_PATTERN.test(text.charAt(i))) return true;
121
150
  }
122
151
  return false;
123
152
  }
@@ -161,27 +190,6 @@ function getUnicodeMeasurementOffsets(text) {
161
190
  return offsets;
162
191
  }
163
192
  /**
164
- * Expand tab characters to spaces using fixed tab stops: each tab advances to
165
- * the next multiple of tabSize from its running column, matching how the
166
- * rendered text expands tabs via CSS `tab-size`. Expanding every tab to a flat
167
- * tabSize would mis-measure tabs that follow other characters on the same line
168
- * (e.g. an alignment tab in `foo\tbar`).
169
- */
170
- function expandTabsToSpaces(text, tabSize) {
171
- if (!text.includes(" ")) return text;
172
- let result = "";
173
- let column = 0;
174
- for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 9) {
175
- const advance = tabSize - column % tabSize;
176
- result += " ".repeat(advance);
177
- column += advance;
178
- } else {
179
- result += text[i];
180
- column += 1;
181
- }
182
- return result;
183
- }
184
- /**
185
193
  * Count the rendered columns of ASCII text, advancing each tab to the next
186
194
  * fixed tab stop (a multiple of tabSize) to match CSS `tab-size`. Returns -1
187
195
  * for non-ASCII text, which must be measured glyph-by-glyph instead.
@@ -196,6 +204,6 @@ function getExpandedAsciiTextColumns(text, tabSize) {
196
204
  return columns;
197
205
  }
198
206
  //#endregion
199
- export { Metrics, expandTabsToSpaces, getExpandedAsciiTextColumns, getUnicodeMeasurementOffsets, needsDomTextMeasurement, snapTextOffsetToUnicodeBoundary };
207
+ export { Metrics, getExpandedAsciiTextColumns, getUnicodeMeasurementOffsets, needsDomTextMeasurement, snapTextOffsetToUnicodeBoundary };
200
208
 
201
209
  //# sourceMappingURL=textMeasure.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"textMeasure.js","names":["#root","#canvasCtx","#font","#textWidthCache"],"sources":["../../src/editor/textMeasure.ts"],"sourcesContent":["import { getGraphemeSegmenter, h, round } from './utils';\n\n// Upper bound on cached DOM text-width measurements. The cache only holds\n// non-ASCII runs (emoji, ZWJ sequences, variation selectors), so it stays\n// small for ordinary code, but capping it prevents unbounded growth on\n// emoji-heavy documents. Past the cap the oldest entry is evicted.\nconst TEXT_WIDTH_CACHE_LIMIT = 4096;\n\nexport class Metrics {\n #root?: HTMLElement;\n #canvasCtx?: CanvasRenderingContext2D;\n #font?: string;\n\n // Memoizes domMeasureTextWidth() results\n #textWidthCache = new Map<string, number>();\n\n /** Width of the '0' character. */\n ch: number = -1;\n /** Size of a tab(\\t) character. */\n tabSize: number = 2;\n /** Height of the code line. */\n lineHeight: number = 20;\n /** Padding top of the root element. */\n paddingTop: number = 0;\n\n /** initialize the metrics */\n init(root: HTMLElement): void {\n if (\n this.#root === root &&\n this.#canvasCtx !== undefined &&\n this.ch !== -1\n ) {\n // already initialized\n return;\n }\n\n this.#root = root;\n this.#canvasCtx ??=\n document.createElement('canvas').getContext('2d') ?? undefined;\n if (this.#canvasCtx === undefined) {\n throw new Error('Could not get canvas context');\n }\n\n const parent = root.parentElement;\n if (parent !== null) {\n const { paddingTop } = getComputedStyle(parent);\n if (paddingTop.endsWith('px')) {\n this.paddingTop = parseFloat(paddingTop.slice(0, -2));\n }\n }\n\n const { fontSize, fontFamily, tabSize, lineHeight } =\n getComputedStyle(root);\n if (lineHeight.endsWith('px')) {\n this.lineHeight = parseFloat(lineHeight.slice(0, -2));\n } else if (fontSize.endsWith('px')) {\n this.lineHeight = round(\n parseFloat(fontSize.slice(0, -2)) * parseFloat(lineHeight)\n );\n }\n const font = fontSize + ' ' + fontFamily;\n if (this.#font !== font || this.ch === -1) {\n this.#font = font;\n this.#canvasCtx.font = font;\n this.ch = this.canvasMeasureTextWidth('0');\n // Cached DOM widths were measured against the previous font.\n this.clearTextWidthCache();\n }\n const nextTabSize = parseInt(tabSize, 10);\n if (!Number.isNaN(nextTabSize)) {\n this.tabSize = nextTabSize;\n }\n }\n\n /**\n * Re-measure the '0' character width against the font that is loaded right\n * now, returning true when it changed.\n *\n * A custom web font can finish loading after the editor first renders.\n * Until then canvas measureText reports the fallback font's width, and\n * getComputedStyle returns the same font-family string before and after the\n * file arrives, so init()'s font guard never re-measures on its own. Call\n * this once fonts have settled (e.g. on document.fonts.ready) to replace a\n * width measured against the fallback font with the real glyph width. The\n * boolean return lets the caller skip re-rendering when nothing changed.\n */\n remeasureCharacterWidth(): boolean {\n if (this.#canvasCtx === undefined || this.#font === undefined) {\n return false;\n }\n this.#canvasCtx.font = this.#font;\n const ch = this.canvasMeasureTextWidth('0');\n if (ch === this.ch) {\n return false;\n }\n this.ch = ch;\n return true;\n }\n\n /** measure the width of the text */\n measureTextWidth(text: string): number {\n const textWithExpandedTabs = expandTabsToSpaces(text, this.tabSize);\n if (needsDomTextMeasurement(textWithExpandedTabs)) {\n return this.domMeasureTextWidth(textWithExpandedTabs);\n }\n return this.canvasMeasureTextWidth(textWithExpandedTabs);\n }\n\n /** measure the width of the text using the canvas measureText API */\n canvasMeasureTextWidth(text: string): number {\n if (this.#canvasCtx === undefined) {\n throw new Error('Metrics not initialized');\n }\n return round(this.#canvasCtx.measureText(text).width);\n }\n\n /**\n * measure the width of the text using the DOM\n * this is slow because it cause a reflow, use it for non-ascii text;\n * results are memoized per text so repeated measurements skip the reflow\n */\n domMeasureTextWidth(text: string): number {\n if (this.#root === undefined) {\n throw new Error('Metrics not initialized');\n }\n const cacheKey = text + '|' + this.#font;\n const cached = this.#textWidthCache.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n const measureEl = h(\n 'span',\n {\n style: {\n position: 'absolute',\n top: '0',\n left: '0',\n visibility: 'hidden',\n pointerEvents: 'none',\n whiteSpace: 'pre',\n font: 'inherit',\n },\n textContent: text,\n },\n this.#root\n );\n let width: number;\n try {\n // round() to match canvasMeasureTextWidth and ch; otherwise the DOM path\n // returns raw sub-pixel widths and caret/selection offsets drift between\n // ASCII and non-ASCII runs on the same line.\n width = round(measureEl.getBoundingClientRect().width);\n } finally {\n measureEl.remove();\n }\n if (this.#textWidthCache.size >= TEXT_WIDTH_CACHE_LIMIT) {\n const oldestKey = this.#textWidthCache.keys().next().value;\n if (oldestKey !== undefined) {\n this.#textWidthCache.delete(oldestKey);\n }\n }\n this.#textWidthCache.set(cacheKey, width);\n return width;\n }\n\n /**\n * discard memoized DOM text widths\n * call this when the inherited font may have changed without re-running\n * init(), e.g. on a layout reflow, so stale widths are not reused\n */\n clearTextWidthCache(): void {\n this.#textWidthCache.clear();\n }\n}\n\n/** Check if the text needs DOM text measurement. */\nexport function needsDomTextMeasurement(text: string): boolean {\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n if (\n (code >= 0xd800 && code <= 0xdfff) ||\n code === 0x200d ||\n code === 0xfe0e ||\n code === 0xfe0f\n ) {\n return true;\n }\n }\n return false;\n}\n\n/** snap the text offset to the Unicode boundary */\nexport function snapTextOffsetToUnicodeBoundary(\n text: string,\n offset: number\n): number {\n const boundedOffset = Math.max(0, Math.min(offset, text.length));\n if (\n boundedOffset === 0 ||\n boundedOffset === text.length ||\n !needsDomTextMeasurement(text)\n ) {\n return boundedOffset;\n }\n // Avoid measuring a caret position inside one visual emoji/grapheme.\n // Browser caret movement can report offsets around UTF-16 surrogate\n // pairs and emoji joiners; measuring a partial sequence gives a\n // replacement-glyph width.\n const segmenter = getGraphemeSegmenter();\n if (segmenter !== undefined) {\n for (const segment of segmenter.segment(text)) {\n const segmentStart = segment.index;\n const segmentEnd = segmentStart + segment.segment.length;\n if (boundedOffset > segmentStart && boundedOffset < segmentEnd) {\n return segmentEnd;\n }\n if (boundedOffset <= segmentStart) {\n break;\n }\n }\n return boundedOffset;\n }\n // Degraded path for engines lacking Intl.Segmenter: snap out of a\n // surrogate pair by stepping over code points.\n let segmentStart = 0;\n for (const codePoint of text) {\n const segmentEnd = segmentStart + codePoint.length;\n if (boundedOffset > segmentStart && boundedOffset < segmentEnd) {\n return segmentEnd;\n }\n if (boundedOffset <= segmentStart) {\n break;\n }\n segmentStart = segmentEnd;\n }\n return boundedOffset;\n}\n\n/** get the offsets of the Unicode grapheme clusters in the text */\nexport function getUnicodeMeasurementOffsets(\n text: string\n): number[] | undefined {\n if (!needsDomTextMeasurement(text)) {\n return undefined;\n }\n const offsets = [0];\n const segmenter = getGraphemeSegmenter();\n if (segmenter !== undefined) {\n for (const segment of segmenter.segment(text)) {\n offsets.push(segment.index + segment.segment.length);\n }\n return offsets;\n }\n // Degraded path for engines lacking Intl.Segmenter: step by code point.\n let offset = 0;\n for (const codePoint of text) {\n offset += codePoint.length;\n offsets.push(offset);\n }\n return offsets;\n}\n\n/**\n * Expand tab characters to spaces using fixed tab stops: each tab advances to\n * the next multiple of tabSize from its running column, matching how the\n * rendered text expands tabs via CSS `tab-size`. Expanding every tab to a flat\n * tabSize would mis-measure tabs that follow other characters on the same line\n * (e.g. an alignment tab in `foo\\tbar`).\n */\nexport function expandTabsToSpaces(text: string, tabSize: number): string {\n if (!text.includes('\\t')) {\n return text;\n }\n let result = '';\n let column = 0;\n for (let i = 0; i < text.length; i++) {\n if (text.charCodeAt(i) === /* '\\t' */ 9) {\n const advance = tabSize - (column % tabSize);\n result += ' '.repeat(advance);\n column += advance;\n } else {\n result += text[i];\n column += 1;\n }\n }\n return result;\n}\n\n/**\n * Count the rendered columns of ASCII text, advancing each tab to the next\n * fixed tab stop (a multiple of tabSize) to match CSS `tab-size`. Returns -1\n * for non-ASCII text, which must be measured glyph-by-glyph instead.\n */\nexport function getExpandedAsciiTextColumns(\n text: string,\n tabSize: number\n): number {\n let columns = 0;\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n if (code > 127) {\n return -1;\n }\n columns += code === /* '\\t' */ 9 ? tabSize - (columns % tabSize) : 1;\n }\n return columns;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"textMeasure.js","names":["#root","#canvasCtx","#font","#clearTextWidthCache","#measureTextWidthWithoutTabs","#textWidthCache"],"sources":["../../src/editor/textMeasure.ts"],"sourcesContent":["import { getGraphemeSegmenter, h, round } from './utils';\n\n// Upper bound on cached DOM text-width measurements. The cache only holds\n// non-ASCII runs (emoji, ZWJ sequences, variation selectors, combining marks),\n// so it stays small for ordinary code, but capping it prevents unbounded growth\n// on emoji-heavy documents. Past the cap the oldest entry is evicted.\nconst TEXT_WIDTH_CACHE_LIMIT = 4096;\n\nconst COMBINING_MARK_PATTERN = /\\p{Mark}/u;\n\nexport class Metrics {\n #root?: HTMLElement;\n #canvasCtx?: CanvasRenderingContext2D;\n #font?: string;\n\n // Memoizes domMeasureTextWidth() results\n #textWidthCache = new Map<string, number>();\n\n /** Width of the '0' character. */\n ch: number = -1;\n /** Size of a tab(\\t) character. */\n tabSize: number = 2;\n /** Height of the code line. */\n lineHeight: number = 20;\n /** Padding top of the root element. */\n paddingTop: number = 0;\n\n /** initialize the metrics */\n init(root: HTMLElement): void {\n if (\n this.#root === root &&\n this.#canvasCtx !== undefined &&\n this.ch !== -1\n ) {\n // already initialized\n return;\n }\n\n this.#root = root;\n this.#canvasCtx ??=\n document.createElement('canvas').getContext('2d') ?? undefined;\n if (this.#canvasCtx === undefined) {\n throw new Error('Could not get canvas context');\n }\n\n const parent = root.parentElement;\n if (parent !== null) {\n const { paddingTop } = getComputedStyle(parent);\n if (paddingTop.endsWith('px')) {\n this.paddingTop = parseFloat(paddingTop.slice(0, -2));\n }\n }\n\n const { fontSize, fontFamily, tabSize, lineHeight } =\n getComputedStyle(root);\n if (lineHeight.endsWith('px')) {\n this.lineHeight = parseFloat(lineHeight.slice(0, -2));\n } else if (fontSize.endsWith('px')) {\n this.lineHeight = round(\n parseFloat(fontSize.slice(0, -2)) * parseFloat(lineHeight)\n );\n }\n const font = fontSize + ' ' + fontFamily;\n if (this.#font !== font || this.ch === -1) {\n this.#font = font;\n this.#canvasCtx.font = font;\n this.ch = this.canvasMeasureTextWidth('0');\n // Cached DOM widths were measured against the previous font.\n this.clearTextWidthCache();\n }\n const nextTabSize = parseInt(tabSize, 10);\n if (!Number.isNaN(nextTabSize)) {\n this.tabSize = nextTabSize;\n }\n }\n\n /**\n * Re-measure the '0' character width against the font that is loaded right\n * now, returning true when measurement-dependent UI should refresh.\n *\n * A custom web font can finish loading after the editor first renders.\n * Until then canvas measureText reports the fallback font's width, and\n * getComputedStyle returns the same font-family string before and after the\n * file arrives, so init()'s font guard never re-measures on its own. Call\n * this once fonts have settled (e.g. on document.fonts.ready) to replace a\n * width measured against the fallback font with the real glyph width. Font\n * completion can also change DOM-measured emoji/ZWJ widths while leaving the\n * ASCII width unchanged, and the computed font string stays the same in both\n * cases. The boolean return lets the caller skip re-rendering when there is no\n * measured state to refresh.\n */\n remeasureCharacterWidth(): boolean {\n if (this.#canvasCtx === undefined || this.#font === undefined) {\n return false;\n }\n this.#canvasCtx.font = this.#font;\n const ch = this.canvasMeasureTextWidth('0');\n const characterWidthChanged = ch !== this.ch;\n if (characterWidthChanged) {\n this.ch = ch;\n }\n const textWidthCacheCleared = this.#clearTextWidthCache();\n return characterWidthChanged || textWidthCacheCleared;\n }\n\n /** measure the width of the text */\n measureTextWidth(text: string): number {\n if (!text.includes('\\t')) {\n return this.#measureTextWidthWithoutTabs(text);\n }\n if (this.#canvasCtx === undefined) {\n throw new Error('Metrics not initialized');\n }\n const asciiColumns = getExpandedAsciiTextColumns(text, this.tabSize);\n if (asciiColumns !== -1) {\n return asciiColumns * this.ch;\n }\n\n let width = 0;\n let runStart = 0;\n const tabStopWidth = this.tabSize * this.ch;\n for (let i = 0; i < text.length; i++) {\n if (text.charCodeAt(i) !== /* '\\t' */ 9) {\n continue;\n }\n\n if (i > runStart) {\n width += this.#measureTextWidthWithoutTabs(text.slice(runStart, i));\n }\n // CSS tab stops are pixel positions, so wide glyphs before a tab must\n // contribute their measured width rather than one logical column.\n if (tabStopWidth > 0) {\n const remainder = width % tabStopWidth;\n width += remainder === 0 ? tabStopWidth : tabStopWidth - remainder;\n }\n runStart = i + 1;\n }\n\n if (runStart < text.length) {\n width += this.#measureTextWidthWithoutTabs(text.slice(runStart));\n }\n return round(width);\n }\n\n #measureTextWidthWithoutTabs(text: string): number {\n if (needsDomTextMeasurement(text)) {\n return this.domMeasureTextWidth(text);\n }\n return this.canvasMeasureTextWidth(text);\n }\n\n /** measure the width of the text using the canvas measureText API */\n canvasMeasureTextWidth(text: string): number {\n if (this.#canvasCtx === undefined) {\n throw new Error('Metrics not initialized');\n }\n return round(this.#canvasCtx.measureText(text).width);\n }\n\n /**\n * measure the width of the text using the DOM\n * this is slow because it cause a reflow, use it for non-ascii text;\n * results are memoized per text so repeated measurements skip the reflow\n */\n domMeasureTextWidth(text: string): number {\n if (this.#root === undefined) {\n throw new Error('Metrics not initialized');\n }\n const cacheKey = text + '|' + this.#font;\n const cached = this.#textWidthCache.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n const measureEl = h(\n 'span',\n {\n style: {\n position: 'absolute',\n top: '0',\n left: '0',\n visibility: 'hidden',\n pointerEvents: 'none',\n whiteSpace: 'pre',\n font: 'inherit',\n },\n textContent: text,\n },\n this.#root\n );\n let width: number;\n try {\n // round() to match canvasMeasureTextWidth and ch; otherwise the DOM path\n // returns raw sub-pixel widths and caret/selection offsets drift between\n // ASCII and non-ASCII runs on the same line.\n width = round(measureEl.getBoundingClientRect().width);\n } finally {\n measureEl.remove();\n }\n if (this.#textWidthCache.size >= TEXT_WIDTH_CACHE_LIMIT) {\n const oldestKey = this.#textWidthCache.keys().next().value;\n if (oldestKey !== undefined) {\n this.#textWidthCache.delete(oldestKey);\n }\n }\n this.#textWidthCache.set(cacheKey, width);\n return width;\n }\n\n /**\n * discard memoized DOM text widths\n * call this when the inherited font may have changed without re-running\n * init(), e.g. on a layout reflow, so stale widths are not reused\n */\n clearTextWidthCache(): void {\n this.#clearTextWidthCache();\n }\n\n #clearTextWidthCache(): boolean {\n const hadCachedTextWidths = this.#textWidthCache.size > 0;\n this.#textWidthCache.clear();\n return hadCachedTextWidths;\n }\n}\n\n/** Check if the text needs DOM text measurement. */\nexport function needsDomTextMeasurement(text: string): boolean {\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n if (\n (code >= 0xd800 && code <= 0xdfff) ||\n code === 0x200d ||\n code === 0xfe0e ||\n code === 0xfe0f ||\n (code > 0x7f && COMBINING_MARK_PATTERN.test(text.charAt(i)))\n ) {\n return true;\n }\n }\n return false;\n}\n\n/** snap the text offset to the Unicode boundary */\nexport function snapTextOffsetToUnicodeBoundary(\n text: string,\n offset: number\n): number {\n const boundedOffset = Math.max(0, Math.min(offset, text.length));\n if (\n boundedOffset === 0 ||\n boundedOffset === text.length ||\n !needsDomTextMeasurement(text)\n ) {\n return boundedOffset;\n }\n // Avoid measuring a caret position inside one visual emoji/grapheme.\n // Browser caret movement can report offsets around UTF-16 surrogate\n // pairs and emoji joiners; measuring a partial sequence gives a\n // replacement-glyph width.\n const segmenter = getGraphemeSegmenter();\n if (segmenter !== undefined) {\n for (const segment of segmenter.segment(text)) {\n const segmentStart = segment.index;\n const segmentEnd = segmentStart + segment.segment.length;\n if (boundedOffset > segmentStart && boundedOffset < segmentEnd) {\n return segmentEnd;\n }\n if (boundedOffset <= segmentStart) {\n break;\n }\n }\n return boundedOffset;\n }\n // Degraded path for engines lacking Intl.Segmenter: snap out of a\n // surrogate pair by stepping over code points.\n let segmentStart = 0;\n for (const codePoint of text) {\n const segmentEnd = segmentStart + codePoint.length;\n if (boundedOffset > segmentStart && boundedOffset < segmentEnd) {\n return segmentEnd;\n }\n if (boundedOffset <= segmentStart) {\n break;\n }\n segmentStart = segmentEnd;\n }\n return boundedOffset;\n}\n\n/** get the offsets of the Unicode grapheme clusters in the text */\nexport function getUnicodeMeasurementOffsets(\n text: string\n): number[] | undefined {\n if (!needsDomTextMeasurement(text)) {\n return undefined;\n }\n const offsets = [0];\n const segmenter = getGraphemeSegmenter();\n if (segmenter !== undefined) {\n for (const segment of segmenter.segment(text)) {\n offsets.push(segment.index + segment.segment.length);\n }\n return offsets;\n }\n // Degraded path for engines lacking Intl.Segmenter: step by code point.\n let offset = 0;\n for (const codePoint of text) {\n offset += codePoint.length;\n offsets.push(offset);\n }\n return offsets;\n}\n\n/**\n * Count the rendered columns of ASCII text, advancing each tab to the next\n * fixed tab stop (a multiple of tabSize) to match CSS `tab-size`. Returns -1\n * for non-ASCII text, which must be measured glyph-by-glyph instead.\n */\nexport function getExpandedAsciiTextColumns(\n text: string,\n tabSize: number\n): number {\n let columns = 0;\n for (let i = 0; i < text.length; i++) {\n const code = text.charCodeAt(i);\n if (code > 127) {\n return -1;\n }\n columns += code === /* '\\t' */ 9 ? tabSize - (columns % tabSize) : 1;\n }\n return columns;\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { BaseCodeOptions, DiffsHighlighter, HighlightedToken, RenderRange } from "../types.js";
2
2
  import { TextDocument, TextDocumentChange } from "./textDocument.js";
3
3
 
4
- //#region src/editor/tokenzier.d.ts
4
+ //#region src/editor/tokenizer.d.ts
5
5
  interface EditorTokenizerProps {
6
6
  highlighter: DiffsHighlighter;
7
7
  textDocument: TextDocument<unknown>;
@@ -35,7 +35,7 @@ declare class EditorTokenizer {
35
35
  pauseBackgroundTokenize(): void;
36
36
  resumeBackgroundTokenize(): void;
37
37
  }
38
- declare function renderLineTokens(tokens: Array<HighlightedToken>, themeType: 'light' | 'dark'): (HTMLElement | string)[];
38
+ declare function renderLineTokens(tokens: Array<HighlightedToken>): (HTMLElement | string)[];
39
39
  //#endregion
40
40
  export { EditorTokenizer, EditorTokenizerProps, renderLineTokens };
41
- //# sourceMappingURL=tokenzier.d.ts.map
41
+ //# sourceMappingURL=tokenizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenizer.d.ts","names":[],"sources":["../../src/editor/tokenizer.ts"],"mappings":";;;;UAqBiB,oBAAA;EACf,WAAA,EAAa,gBAAA;EACb,YAAA,EAAc,YAAA;EACd,WAAA,EAAa,eAAA;EACb,aAAA;EACA,QAAA,GAAW,KAAA;EACX,eAAA,GACE,KAAA,EAAO,GAAA,SAAY,KAAA,CAAM,gBAAA,IACzB,SAAA;EAKF,aAAA;EACA,OAAA;AAAA;;cAIW,eAAA;EAAA;MAyFP,SAAA;EAIJ,kCAAA,CACE,SAAA;EAsBF,WAAA;IACE,WAAA;IACA,WAAA;IACA,YAAA;IACA,aAAA;IACA,QAAA;IACA,eAAA;IACA,aAAA;IACA;EAAA,GACC,oBAAA;EAsHH,SAAA,CAAU,WAAA,EAAa,eAAA;EAgFvB,OAAA;EAUA,QAAA,CACE,MAAA,EAAQ,kBAAA,EACR,WAAA,GAAc,WAAA,GACb,GAAA,SAAY,KAAA,CAAM,gBAAA;EAmMrB,kBAAA,CAAmB,WAAA,GAAc,WAAA;EAKjC,sBAAA;EAiBA,uBAAA;EAYA,wBAAA;AAAA;AAAA,iBAocc,gBAAA,CACd,MAAA,EAAQ,KAAA,CAAM,gBAAA,KACZ,WAAA"}