@pierre/diffs 1.3.0-beta.6 → 1.3.0-beta.8
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.
- package/dist/components/CodeView.d.ts +90 -5
- package/dist/components/CodeView.d.ts.map +1 -1
- package/dist/components/CodeView.js +291 -38
- package/dist/components/CodeView.js.map +1 -1
- package/dist/components/File.d.ts +1 -0
- package/dist/components/File.d.ts.map +1 -1
- package/dist/components/File.js +20 -6
- package/dist/components/File.js.map +1 -1
- package/dist/components/FileDiff.d.ts +38 -23
- package/dist/components/FileDiff.d.ts.map +1 -1
- package/dist/components/FileDiff.js +144 -61
- package/dist/components/FileDiff.js.map +1 -1
- package/dist/components/UnresolvedFile.d.ts +3 -2
- package/dist/components/UnresolvedFile.d.ts.map +1 -1
- package/dist/components/UnresolvedFile.js +4 -2
- package/dist/components/UnresolvedFile.js.map +1 -1
- package/dist/components/VirtualizedFile.d.ts.map +1 -1
- package/dist/components/VirtualizedFile.js +6 -8
- package/dist/components/VirtualizedFile.js.map +1 -1
- package/dist/components/VirtualizedFileDiff.d.ts +13 -5
- package/dist/components/VirtualizedFileDiff.d.ts.map +1 -1
- package/dist/components/VirtualizedFileDiff.js +192 -55
- package/dist/components/VirtualizedFileDiff.js.map +1 -1
- package/dist/components/Virtualizer.d.ts +2 -0
- package/dist/components/Virtualizer.d.ts.map +1 -1
- package/dist/components/Virtualizer.js +16 -3
- package/dist/components/Virtualizer.js.map +1 -1
- package/dist/constants.d.ts +3 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/editor/command.d.ts +1 -1
- package/dist/editor/command.d.ts.map +1 -1
- package/dist/editor/command.js +8 -0
- package/dist/editor/command.js.map +1 -1
- package/dist/editor/editor.d.ts +10 -3
- package/dist/editor/editor.d.ts.map +1 -1
- package/dist/editor/editor.js +325 -135
- package/dist/editor/editor.js.map +1 -1
- package/dist/editor/editor2.js +1 -1
- package/dist/editor/lineAnnotations.d.ts.map +1 -1
- package/dist/editor/lineAnnotations.js +84 -12
- package/dist/editor/lineAnnotations.js.map +1 -1
- package/dist/editor/marker.d.ts +5 -3
- package/dist/editor/marker.d.ts.map +1 -1
- package/dist/editor/marker.js +35 -12
- package/dist/editor/marker.js.map +1 -1
- package/dist/editor/matchBrackets.d.ts +8 -0
- package/dist/editor/matchBrackets.d.ts.map +1 -0
- package/dist/editor/matchBrackets.js +146 -0
- package/dist/editor/matchBrackets.js.map +1 -0
- package/dist/editor/popover.d.ts +45 -0
- package/dist/editor/popover.d.ts.map +1 -0
- package/dist/editor/popover.js +145 -0
- package/dist/editor/popover.js.map +1 -0
- package/dist/editor/selection.d.ts +14 -2
- package/dist/editor/selection.d.ts.map +1 -1
- package/dist/editor/selection.js +142 -41
- package/dist/editor/selection.js.map +1 -1
- package/dist/editor/selectionAction.d.ts +20 -3
- package/dist/editor/selectionAction.d.ts.map +1 -1
- package/dist/editor/selectionAction.js +41 -10
- package/dist/editor/selectionAction.js.map +1 -1
- package/dist/editor/textDocument.d.ts +2 -0
- package/dist/editor/textDocument.d.ts.map +1 -1
- package/dist/editor/textDocument.js +22 -6
- package/dist/editor/textDocument.js.map +1 -1
- package/dist/editor/textMeasure.d.ts.map +1 -1
- package/dist/editor/textMeasure.js +25 -7
- package/dist/editor/textMeasure.js.map +1 -1
- package/dist/editor/tokenzier.d.ts +6 -7
- package/dist/editor/tokenzier.d.ts.map +1 -1
- package/dist/editor/tokenzier.js +83 -34
- package/dist/editor/tokenzier.js.map +1 -1
- package/dist/editor/utils.d.ts +3 -1
- package/dist/editor/utils.d.ts.map +1 -1
- package/dist/editor/utils.js +14 -1
- package/dist/editor/utils.js.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.js +6 -4
- package/dist/managers/InteractionManager.d.ts.map +1 -1
- package/dist/managers/InteractionManager.js +0 -1
- package/dist/managers/InteractionManager.js.map +1 -1
- package/dist/managers/ResizeManager.d.ts +10 -2
- package/dist/managers/ResizeManager.d.ts.map +1 -1
- package/dist/managers/ResizeManager.js +33 -13
- package/dist/managers/ResizeManager.js.map +1 -1
- package/dist/react/CodeView.d.ts +25 -1
- package/dist/react/CodeView.d.ts.map +1 -1
- package/dist/react/CodeView.js +76 -17
- package/dist/react/CodeView.js.map +1 -1
- package/dist/react/EditorContext.d.ts +2 -2
- package/dist/react/EditorContext.d.ts.map +1 -1
- package/dist/react/EditorContext.js.map +1 -1
- package/dist/react/MultiFileDiff.d.ts +3 -4
- package/dist/react/MultiFileDiff.d.ts.map +1 -1
- package/dist/react/MultiFileDiff.js.map +1 -1
- package/dist/react/index.d.ts +2 -2
- package/dist/react/utils/useFileDiffInstance.js +14 -15
- package/dist/react/utils/useFileDiffInstance.js.map +1 -1
- package/dist/renderers/DiffHunksRenderer.d.ts +2 -2
- package/dist/renderers/DiffHunksRenderer.d.ts.map +1 -1
- package/dist/renderers/DiffHunksRenderer.js +45 -23
- package/dist/renderers/DiffHunksRenderer.js.map +1 -1
- package/dist/renderers/FileRenderer.d.ts +2 -1
- package/dist/renderers/FileRenderer.d.ts.map +1 -1
- package/dist/renderers/FileRenderer.js +58 -38
- package/dist/renderers/FileRenderer.js.map +1 -1
- package/dist/ssr/index.d.ts +2 -2
- package/dist/ssr/preloadDiffs.d.ts +11 -10
- package/dist/ssr/preloadDiffs.d.ts.map +1 -1
- package/dist/ssr/preloadDiffs.js +14 -6
- package/dist/ssr/preloadDiffs.js.map +1 -1
- package/dist/types.d.ts +100 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/applyLineTextWithNewline.d.ts +5 -0
- package/dist/utils/applyLineTextWithNewline.d.ts.map +1 -0
- package/dist/utils/applyLineTextWithNewline.js +11 -0
- package/dist/utils/applyLineTextWithNewline.js.map +1 -0
- package/dist/utils/areHunkDataEqual.js +1 -1
- package/dist/utils/areHunkDataEqual.js.map +1 -1
- package/dist/utils/areManagedSnapshotsEqual.d.ts +2 -2
- package/dist/utils/areManagedSnapshotsEqual.d.ts.map +1 -1
- package/dist/utils/areManagedSnapshotsEqual.js +5 -0
- package/dist/utils/areManagedSnapshotsEqual.js.map +1 -1
- package/dist/utils/awaitWithTimeout.d.ts +5 -0
- package/dist/utils/awaitWithTimeout.d.ts.map +1 -0
- package/dist/utils/awaitWithTimeout.js +15 -0
- package/dist/utils/awaitWithTimeout.js.map +1 -0
- package/dist/utils/cloneFileDiffMetadata.d.ts +7 -0
- package/dist/utils/cloneFileDiffMetadata.d.ts.map +1 -0
- package/dist/utils/cloneFileDiffMetadata.js +16 -0
- package/dist/utils/cloneFileDiffMetadata.js.map +1 -0
- package/dist/utils/computeEstimatedDiffHeights.d.ts +3 -1
- package/dist/utils/computeEstimatedDiffHeights.d.ts.map +1 -1
- package/dist/utils/computeEstimatedDiffHeights.js +8 -1
- package/dist/utils/computeEstimatedDiffHeights.js.map +1 -1
- package/dist/utils/createCodeViewHeaderFooterHostElement.d.ts +5 -0
- package/dist/utils/createCodeViewHeaderFooterHostElement.d.ts.map +1 -0
- package/dist/utils/createCodeViewHeaderFooterHostElement.js +19 -0
- package/dist/utils/createCodeViewHeaderFooterHostElement.js.map +1 -0
- package/dist/utils/createPreElement.js +0 -1
- package/dist/utils/createPreElement.js.map +1 -1
- package/dist/utils/getDiffFileInput.d.ts +14 -0
- package/dist/utils/getDiffFileInput.d.ts.map +1 -0
- package/dist/utils/getDiffFileInput.js +24 -0
- package/dist/utils/getDiffFileInput.js.map +1 -0
- package/dist/utils/getDiffHunksRendererOptions.js +1 -0
- package/dist/utils/getDiffHunksRendererOptions.js.map +1 -1
- package/dist/utils/getFiletypeFromFileName.d.ts.map +1 -1
- package/dist/utils/getFiletypeFromFileName.js +2 -0
- package/dist/utils/getFiletypeFromFileName.js.map +1 -1
- package/dist/utils/hydratePartialDiff.d.ts +10 -0
- package/dist/utils/hydratePartialDiff.d.ts.map +1 -0
- package/dist/utils/hydratePartialDiff.js +140 -0
- package/dist/utils/hydratePartialDiff.js.map +1 -0
- package/dist/utils/iterateOverDiff.js +3 -3
- package/dist/utils/iterateOverDiff.js.map +1 -1
- package/dist/utils/parseDiffFromFile.d.ts +1 -1
- package/dist/utils/parseDiffFromFile.d.ts.map +1 -1
- package/dist/utils/parseDiffFromFile.js +26 -5
- package/dist/utils/parseDiffFromFile.js.map +1 -1
- package/dist/utils/setWrapperNodeProps.js +0 -1
- package/dist/utils/setWrapperNodeProps.js.map +1 -1
- package/dist/utils/updateDiffHunks.d.ts +5 -1
- package/dist/utils/updateDiffHunks.d.ts.map +1 -1
- package/dist/utils/updateDiffHunks.js +48 -4
- package/dist/utils/updateDiffHunks.js.map +1 -1
- package/dist/worker/WorkerPoolManager.d.ts +7 -2
- package/dist/worker/WorkerPoolManager.d.ts.map +1 -1
- package/dist/worker/WorkerPoolManager.js +78 -15
- package/dist/worker/WorkerPoolManager.js.map +1 -1
- package/dist/worker/index.d.ts +2 -2
- package/dist/worker/types.d.ts +7 -1
- package/dist/worker/types.d.ts.map +1 -1
- package/dist/worker/worker-portable.js +5 -3
- package/dist/worker/worker-portable.js.map +1 -1
- package/dist/worker/worker.js +5 -3
- package/dist/worker/worker.js.map +1 -1
- package/package.json +1 -1
|
@@ -101,6 +101,8 @@ interface TextDocumentChange {
|
|
|
101
101
|
readonly lineDelta: number;
|
|
102
102
|
/** Exact rendered line ranges touched by each edit after the edit was applied. */
|
|
103
103
|
readonly changedLineRanges: readonly [startLine: number, endLine: number][];
|
|
104
|
+
/** Per-edit rendered line ranges before adjacent ranges are coalesced. */
|
|
105
|
+
readonly changedLineChanges?: readonly [startLine: number, endLine: number, lineDelta: number][];
|
|
104
106
|
}
|
|
105
107
|
/**
|
|
106
108
|
* A vscode-languageserver-textdocument compatible text document.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textDocument.d.ts","names":[],"sources":["../../src/editor/textDocument.ts"],"mappings":";;;;;;;;AAsBA;;;;AAoBoB;AAgBpB;;;UApCiB,QAAA;EAwCN;;;;;AAIa;AAMxB;;;EAVW,SA9BA,IAAA;EA6CA;;;;AAKO;AAIlB;;;;EATW,SAnCA,SAAS;AAAA;;;AAqDL;AAGf;;;;;;;;;;UAxCiB,KAAA;EAsDuB;;;EAAA,SAlD7B,KAAA,EAAO,QAAA;
|
|
1
|
+
{"version":3,"file":"textDocument.d.ts","names":[],"sources":["../../src/editor/textDocument.ts"],"mappings":";;;;;;;;AAsBA;;;;AAoBoB;AAgBpB;;;UApCiB,QAAA;EAwCN;;;;;AAIa;AAMxB;;;EAVW,SA9BA,IAAA;EA6CA;;;;AAKO;AAIlB;;;;EATW,SAnCA,SAAS;AAAA;;;AAqDL;AAGf;;;;;;;;;;UAxCiB,KAAA;EAsDuB;;;EAAA,SAlD7B,KAAA,EAAO,QAAA;EAsDd;;;EAAA,SAlDO,GAAA,EAAK,QAAQ;AAAA;;;;UAMP,QAAA;EAmHa;;;;EAAA,SA9GnB,KAAA,EAAO,KAAK;EA6KA;;;;EAAA,SAxKZ,OAAA;AAAA;;UAIM,gBAAA;EAiMZ;EAAA,SA/LM,KAAA;EAyOmC;EAAA,SAvOnC,GAAA;EAwOkC;;;;EAAA,SAnOlC,IAAA;AAAA;AAAA,UAGM,kBAAA;EAkQI;EAAA,SAhQV,SAAA;EAiQe;EAAA,SA/Pf,cAAA;EAkR8B;EAAA,SAhR9B,OAAA;EAgRsC;EAAA,SA9QtC,iBAAA;;WAEA,SAAA;EAwBP;EAAA,SAtBO,SAAA;EAwBP;EAAA,SAtBO,iBAAA,YAA6B,SAAA,UAAmB,OAAA;EAwB5C;EAAA,SAtBJ,kBAAA,aACP,SAAA,UACA,OAAA,UACA,SAAA;AAAA;;;;cAOS,YAAA;EAAA;EAOX,WAAA,CACE,GAAA,UACA,IAAA,UACA,UAAA,WACA,OAAA,WACA,SAAA,GAAW,SAAA,CAAU,WAAA;EAAA,IASnB,GAAA;EAAA,IAIA,UAAA;EAAA,IAIA,OAAA;EAAA,IAIA,SAAA;EAAA,IASA,GAAA;EAAA,IAaA,OAAA;EAAA,IAIA,OAAA;EAIJ,UAAA,CAAW,MAAA,WAAiB,QAAA;EAI5B,WAAA,CAAY,OAAA,sBAA6B,QAAA;EAIzC,QAAA,CAAS,QAAA,EAAU,QAAA;EAInB,OAAA,CAAQ,KAAA,GAAQ,KAAA;EAgBhB,WAAA,CAAY,IAAA,UAAc,gBAAA;EAO1B,YAAA,CAAa,IAAA;EAIb,aAAA,CAAc,IAAA,UAAc,gBAAA;EAI5B,MAAA,CAAO,MAAA;EACP,MAAA,CAAO,QAAA,EAAU,QAAA;EAQjB,YAAA,CAAa,KAAA,UAAe,GAAA;EAI5B,+BAAA,CACE,MAAA,UACA,QAAA,YAAoB,KAAA,UAAe,GAAA;EAKrC,MAAA,CAAO,YAAA,EAAc,YAAA,IAAgB,KAAA,UAAe,GAAA;EAIpD,UAAA,CACE,KAAA,EAAO,QAAA,IACP,aAAA,YACA,gBAAA,GAAmB,eAAA,IACnB,eAAA,GAAkB,eAAA,IAClB,YAAA,aACC,kBAAA;EAaH,kBAAA,CACE,KAAA,EAAO,gBAAA,IACP,aAAA,YACA,gBAAA,GAAmB,eAAA,IACnB,eAAA,GAAkB,eAAA,IAClB,YAAA,aACC,kBAAA;EAqCH,0BAAA,CAA2B,UAAA,EAAY,eAAA;EAIvC,0BAAA,CACE,qBAAA,EAAuB,kBAAA,CAAmB,WAAA,KAC1C,oBAAA,EAAsB,kBAAA,CAAmB,WAAA;EAQ3C,IAAA,KAEM,MAAA,EAAQ,kBAAA,EACR,UAAA,GAAa,eAAA,IACb,eAAA,GAAkB,kBAAA,CAAmB,WAAA;EAmB3C,IAAA,KAEM,MAAA,EAAQ,kBAAA,EACR,UAAA,GAAa,eAAA,IACb,eAAA,GAAkB,kBAAA,CAAmB,WAAA;EAmB3C,iBAAA,CAAkB,QAAA,EAAU,QAAA,GAAW,QAAA;AAAA"}
|
|
@@ -173,7 +173,8 @@ var TextDocument = class {
|
|
|
173
173
|
previousLineCount,
|
|
174
174
|
lineCount,
|
|
175
175
|
lineDelta: lineCount - previousLineCount,
|
|
176
|
-
changedLineRanges: changedLineRange.ranges
|
|
176
|
+
changedLineRanges: changedLineRange.ranges,
|
|
177
|
+
changedLineChanges: changedLineRange.changes
|
|
177
178
|
};
|
|
178
179
|
}
|
|
179
180
|
#computeChangedLineRange(edits, editPositions) {
|
|
@@ -181,29 +182,44 @@ var TextDocument = class {
|
|
|
181
182
|
let endLine = 0;
|
|
182
183
|
let lineDeltaBeforeEdit = 0;
|
|
183
184
|
const ranges = [];
|
|
185
|
+
const changes = [];
|
|
184
186
|
for (let i = 0; i < edits.length; i++) {
|
|
185
187
|
const edit = edits[i];
|
|
186
|
-
const
|
|
187
|
-
const
|
|
188
|
+
const editStart = editPositions[i * 2];
|
|
189
|
+
const editEnd = editPositions[i * 2 + 1];
|
|
190
|
+
const editStartLine = editStart.line;
|
|
191
|
+
const editEndLine = editEnd.line;
|
|
188
192
|
const insertedLineSpan = countLineBreaks(edit.text);
|
|
189
193
|
const changedStartLine = editStartLine + lineDeltaBeforeEdit;
|
|
190
194
|
const changedEndLine = changedStartLine + insertedLineSpan;
|
|
195
|
+
const lineDelta = insertedLineSpan - (editEndLine - editStartLine);
|
|
191
196
|
startLine = Math.min(startLine, editStartLine);
|
|
192
197
|
endLine = Math.max(endLine, changedEndLine);
|
|
193
198
|
const lastRange = ranges[ranges.length - 1];
|
|
194
199
|
if (lastRange !== void 0 && changedStartLine <= lastRange[1] + 1) ranges[ranges.length - 1] = [lastRange[0], Math.max(lastRange[1], changedEndLine)];
|
|
195
200
|
else ranges.push([changedStartLine, changedEndLine]);
|
|
196
|
-
|
|
201
|
+
changes.push([
|
|
202
|
+
changedStartLine,
|
|
203
|
+
changedEndLine,
|
|
204
|
+
lineDelta
|
|
205
|
+
]);
|
|
206
|
+
lineDeltaBeforeEdit += lineDelta;
|
|
197
207
|
}
|
|
198
208
|
if (startLine === Infinity) return {
|
|
199
209
|
startLine: 0,
|
|
200
210
|
endLine: 0,
|
|
201
|
-
ranges: [[0, 0]]
|
|
211
|
+
ranges: [[0, 0]],
|
|
212
|
+
changes: [[
|
|
213
|
+
0,
|
|
214
|
+
0,
|
|
215
|
+
0
|
|
216
|
+
]]
|
|
202
217
|
};
|
|
203
218
|
return {
|
|
204
219
|
startLine,
|
|
205
220
|
endLine,
|
|
206
|
-
ranges
|
|
221
|
+
ranges,
|
|
222
|
+
changes
|
|
207
223
|
};
|
|
208
224
|
}
|
|
209
225
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textDocument.js","names":["#uri","#languageId","#version","#pieceTable","#editStack","#resolveEdit","#sortAndValidateResolvedEdits","#applyResolvedEditsToBuffer","#computeChangedLineRange"],"sources":["../../src/editor/textDocument.ts"],"sourcesContent":["import type { DiffLineAnnotation } from '../types';\nimport { countLineBreaks } from '../utils/computeFileOffsets';\nimport {\n coalesceEditStackEntries,\n createEditStackEntry,\n EditStack,\n shouldCoalesceEditStackEntry,\n} from './editStack';\nimport { PieceTable } from './pieceTable';\nimport type { SearchParams } from './searchPanel';\nimport { type EditorSelection } from './selection';\n\n/**\n * Position in a text document expressed as zero-based line and character offset.\n * The offsets are based on a UTF-16 string representation. So a string of the form\n * `a𐐀b` the character offset of the character `a` is 0, the character offset of `𐐀`\n * is 1 and the character offset of b is 3 since `𐐀` is represented using two code\n * units in UTF-16.\n *\n * Positions are line end character agnostic. So you can not specify a position that\n * denotes `\\r|\\n` or `\\n|` where `|` represents the character offset.\n */\nexport interface Position {\n /**\n * Line position in a document (zero-based).\n *\n * If a line number is greater than the number of lines in a document, it\n * defaults back to the number of lines in the document.\n * If a line number is negative, it defaults to 0.\n *\n * The above two properties are implementation specific.\n */\n readonly line: number;\n /**\n * Character offset on a line in a document (zero-based).\n *\n * The meaning of this offset is determined by the negotiated\n * `PositionEncodingKind`.\n *\n * If the character value is greater than the line length it defaults back\n * to the line length. This property is implementation specific.\n */\n readonly character: number;\n}\n\n/**\n * A range in a text document expressed as (zero-based) start and end positions.\n *\n * If you want to specify a range that contains a line including the line ending\n * character(s) then use an end position denoting the start of the next line.\n * For example:\n * ```ts\n * {\n * start: { line: 5, character: 23 }\n * end : { line 6, character : 0 }\n * }\n * ```\n */\nexport interface Range {\n /**\n * The range's start position.\n */\n readonly start: Position;\n /**\n * The range's end position.\n */\n readonly end: Position;\n}\n\n/**\n * A text edit applicable to a text document.\n */\nexport interface TextEdit {\n /**\n * The range of the text document to be manipulated. To insert\n * text into a document create a range where start === end.\n */\n readonly range: Range;\n /**\n * The string to be inserted. For delete operations use an\n * empty string.\n */\n readonly newText: string;\n}\n\n/** Different with `TextEdit`, the range has been resolved to offsets. */\nexport interface ResolvedTextEdit {\n /** The start offset of the text change. */\n readonly start: number;\n /** The end offset of the text change. */\n readonly end: number;\n /**\n * The string to be inserted. For delete operations use an\n * empty string.\n */\n readonly text: string;\n}\n\nexport interface TextDocumentChange {\n /** First line whose rendered content or tokenizer state may have changed. */\n readonly startLine: number;\n /** Character on the first changed line where the edit began. */\n readonly startCharacter: number;\n /** Last line whose rendered content may have changed after the edit. */\n readonly endLine: number;\n /** Line count before the edit was applied. */\n readonly previousLineCount: number;\n /** Line count after the edit was applied. */\n readonly lineCount: number;\n /** Difference between the old and new line counts. */\n readonly lineDelta: number;\n /** Exact rendered line ranges touched by each edit after the edit was applied. */\n readonly changedLineRanges: readonly [startLine: number, endLine: number][];\n}\n\n/**\n * A vscode-languageserver-textdocument compatible text document.\n */\nexport class TextDocument<LAnnotation> {\n #uri: string;\n #languageId: string;\n #version: number;\n #pieceTable: PieceTable;\n #editStack: EditStack<LAnnotation>;\n\n constructor(\n uri: string,\n text: string,\n languageId = 'plaintext',\n version = 0,\n editStack: EditStack<LAnnotation> = new EditStack()\n ) {\n this.#uri = new URL(uri, 'file://').toString();\n this.#languageId = languageId;\n this.#version = version;\n this.#pieceTable = new PieceTable(text);\n this.#editStack = editStack;\n }\n\n get uri(): string {\n return this.#uri;\n }\n\n get languageId(): string {\n return this.#languageId;\n }\n\n get version(): number {\n return this.#version;\n }\n\n get lineCount(): number {\n return this.#pieceTable.lineCount;\n }\n\n // The line ending the document uses, detected from its first line. Lets\n // inserted or pasted text match the rest of the file instead of leaving\n // mixed endings behind. Recognizes Windows `\\r\\n` and classic-Mac lone `\\r`\n // breaks (both of which the piece table already splits on), defaulting to\n // Unix `\\n`.\n get eol(): string {\n if (this.lineCount > 1) {\n const firstLineBreak = this.getLineText(0, true);\n if (firstLineBreak.endsWith('\\r\\n')) {\n return '\\r\\n';\n }\n if (firstLineBreak.endsWith('\\r')) {\n return '\\r';\n }\n }\n return '\\n';\n }\n\n get canUndo(): boolean {\n return this.#editStack.canUndo;\n }\n\n get canRedo(): boolean {\n return this.#editStack.canRedo;\n }\n\n positionAt(offset: number): Position {\n return this.#pieceTable.positionAt(offset);\n }\n\n positionsAt(offsets: readonly number[]): Position[] {\n return this.#pieceTable.positionsAt(offsets);\n }\n\n offsetAt(position: Position): number {\n return this.#pieceTable.offsetAt(this.normalizePosition(position));\n }\n\n getText(range?: Range): string {\n if (range === undefined) {\n return this.#pieceTable.getText();\n }\n // Clamp the range to visible line content before extracting text. A\n // preserved vertical-move \"goal column\" can leave a selection focus whose\n // character overshoots a shorter line; without this, the piece table clamps\n // to the line's offset span (which includes the trailing line break) and\n // copy/cut would pull in that newline. Mirrors `offsetAt`, which normalizes\n // positions the same way.\n return this.#pieceTable.getText({\n start: this.normalizePosition(range.start),\n end: this.normalizePosition(range.end),\n });\n }\n\n getLineText(line: number, includeLineBreak?: boolean): string {\n return this.#pieceTable.getLineText(line, includeLineBreak);\n }\n\n // Rewrites every line break in `text` to the document's EOL. Clipboard text\n // can carry Windows (`\\r\\n`) or classic-Mac (`\\r`) breaks; inserting it\n // verbatim would leave mixed line endings in a file that uses one style.\n normalizeEol(text: string): string {\n return text.replace(/\\r\\n|\\r|\\n/g, this.eol);\n }\n\n getLineLength(line: number, includeLineBreak?: boolean): number {\n return this.#pieceTable.getLineLength(line, includeLineBreak);\n }\n\n charAt(offset: number): string;\n charAt(position: Position): string;\n charAt(positionOrOffset: Position | number): string {\n if (typeof positionOrOffset === 'number') {\n return this.#pieceTable.charAt(positionOrOffset);\n }\n return this.#pieceTable.charAt(this.offsetAt(positionOrOffset));\n }\n\n getTextSlice(start: number, end: number): string {\n return this.#pieceTable.getTextSlice(start, end);\n }\n\n findNextNonOverlappingSubstring(\n needle: string,\n occupied: readonly [start: number, end: number][]\n ): number | undefined {\n return this.#pieceTable.findNextNonOverlappingSubstring(needle, occupied);\n }\n\n search(searchParams: SearchParams): [start: number, end: number][] {\n return this.#pieceTable.search(searchParams);\n }\n\n applyEdits(\n edits: TextEdit[],\n updateHistory = false,\n selectionsBefore?: EditorSelection[],\n selectionsAfter?: EditorSelection[],\n undoBoundary = false\n ): TextDocumentChange | undefined {\n if (edits.length === 0) {\n return;\n }\n return this.applyResolvedEdits(\n edits.map((edit) => this.#resolveEdit(edit)),\n updateHistory,\n selectionsBefore,\n selectionsAfter,\n undoBoundary\n );\n }\n\n applyResolvedEdits(\n edits: ResolvedTextEdit[],\n updateHistory = false,\n selectionsBefore?: EditorSelection[],\n selectionsAfter?: EditorSelection[],\n undoBoundary = false\n ): TextDocumentChange | undefined {\n if (edits.length === 0) {\n return undefined;\n }\n const resolvedEdits = this.#sortAndValidateResolvedEdits(edits);\n if (updateHistory) {\n const entry = createEditStackEntry(\n this,\n resolvedEdits,\n this.#version,\n this.#version + 1,\n selectionsBefore,\n selectionsAfter\n );\n if (undoBoundary) {\n entry.undoBoundary = true;\n }\n const previousEntry = this.#editStack.peekUndo();\n const change = this.#applyResolvedEditsToBuffer(resolvedEdits);\n this.#version++;\n if (\n change.lineDelta === 0 &&\n shouldCoalesceEditStackEntry(previousEntry, entry)\n ) {\n this.#editStack.replaceLastUndo(\n coalesceEditStackEntries(previousEntry!, entry)\n );\n } else {\n this.#editStack.push(entry);\n }\n return change;\n }\n const change = this.#applyResolvedEditsToBuffer(resolvedEdits);\n this.#version++;\n return change;\n }\n\n setLastUndoSelectionsAfter(selections: EditorSelection[]): void {\n this.#editStack.setLastUndoSelectionsAfter(selections);\n }\n\n setLastUndoLineAnnotations(\n lineAnnotationsBefore: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter: DiffLineAnnotation<LAnnotation>[]\n ): void {\n this.#editStack.setLastUndoLineAnnotations(\n lineAnnotationsBefore,\n lineAnnotationsAfter\n );\n }\n\n undo():\n | [\n change: TextDocumentChange,\n selections?: EditorSelection[],\n lineAnnotations?: DiffLineAnnotation<LAnnotation>[],\n ]\n | undefined {\n const entry = this.#editStack.popUndoToRedo();\n if (entry === undefined) {\n return undefined;\n }\n const change = this.#applyResolvedEditsToBuffer(entry.inverseEdits);\n if (change === undefined) {\n return undefined;\n }\n this.#version = entry.versionBefore;\n return [\n change,\n entry.selectionsBefore?.slice(),\n entry.lineAnnotationsBefore?.slice(),\n ];\n }\n\n redo():\n | [\n change: TextDocumentChange,\n selections?: EditorSelection[],\n lineAnnotations?: DiffLineAnnotation<LAnnotation>[],\n ]\n | undefined {\n const entry = this.#editStack.popRedoToUndo();\n if (entry === undefined) {\n return undefined;\n }\n const change = this.#applyResolvedEditsToBuffer(entry.forwardEdits);\n if (change === undefined) {\n return undefined;\n }\n this.#version = entry.versionAfter;\n return [\n change,\n entry.selectionsAfter?.slice(),\n entry.lineAnnotationsAfter?.slice(),\n ];\n }\n\n normalizePosition(position: Position): Position {\n const line = Math.max(0, Math.min(position.line, this.lineCount - 1));\n return {\n line,\n character: Math.max(\n 0,\n Math.min(position.character, this.getLineLength(line))\n ),\n };\n }\n\n #resolveEdit(edit: TextEdit): ResolvedTextEdit {\n let start = this.offsetAt(edit.range.start);\n let end = this.offsetAt(edit.range.end);\n if (start > end) {\n const t = start;\n start = end;\n end = t;\n }\n return { start, end, text: edit.newText };\n }\n\n #sortAndValidateResolvedEdits(edits: ResolvedTextEdit[]): ResolvedTextEdit[] {\n const sortedEdits = [...edits].sort((a, b) => a.start - b.start);\n for (let i = 0; i < sortedEdits.length - 1; i++) {\n if (sortedEdits[i].end > sortedEdits[i + 1].start) {\n throw new Error('Overlapping text edits are not supported');\n }\n }\n return sortedEdits;\n }\n\n #applyResolvedEditsToBuffer(edits: ResolvedTextEdit[]): TextDocumentChange {\n const previousLineCount = this.#pieceTable.lineCount;\n const editPositions = this.positionsAt(\n edits.flatMap((edit) => [edit.start, edit.end])\n );\n const changedLineRange = this.#computeChangedLineRange(\n edits,\n editPositions\n );\n const startPosition = editPositions[0];\n this.#pieceTable.applyEdits(edits);\n const lineCount = this.#pieceTable.lineCount;\n const change: TextDocumentChange = {\n startLine: changedLineRange.startLine,\n startCharacter: startPosition.character,\n endLine: Math.min(changedLineRange.endLine, Math.max(0, lineCount - 1)),\n previousLineCount,\n lineCount,\n lineDelta: lineCount - previousLineCount,\n changedLineRanges: changedLineRange.ranges,\n };\n return change;\n }\n\n #computeChangedLineRange(\n edits: ResolvedTextEdit[],\n editPositions: Position[]\n ): {\n startLine: number;\n endLine: number;\n ranges: [number, number][];\n } {\n let startLine = Infinity;\n let endLine = 0;\n let lineDeltaBeforeEdit = 0;\n const ranges: [number, number][] = [];\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i];\n const editStartLine = editPositions[i * 2].line;\n const editEndLine = editPositions[i * 2 + 1].line;\n const insertedLineSpan = countLineBreaks(edit.text);\n const changedStartLine = editStartLine + lineDeltaBeforeEdit;\n const changedEndLine = changedStartLine + insertedLineSpan;\n startLine = Math.min(startLine, editStartLine);\n endLine = Math.max(endLine, changedEndLine);\n const lastRange = ranges[ranges.length - 1];\n if (lastRange !== undefined && changedStartLine <= lastRange[1] + 1) {\n ranges[ranges.length - 1] = [\n lastRange[0],\n Math.max(lastRange[1], changedEndLine),\n ];\n } else {\n ranges.push([changedStartLine, changedEndLine]);\n }\n lineDeltaBeforeEdit += insertedLineSpan - (editEndLine - editStartLine);\n }\n if (startLine === Infinity) {\n return {\n startLine: 0,\n endLine: 0,\n ranges: [[0, 0]],\n };\n }\n return { startLine, endLine, ranges };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"textDocument.js","names":["#uri","#languageId","#version","#pieceTable","#editStack","#resolveEdit","#sortAndValidateResolvedEdits","#applyResolvedEditsToBuffer","#computeChangedLineRange"],"sources":["../../src/editor/textDocument.ts"],"sourcesContent":["import type { DiffLineAnnotation } from '../types';\nimport { countLineBreaks } from '../utils/computeFileOffsets';\nimport {\n coalesceEditStackEntries,\n createEditStackEntry,\n EditStack,\n shouldCoalesceEditStackEntry,\n} from './editStack';\nimport { PieceTable } from './pieceTable';\nimport type { SearchParams } from './searchPanel';\nimport { type EditorSelection } from './selection';\n\n/**\n * Position in a text document expressed as zero-based line and character offset.\n * The offsets are based on a UTF-16 string representation. So a string of the form\n * `a𐐀b` the character offset of the character `a` is 0, the character offset of `𐐀`\n * is 1 and the character offset of b is 3 since `𐐀` is represented using two code\n * units in UTF-16.\n *\n * Positions are line end character agnostic. So you can not specify a position that\n * denotes `\\r|\\n` or `\\n|` where `|` represents the character offset.\n */\nexport interface Position {\n /**\n * Line position in a document (zero-based).\n *\n * If a line number is greater than the number of lines in a document, it\n * defaults back to the number of lines in the document.\n * If a line number is negative, it defaults to 0.\n *\n * The above two properties are implementation specific.\n */\n readonly line: number;\n /**\n * Character offset on a line in a document (zero-based).\n *\n * The meaning of this offset is determined by the negotiated\n * `PositionEncodingKind`.\n *\n * If the character value is greater than the line length it defaults back\n * to the line length. This property is implementation specific.\n */\n readonly character: number;\n}\n\n/**\n * A range in a text document expressed as (zero-based) start and end positions.\n *\n * If you want to specify a range that contains a line including the line ending\n * character(s) then use an end position denoting the start of the next line.\n * For example:\n * ```ts\n * {\n * start: { line: 5, character: 23 }\n * end : { line 6, character : 0 }\n * }\n * ```\n */\nexport interface Range {\n /**\n * The range's start position.\n */\n readonly start: Position;\n /**\n * The range's end position.\n */\n readonly end: Position;\n}\n\n/**\n * A text edit applicable to a text document.\n */\nexport interface TextEdit {\n /**\n * The range of the text document to be manipulated. To insert\n * text into a document create a range where start === end.\n */\n readonly range: Range;\n /**\n * The string to be inserted. For delete operations use an\n * empty string.\n */\n readonly newText: string;\n}\n\n/** Different with `TextEdit`, the range has been resolved to offsets. */\nexport interface ResolvedTextEdit {\n /** The start offset of the text change. */\n readonly start: number;\n /** The end offset of the text change. */\n readonly end: number;\n /**\n * The string to be inserted. For delete operations use an\n * empty string.\n */\n readonly text: string;\n}\n\nexport interface TextDocumentChange {\n /** First line whose rendered content or tokenizer state may have changed. */\n readonly startLine: number;\n /** Character on the first changed line where the edit began. */\n readonly startCharacter: number;\n /** Last line whose rendered content may have changed after the edit. */\n readonly endLine: number;\n /** Line count before the edit was applied. */\n readonly previousLineCount: number;\n /** Line count after the edit was applied. */\n readonly lineCount: number;\n /** Difference between the old and new line counts. */\n readonly lineDelta: number;\n /** Exact rendered line ranges touched by each edit after the edit was applied. */\n readonly changedLineRanges: readonly [startLine: number, endLine: number][];\n /** Per-edit rendered line ranges before adjacent ranges are coalesced. */\n readonly changedLineChanges?: readonly [\n startLine: number,\n endLine: number,\n lineDelta: number,\n ][];\n}\n\n/**\n * A vscode-languageserver-textdocument compatible text document.\n */\nexport class TextDocument<LAnnotation> {\n #uri: string;\n #languageId: string;\n #version: number;\n #pieceTable: PieceTable;\n #editStack: EditStack<LAnnotation>;\n\n constructor(\n uri: string,\n text: string,\n languageId = 'plaintext',\n version = 0,\n editStack: EditStack<LAnnotation> = new EditStack()\n ) {\n this.#uri = new URL(uri, 'file://').toString();\n this.#languageId = languageId;\n this.#version = version;\n this.#pieceTable = new PieceTable(text);\n this.#editStack = editStack;\n }\n\n get uri(): string {\n return this.#uri;\n }\n\n get languageId(): string {\n return this.#languageId;\n }\n\n get version(): number {\n return this.#version;\n }\n\n get lineCount(): number {\n return this.#pieceTable.lineCount;\n }\n\n // The line ending the document uses, detected from its first line. Lets\n // inserted or pasted text match the rest of the file instead of leaving\n // mixed endings behind. Recognizes Windows `\\r\\n` and classic-Mac lone `\\r`\n // breaks (both of which the piece table already splits on), defaulting to\n // Unix `\\n`.\n get eol(): string {\n if (this.lineCount > 1) {\n const firstLineBreak = this.getLineText(0, true);\n if (firstLineBreak.endsWith('\\r\\n')) {\n return '\\r\\n';\n }\n if (firstLineBreak.endsWith('\\r')) {\n return '\\r';\n }\n }\n return '\\n';\n }\n\n get canUndo(): boolean {\n return this.#editStack.canUndo;\n }\n\n get canRedo(): boolean {\n return this.#editStack.canRedo;\n }\n\n positionAt(offset: number): Position {\n return this.#pieceTable.positionAt(offset);\n }\n\n positionsAt(offsets: readonly number[]): Position[] {\n return this.#pieceTable.positionsAt(offsets);\n }\n\n offsetAt(position: Position): number {\n return this.#pieceTable.offsetAt(this.normalizePosition(position));\n }\n\n getText(range?: Range): string {\n if (range === undefined) {\n return this.#pieceTable.getText();\n }\n // Clamp the range to visible line content before extracting text. A\n // preserved vertical-move \"goal column\" can leave a selection focus whose\n // character overshoots a shorter line; without this, the piece table clamps\n // to the line's offset span (which includes the trailing line break) and\n // copy/cut would pull in that newline. Mirrors `offsetAt`, which normalizes\n // positions the same way.\n return this.#pieceTable.getText({\n start: this.normalizePosition(range.start),\n end: this.normalizePosition(range.end),\n });\n }\n\n getLineText(line: number, includeLineBreak?: boolean): string {\n return this.#pieceTable.getLineText(line, includeLineBreak);\n }\n\n // Rewrites every line break in `text` to the document's EOL. Clipboard text\n // can carry Windows (`\\r\\n`) or classic-Mac (`\\r`) breaks; inserting it\n // verbatim would leave mixed line endings in a file that uses one style.\n normalizeEol(text: string): string {\n return text.replace(/\\r\\n|\\r|\\n/g, this.eol);\n }\n\n getLineLength(line: number, includeLineBreak?: boolean): number {\n return this.#pieceTable.getLineLength(line, includeLineBreak);\n }\n\n charAt(offset: number): string;\n charAt(position: Position): string;\n charAt(positionOrOffset: Position | number): string {\n if (typeof positionOrOffset === 'number') {\n return this.#pieceTable.charAt(positionOrOffset);\n }\n return this.#pieceTable.charAt(this.offsetAt(positionOrOffset));\n }\n\n getTextSlice(start: number, end: number): string {\n return this.#pieceTable.getTextSlice(start, end);\n }\n\n findNextNonOverlappingSubstring(\n needle: string,\n occupied: readonly [start: number, end: number][]\n ): number | undefined {\n return this.#pieceTable.findNextNonOverlappingSubstring(needle, occupied);\n }\n\n search(searchParams: SearchParams): [start: number, end: number][] {\n return this.#pieceTable.search(searchParams);\n }\n\n applyEdits(\n edits: TextEdit[],\n updateHistory = false,\n selectionsBefore?: EditorSelection[],\n selectionsAfter?: EditorSelection[],\n undoBoundary = false\n ): TextDocumentChange | undefined {\n if (edits.length === 0) {\n return;\n }\n return this.applyResolvedEdits(\n edits.map((edit) => this.#resolveEdit(edit)),\n updateHistory,\n selectionsBefore,\n selectionsAfter,\n undoBoundary\n );\n }\n\n applyResolvedEdits(\n edits: ResolvedTextEdit[],\n updateHistory = false,\n selectionsBefore?: EditorSelection[],\n selectionsAfter?: EditorSelection[],\n undoBoundary = false\n ): TextDocumentChange | undefined {\n if (edits.length === 0) {\n return undefined;\n }\n const resolvedEdits = this.#sortAndValidateResolvedEdits(edits);\n if (updateHistory) {\n const entry = createEditStackEntry(\n this,\n resolvedEdits,\n this.#version,\n this.#version + 1,\n selectionsBefore,\n selectionsAfter\n );\n if (undoBoundary) {\n entry.undoBoundary = true;\n }\n const previousEntry = this.#editStack.peekUndo();\n const change = this.#applyResolvedEditsToBuffer(resolvedEdits);\n this.#version++;\n if (\n change.lineDelta === 0 &&\n shouldCoalesceEditStackEntry(previousEntry, entry)\n ) {\n this.#editStack.replaceLastUndo(\n coalesceEditStackEntries(previousEntry!, entry)\n );\n } else {\n this.#editStack.push(entry);\n }\n return change;\n }\n const change = this.#applyResolvedEditsToBuffer(resolvedEdits);\n this.#version++;\n return change;\n }\n\n setLastUndoSelectionsAfter(selections: EditorSelection[]): void {\n this.#editStack.setLastUndoSelectionsAfter(selections);\n }\n\n setLastUndoLineAnnotations(\n lineAnnotationsBefore: DiffLineAnnotation<LAnnotation>[],\n lineAnnotationsAfter: DiffLineAnnotation<LAnnotation>[]\n ): void {\n this.#editStack.setLastUndoLineAnnotations(\n lineAnnotationsBefore,\n lineAnnotationsAfter\n );\n }\n\n undo():\n | [\n change: TextDocumentChange,\n selections?: EditorSelection[],\n lineAnnotations?: DiffLineAnnotation<LAnnotation>[],\n ]\n | undefined {\n const entry = this.#editStack.popUndoToRedo();\n if (entry === undefined) {\n return undefined;\n }\n const change = this.#applyResolvedEditsToBuffer(entry.inverseEdits);\n if (change === undefined) {\n return undefined;\n }\n this.#version = entry.versionBefore;\n return [\n change,\n entry.selectionsBefore?.slice(),\n entry.lineAnnotationsBefore?.slice(),\n ];\n }\n\n redo():\n | [\n change: TextDocumentChange,\n selections?: EditorSelection[],\n lineAnnotations?: DiffLineAnnotation<LAnnotation>[],\n ]\n | undefined {\n const entry = this.#editStack.popRedoToUndo();\n if (entry === undefined) {\n return undefined;\n }\n const change = this.#applyResolvedEditsToBuffer(entry.forwardEdits);\n if (change === undefined) {\n return undefined;\n }\n this.#version = entry.versionAfter;\n return [\n change,\n entry.selectionsAfter?.slice(),\n entry.lineAnnotationsAfter?.slice(),\n ];\n }\n\n normalizePosition(position: Position): Position {\n const line = Math.max(0, Math.min(position.line, this.lineCount - 1));\n return {\n line,\n character: Math.max(\n 0,\n Math.min(position.character, this.getLineLength(line))\n ),\n };\n }\n\n #resolveEdit(edit: TextEdit): ResolvedTextEdit {\n let start = this.offsetAt(edit.range.start);\n let end = this.offsetAt(edit.range.end);\n if (start > end) {\n const t = start;\n start = end;\n end = t;\n }\n return { start, end, text: edit.newText };\n }\n\n #sortAndValidateResolvedEdits(edits: ResolvedTextEdit[]): ResolvedTextEdit[] {\n const sortedEdits = [...edits].sort((a, b) => a.start - b.start);\n for (let i = 0; i < sortedEdits.length - 1; i++) {\n if (sortedEdits[i].end > sortedEdits[i + 1].start) {\n throw new Error('Overlapping text edits are not supported');\n }\n }\n return sortedEdits;\n }\n\n #applyResolvedEditsToBuffer(edits: ResolvedTextEdit[]): TextDocumentChange {\n const previousLineCount = this.#pieceTable.lineCount;\n const editPositions = this.positionsAt(\n edits.flatMap((edit) => [edit.start, edit.end])\n );\n const changedLineRange = this.#computeChangedLineRange(\n edits,\n editPositions\n );\n const startPosition = editPositions[0];\n this.#pieceTable.applyEdits(edits);\n const lineCount = this.#pieceTable.lineCount;\n const change: TextDocumentChange = {\n startLine: changedLineRange.startLine,\n startCharacter: startPosition.character,\n endLine: Math.min(changedLineRange.endLine, Math.max(0, lineCount - 1)),\n previousLineCount,\n lineCount,\n lineDelta: lineCount - previousLineCount,\n changedLineRanges: changedLineRange.ranges,\n changedLineChanges: changedLineRange.changes,\n };\n return change;\n }\n\n #computeChangedLineRange(\n edits: ResolvedTextEdit[],\n editPositions: Position[]\n ): {\n startLine: number;\n endLine: number;\n ranges: [number, number][];\n changes: [startLine: number, endLine: number, lineDelta: number][];\n } {\n let startLine = Infinity;\n let endLine = 0;\n let lineDeltaBeforeEdit = 0;\n const ranges: [number, number][] = [];\n const changes: [startLine: number, endLine: number, lineDelta: number][] =\n [];\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i];\n const editStart = editPositions[i * 2];\n const editEnd = editPositions[i * 2 + 1];\n const editStartLine = editStart.line;\n const editEndLine = editEnd.line;\n const insertedLineSpan = countLineBreaks(edit.text);\n const changedStartLine = editStartLine + lineDeltaBeforeEdit;\n const changedEndLine = changedStartLine + insertedLineSpan;\n const lineDelta = insertedLineSpan - (editEndLine - editStartLine);\n startLine = Math.min(startLine, editStartLine);\n endLine = Math.max(endLine, changedEndLine);\n const lastRange = ranges[ranges.length - 1];\n if (lastRange !== undefined && changedStartLine <= lastRange[1] + 1) {\n ranges[ranges.length - 1] = [\n lastRange[0],\n Math.max(lastRange[1], changedEndLine),\n ];\n } else {\n ranges.push([changedStartLine, changedEndLine]);\n }\n changes.push([changedStartLine, changedEndLine, lineDelta]);\n lineDeltaBeforeEdit += lineDelta;\n }\n if (startLine === Infinity) {\n return {\n startLine: 0,\n endLine: 0,\n ranges: [[0, 0]],\n changes: [[0, 0, 0]],\n };\n }\n return { startLine, endLine, ranges, changes };\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textMeasure.d.ts","names":[],"sources":["../../src/editor/textMeasure.ts"],"mappings":";cAQa,OAAA;EAAA;EAAO;EASlB,EAAA;EASsB;EAPtB,OAAA;EAFA;EAIA,UAAA;EAAA;EAEA,UAAA;EAGA;EAAA,IAAA,CAAK,IAAA,EAAM,WAAW;EAAjB;;;;;;;;;;AAgJc;AAMrB;EA1FE,uBAAA;;EAcA,gBAAA,CAAiB,IAAA;EA4EiC;EAnElD,sBAAA,CAAuB,IAAA;EAmFsB;;;AAE/B;
|
|
1
|
+
{"version":3,"file":"textMeasure.d.ts","names":[],"sources":["../../src/editor/textMeasure.ts"],"mappings":";cAQa,OAAA;EAAA;EAAO;EASlB,EAAA;EASsB;EAPtB,OAAA;EAFA;EAIA,UAAA;EAAA;EAEA,UAAA;EAGA;EAAA,IAAA,CAAK,IAAA,EAAM,WAAW;EAAjB;;;;;;;;;;AAgJc;AAMrB;EA1FE,uBAAA;;EAcA,gBAAA,CAAiB,IAAA;EA4EiC;EAnElD,sBAAA,CAAuB,IAAA;EAmFsB;;;AAE/B;AA6ChB;EAtHE,mBAAA,CAAoB,IAAA;;;AAuHR;AA6Bd;;EAnGE,mBAAA;AAAA;AAmG8D;AAAA,iBA7FhD,uBAAA,CAAwB,IAAY;;iBAgBpC,+BAAA,CACd,IAAA,UACA,MAAc;;iBA6CA,4BAAA,CACd,IAAY;;;;;;;;iBA6BE,kBAAA,CAAmB,IAAA,UAAc,OAAe;;;;;;iBAwBhD,2BAAA,CACd,IAAA,UACA,OAAe"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { h, round } from "./utils.js";
|
|
1
|
+
import { getGraphemeSegmenter, h, round } from "./utils.js";
|
|
2
2
|
//#region src/editor/textMeasure.ts
|
|
3
3
|
const TEXT_WIDTH_CACHE_LIMIT = 4096;
|
|
4
4
|
var Metrics = class {
|
|
@@ -125,12 +125,22 @@ function needsDomTextMeasurement(text) {
|
|
|
125
125
|
function snapTextOffsetToUnicodeBoundary(text, offset) {
|
|
126
126
|
const boundedOffset = Math.max(0, Math.min(offset, text.length));
|
|
127
127
|
if (boundedOffset === 0 || boundedOffset === text.length || !needsDomTextMeasurement(text)) return boundedOffset;
|
|
128
|
-
const segmenter =
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
128
|
+
const segmenter = getGraphemeSegmenter();
|
|
129
|
+
if (segmenter !== void 0) {
|
|
130
|
+
for (const segment of segmenter.segment(text)) {
|
|
131
|
+
const segmentStart = segment.index;
|
|
132
|
+
const segmentEnd = segmentStart + segment.segment.length;
|
|
133
|
+
if (boundedOffset > segmentStart && boundedOffset < segmentEnd) return segmentEnd;
|
|
134
|
+
if (boundedOffset <= segmentStart) break;
|
|
135
|
+
}
|
|
136
|
+
return boundedOffset;
|
|
137
|
+
}
|
|
138
|
+
let segmentStart = 0;
|
|
139
|
+
for (const codePoint of text) {
|
|
140
|
+
const segmentEnd = segmentStart + codePoint.length;
|
|
132
141
|
if (boundedOffset > segmentStart && boundedOffset < segmentEnd) return segmentEnd;
|
|
133
142
|
if (boundedOffset <= segmentStart) break;
|
|
143
|
+
segmentStart = segmentEnd;
|
|
134
144
|
}
|
|
135
145
|
return boundedOffset;
|
|
136
146
|
}
|
|
@@ -138,8 +148,16 @@ function snapTextOffsetToUnicodeBoundary(text, offset) {
|
|
|
138
148
|
function getUnicodeMeasurementOffsets(text) {
|
|
139
149
|
if (!needsDomTextMeasurement(text)) return;
|
|
140
150
|
const offsets = [0];
|
|
141
|
-
const segmenter =
|
|
142
|
-
|
|
151
|
+
const segmenter = getGraphemeSegmenter();
|
|
152
|
+
if (segmenter !== void 0) {
|
|
153
|
+
for (const segment of segmenter.segment(text)) offsets.push(segment.index + segment.segment.length);
|
|
154
|
+
return offsets;
|
|
155
|
+
}
|
|
156
|
+
let offset = 0;
|
|
157
|
+
for (const codePoint of text) {
|
|
158
|
+
offset += codePoint.length;
|
|
159
|
+
offsets.push(offset);
|
|
160
|
+
}
|
|
143
161
|
return offsets;
|
|
144
162
|
}
|
|
145
163
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textMeasure.js","names":["#root","#canvasCtx","#font","#textWidthCache"],"sources":["../../src/editor/textMeasure.ts"],"sourcesContent":["import { 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 = new Intl.Segmenter(undefined, {\n granularity: 'grapheme',\n });\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\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 = new Intl.Segmenter(undefined, {\n granularity: 'grapheme',\n });\n for (const segment of segmenter.segment(text)) {\n offsets.push(segment.index + segment.segment.length);\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","#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,27 +1,30 @@
|
|
|
1
1
|
import { BaseCodeOptions, DiffsHighlighter, HighlightedToken, RenderRange } from "../types.js";
|
|
2
2
|
import { TextDocument, TextDocumentChange } from "./textDocument.js";
|
|
3
|
-
import { IGrammar, StateStack } from "shiki/textmate";
|
|
4
3
|
|
|
5
4
|
//#region src/editor/tokenzier.d.ts
|
|
6
5
|
interface EditorTokenizerProps {
|
|
7
6
|
highlighter: DiffsHighlighter;
|
|
8
7
|
textDocument: TextDocument<unknown>;
|
|
9
8
|
codeOptions: BaseCodeOptions;
|
|
9
|
+
matchBrackets?: boolean;
|
|
10
10
|
setStyle: (style: string) => void;
|
|
11
11
|
onDeferTokenize: (lines: Map<number, Array<HighlightedToken>>, themeType: 'dark' | 'light') => void;
|
|
12
|
+
onThemeChange?: () => void;
|
|
12
13
|
__debug?: boolean;
|
|
13
14
|
}
|
|
14
15
|
/** Stoppable code tokenizer for the editor */
|
|
15
16
|
declare class EditorTokenizer {
|
|
16
17
|
#private;
|
|
17
|
-
static TOKENIZE_TIME_LIMIT: number;
|
|
18
18
|
get themeType(): 'light' | 'dark';
|
|
19
|
+
getStringCommentRegexpRangesInLine(lineIndex: number): [number, number][] | null;
|
|
19
20
|
constructor({
|
|
20
21
|
codeOptions,
|
|
21
22
|
highlighter,
|
|
22
23
|
textDocument,
|
|
24
|
+
matchBrackets,
|
|
23
25
|
setStyle,
|
|
24
26
|
onDeferTokenize,
|
|
27
|
+
onThemeChange,
|
|
25
28
|
__debug
|
|
26
29
|
}: EditorTokenizerProps);
|
|
27
30
|
syncTheme(codeOptions: BaseCodeOptions): void;
|
|
@@ -32,11 +35,7 @@ declare class EditorTokenizer {
|
|
|
32
35
|
pauseBackgroundTokenize(): void;
|
|
33
36
|
resumeBackgroundTokenize(): void;
|
|
34
37
|
}
|
|
35
|
-
declare function tokenizeLine(grammar: IGrammar, colorMap: string[], lineText: string, stateStack: StateStack, timeLimit?: number): {
|
|
36
|
-
ruleStack: StateStack;
|
|
37
|
-
resolvedTokens: Array<HighlightedToken>;
|
|
38
|
-
};
|
|
39
38
|
declare function renderLineTokens(tokens: Array<HighlightedToken>, themeType: 'light' | 'dark'): (HTMLElement | string)[];
|
|
40
39
|
//#endregion
|
|
41
|
-
export { EditorTokenizer, EditorTokenizerProps, renderLineTokens
|
|
40
|
+
export { EditorTokenizer, EditorTokenizerProps, renderLineTokens };
|
|
42
41
|
//# sourceMappingURL=tokenzier.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokenzier.d.ts","names":[],"sources":["../../src/editor/tokenzier.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"tokenzier.d.ts","names":[],"sources":["../../src/editor/tokenzier.ts"],"mappings":";;;;UAmBiB,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;MA8DP,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;EAiFH,SAAA,CAAU,WAAA,EAAa,eAAA;EAkDvB,OAAA;EASA,QAAA,CACE,MAAA,EAAQ,kBAAA,EACR,WAAA,GAAc,WAAA,GACb,GAAA,SAAY,KAAA,CAAM,gBAAA;EAmMrB,kBAAA,CAAmB,WAAA,GAAc,WAAA;EAKjC,sBAAA;EAYA,uBAAA;EAYA,wBAAA;AAAA;AAAA,iBAkTc,gBAAA,CACd,MAAA,EAAQ,KAAA,CAAM,gBAAA,GACd,SAAA,sBACE,WAAA"}
|
package/dist/editor/tokenzier.js
CHANGED
|
@@ -2,9 +2,9 @@ import { DEFAULT_THEMES } from "../constants.js";
|
|
|
2
2
|
import { addEventListener, debounce, h } from "./utils.js";
|
|
3
3
|
import { EncodedTokenMetadata, INITIAL } from "shiki/textmate";
|
|
4
4
|
//#region src/editor/tokenzier.ts
|
|
5
|
+
const TOKENIZE_TIME_LIMIT = 500;
|
|
5
6
|
/** Stoppable code tokenizer for the editor */
|
|
6
|
-
var EditorTokenizer = class
|
|
7
|
-
static TOKENIZE_TIME_LIMIT = 500;
|
|
7
|
+
var EditorTokenizer = class {
|
|
8
8
|
#highlighter;
|
|
9
9
|
#grammar;
|
|
10
10
|
#mediaQueryList;
|
|
@@ -15,6 +15,8 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
15
15
|
#tokenizeMaxLineLength;
|
|
16
16
|
#setStyle;
|
|
17
17
|
#onDeferTokenize;
|
|
18
|
+
#onThemeChange;
|
|
19
|
+
#matchBrackets;
|
|
18
20
|
#debug;
|
|
19
21
|
#disposes;
|
|
20
22
|
#stateStack = [INITIAL];
|
|
@@ -24,6 +26,7 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
24
26
|
#backgroundJobId = 0;
|
|
25
27
|
#backgroundChangedLineRanges;
|
|
26
28
|
#backgroundChangedRangeIndex = 0;
|
|
29
|
+
#bracketIgnoredRanges = /* @__PURE__ */ new Map();
|
|
27
30
|
#isMessageListenerAttached = false;
|
|
28
31
|
#prebuildStateStack = debounce(async (renderRange) => {
|
|
29
32
|
const { startingLine = 0, totalLines = Infinity } = renderRange ?? {};
|
|
@@ -42,7 +45,19 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
42
45
|
get themeType() {
|
|
43
46
|
return this.#themeType;
|
|
44
47
|
}
|
|
45
|
-
|
|
48
|
+
getStringCommentRegexpRangesInLine(lineIndex) {
|
|
49
|
+
if (!this.#matchBrackets || lineIndex < 0 || lineIndex >= this.#textDocument.lineCount) return null;
|
|
50
|
+
this.#ensureGrammar();
|
|
51
|
+
if (this.#grammar === void 0) return null;
|
|
52
|
+
if (!this.#bracketIgnoredRanges.has(lineIndex)) {
|
|
53
|
+
this.#buildStateStack(lineIndex);
|
|
54
|
+
const state = this.#stateStack[lineIndex] ?? INITIAL;
|
|
55
|
+
const result = this.#tokenizeLineAt(lineIndex, state);
|
|
56
|
+
this.#stateStack[lineIndex + 1] = result.state;
|
|
57
|
+
}
|
|
58
|
+
return this.#bracketIgnoredRanges.get(lineIndex) ?? null;
|
|
59
|
+
}
|
|
60
|
+
constructor({ codeOptions, highlighter, textDocument, matchBrackets, setStyle, onDeferTokenize, onThemeChange, __debug }) {
|
|
46
61
|
const { themeType = "system", theme = DEFAULT_THEMES, tokenizeMaxLineLength = 1e3 } = codeOptions;
|
|
47
62
|
this.#mediaQueryList = window.matchMedia("(prefers-color-scheme: dark)");
|
|
48
63
|
if (themeType === "system") this.#themeType = this.#mediaQueryList.matches ? "dark" : "light";
|
|
@@ -67,8 +82,10 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
67
82
|
this.#tokenizeMaxLineLength = tokenizeMaxLineLength;
|
|
68
83
|
this.#setStyle = setStyle;
|
|
69
84
|
this.#onDeferTokenize = onDeferTokenize;
|
|
85
|
+
this.#onThemeChange = onThemeChange;
|
|
86
|
+
this.#matchBrackets = matchBrackets !== false;
|
|
70
87
|
this.#debug = __debug ?? false;
|
|
71
|
-
|
|
88
|
+
this.#ensureGrammar();
|
|
72
89
|
this.#colorMap = [];
|
|
73
90
|
this.#setTheme(typeof theme === "string" ? theme : theme[this.#themeType]);
|
|
74
91
|
}
|
|
@@ -78,6 +95,7 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
78
95
|
this.stopBackgroundTokenize();
|
|
79
96
|
this.#stateStack = [INITIAL];
|
|
80
97
|
if (this.#grammar !== void 0 && this.#textDocument.lineCount > 0) this.#scheduleBackgroundTokenize(0);
|
|
98
|
+
this.#onThemeChange?.();
|
|
81
99
|
}
|
|
82
100
|
syncTheme(codeOptions) {
|
|
83
101
|
const { themeType = "system", theme = DEFAULT_THEMES } = codeOptions;
|
|
@@ -95,6 +113,8 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
95
113
|
const cursorForeground = colors["editorCursor.foreground"];
|
|
96
114
|
const findMatchBackground = colors["editor.findMatchBackground"];
|
|
97
115
|
const findMatchHighlightBackground = colors["editor.findMatchHighlightBackground"];
|
|
116
|
+
const bracketMatchBackground = colors["editorBracketMatch.background"];
|
|
117
|
+
const bracketMatchBorder = colors["editorBracketMatch.border"];
|
|
98
118
|
const hintForeground = colors["editorHint.foreground"];
|
|
99
119
|
const infoForeground = colors["editorInfo.foreground"];
|
|
100
120
|
const warningForeground = colors["editorWarning.foreground"];
|
|
@@ -102,13 +122,15 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
102
122
|
this.#setStyle(`:host {
|
|
103
123
|
--diffs-editor-selection-bg: ${selectionBackground ?? "var(--diffs-line-bg)"};
|
|
104
124
|
--diffs-editor-line-highlight-bg: ${lineHighlightBackground ?? "var(--diffs-line-bg)"};
|
|
105
|
-
--diffs-editor-match-bg: ${findMatchBackground ?? "
|
|
106
|
-
--diffs-editor-match-highlight-bg: ${findMatchHighlightBackground ?? "
|
|
107
|
-
--diffs-editor-
|
|
108
|
-
--diffs-editor-
|
|
109
|
-
--diffs-editor-
|
|
110
|
-
--diffs-editor-
|
|
111
|
-
--diffs-editor-
|
|
125
|
+
--diffs-editor-match-bg: ${findMatchBackground ?? "initial"};
|
|
126
|
+
--diffs-editor-match-highlight-bg: ${findMatchHighlightBackground ?? "initial"};
|
|
127
|
+
--diffs-editor-bracket-match-bg: ${bracketMatchBackground ?? "initial"};
|
|
128
|
+
--diffs-editor-bracket-match-border: ${bracketMatchBorder ?? "initial"};
|
|
129
|
+
--diffs-editor-cursor-fg: ${cursorForeground ?? "initial"};
|
|
130
|
+
--diffs-editor-hint-fg: ${hintForeground ?? "initial"};
|
|
131
|
+
--diffs-editor-info-fg: ${infoForeground ?? "initial"};
|
|
132
|
+
--diffs-editor-warning-fg: ${warningForeground ?? "initial"};
|
|
133
|
+
--diffs-editor-error-fg: ${errorForeground ?? "initial"};
|
|
112
134
|
}`);
|
|
113
135
|
}
|
|
114
136
|
cleanUp() {
|
|
@@ -118,7 +140,11 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
118
140
|
this.#disposes = void 0;
|
|
119
141
|
}
|
|
120
142
|
tokenize(change, renderRange) {
|
|
121
|
-
|
|
143
|
+
this.#ensureGrammar();
|
|
144
|
+
if (this.#grammar === void 0 && !isGrammarlessLanguage(this.#textDocument.languageId)) throw new Error(`Grammar for language "${this.#textDocument.languageId}" not loaded`);
|
|
145
|
+
if (this.#matchBrackets) {
|
|
146
|
+
for (const line of this.#bracketIgnoredRanges.keys()) if (line >= change.startLine) this.#bracketIgnoredRanges.delete(line);
|
|
147
|
+
}
|
|
122
148
|
const { lineCount } = this.#textDocument;
|
|
123
149
|
const { startingLine = 0, totalLines = Infinity } = renderRange ?? {};
|
|
124
150
|
const renderRangeEndLine = totalLines === Infinity ? lineCount : Math.min(startingLine + totalLines, lineCount);
|
|
@@ -203,6 +229,7 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
203
229
|
return dirtyLines;
|
|
204
230
|
}
|
|
205
231
|
prebuildStateStack(renderRange) {
|
|
232
|
+
this.#ensureGrammar();
|
|
206
233
|
this.#prebuildStateStack(renderRange);
|
|
207
234
|
}
|
|
208
235
|
stopBackgroundTokenize() {
|
|
@@ -225,6 +252,9 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
225
252
|
this.#isPaused = false;
|
|
226
253
|
this.#postTokenizeMessage(this.#backgroundJobId);
|
|
227
254
|
}
|
|
255
|
+
#ensureGrammar() {
|
|
256
|
+
if (this.#grammar === void 0 && !isGrammarlessLanguage(this.#textDocument.languageId) && this.#highlighter.getLoadedLanguages().includes(this.#textDocument.languageId)) this.#grammar = this.#highlighter.getLanguage(this.#textDocument.languageId);
|
|
257
|
+
}
|
|
228
258
|
#attachMessageListener() {
|
|
229
259
|
if (this.#isMessageListenerAttached) return;
|
|
230
260
|
globalThis.addEventListener("message", this.#onMessage);
|
|
@@ -262,6 +292,7 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
262
292
|
const lineText = this.#textDocument.getLineText(line);
|
|
263
293
|
if (lineText.length > this.#tokenizeMaxLineLength) {
|
|
264
294
|
console.warn(`[diffs] Line(${line}) too long to tokenize: ${lineText.length}`);
|
|
295
|
+
this.#cacheBracketIgnoredRanges(line, null);
|
|
265
296
|
return {
|
|
266
297
|
resolvedTokens: [[
|
|
267
298
|
0,
|
|
@@ -271,20 +302,27 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
271
302
|
state
|
|
272
303
|
};
|
|
273
304
|
}
|
|
274
|
-
if (this.#grammar === void 0 || lineText === "" || lineText.trim() === "")
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
305
|
+
if (this.#grammar === void 0 || lineText === "" || lineText.trim() === "") {
|
|
306
|
+
this.#cacheBracketIgnoredRanges(line, null);
|
|
307
|
+
return {
|
|
308
|
+
resolvedTokens: [[
|
|
309
|
+
0,
|
|
310
|
+
"",
|
|
311
|
+
lineText
|
|
312
|
+
]],
|
|
313
|
+
state
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const result = tokenizeLine(this.#grammar, this.#colorMap, lineText, state, TOKENIZE_TIME_LIMIT, this.#matchBrackets);
|
|
317
|
+
this.#cacheBracketIgnoredRanges(line, result.bracketIgnoredRanges);
|
|
283
318
|
return {
|
|
284
319
|
resolvedTokens: result.resolvedTokens,
|
|
285
320
|
state: result.ruleStack
|
|
286
321
|
};
|
|
287
322
|
}
|
|
323
|
+
#cacheBracketIgnoredRanges(line, ranges) {
|
|
324
|
+
if (this.#matchBrackets) this.#bracketIgnoredRanges.set(line, ranges);
|
|
325
|
+
}
|
|
288
326
|
#buildStateStack(endAt) {
|
|
289
327
|
const boundedEndAt = Math.min(Math.max(0, endAt), this.#textDocument.lineCount);
|
|
290
328
|
if (this.#stateStack.length > boundedEndAt || this.#grammar === void 0) return;
|
|
@@ -293,7 +331,11 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
293
331
|
for (; line < boundedEndAt; line++) {
|
|
294
332
|
this.#stateStack[line] = state;
|
|
295
333
|
const lineText = this.#textDocument.getLineText(line);
|
|
296
|
-
if (lineText.length <= this.#tokenizeMaxLineLength && lineText !== "" && lineText.trim() !== "")
|
|
334
|
+
if (lineText.length <= this.#tokenizeMaxLineLength && lineText !== "" && lineText.trim() !== "") {
|
|
335
|
+
const result = tokenizeLine(this.#grammar, this.#colorMap, lineText, state, TOKENIZE_TIME_LIMIT, this.#matchBrackets);
|
|
336
|
+
this.#cacheBracketIgnoredRanges(line, result.bracketIgnoredRanges);
|
|
337
|
+
state = result.ruleStack;
|
|
338
|
+
} else this.#cacheBracketIgnoredRanges(line, null);
|
|
297
339
|
}
|
|
298
340
|
this.#stateStack[line] = state;
|
|
299
341
|
}
|
|
@@ -319,14 +361,18 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
319
361
|
"",
|
|
320
362
|
lineText
|
|
321
363
|
]]);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
364
|
+
this.#cacheBracketIgnoredRanges(line, null);
|
|
365
|
+
} else if (lineText === "" || lineText.trim() === "") {
|
|
366
|
+
lines.set(line, [[
|
|
367
|
+
0,
|
|
368
|
+
"",
|
|
369
|
+
lineText
|
|
370
|
+
]]);
|
|
371
|
+
this.#cacheBracketIgnoredRanges(line, null);
|
|
372
|
+
} else {
|
|
373
|
+
const ret = tokenizeLine(this.#grammar, this.#colorMap, lineText, state, TOKENIZE_TIME_LIMIT, this.#matchBrackets);
|
|
329
374
|
lines.set(line, ret.resolvedTokens);
|
|
375
|
+
this.#cacheBracketIgnoredRanges(line, ret.bracketIgnoredRanges);
|
|
330
376
|
state = ret.ruleStack;
|
|
331
377
|
}
|
|
332
378
|
this.#stateStack[line + 1] = state;
|
|
@@ -358,28 +404,31 @@ var EditorTokenizer = class EditorTokenizer {
|
|
|
358
404
|
this.#postTokenizeMessage(jobId);
|
|
359
405
|
}
|
|
360
406
|
};
|
|
361
|
-
function tokenizeLine(grammar, colorMap, lineText, stateStack, timeLimit) {
|
|
407
|
+
function tokenizeLine(grammar, colorMap, lineText, stateStack, timeLimit, collectBracketIgnoredRanges = true) {
|
|
362
408
|
const result = grammar.tokenizeLine2(lineText, stateStack, timeLimit);
|
|
363
409
|
if (result.stoppedEarly) console.warn(`[diffs] Time limit reached when tokenizing line: ${lineText.substring(0, 100)}`);
|
|
364
410
|
const rawTokens = result.tokens;
|
|
365
411
|
const tokensLength = rawTokens.length / 2;
|
|
366
412
|
const resolvedTokens = [];
|
|
413
|
+
const bracketIgnoredRanges = [];
|
|
367
414
|
for (let j = 0; j < tokensLength; j++) {
|
|
368
415
|
const offset = rawTokens[2 * j];
|
|
369
416
|
const nextOffset = j + 1 < tokensLength ? rawTokens[2 * j + 2] : lineText.length;
|
|
370
417
|
if (offset === nextOffset) continue;
|
|
371
418
|
const metadata = rawTokens[2 * j + 1];
|
|
372
|
-
const fg =
|
|
419
|
+
const fg = EncodedTokenMetadata.getForeground(metadata);
|
|
373
420
|
const tokenText = lineText.slice(offset, nextOffset);
|
|
374
421
|
resolvedTokens.push([
|
|
375
422
|
offset,
|
|
376
|
-
fg,
|
|
423
|
+
colorMap[fg],
|
|
377
424
|
tokenText
|
|
378
425
|
]);
|
|
426
|
+
if (collectBracketIgnoredRanges && EncodedTokenMetadata.getTokenType(metadata) > 0) bracketIgnoredRanges.push([offset, nextOffset]);
|
|
379
427
|
}
|
|
380
428
|
return {
|
|
381
429
|
ruleStack: result.ruleStack,
|
|
382
|
-
resolvedTokens
|
|
430
|
+
resolvedTokens,
|
|
431
|
+
bracketIgnoredRanges
|
|
383
432
|
};
|
|
384
433
|
}
|
|
385
434
|
function renderLineTokens(tokens, themeType) {
|
|
@@ -399,6 +448,6 @@ function isGrammarlessLanguage(languageId) {
|
|
|
399
448
|
return languageId === "text" || languageId === "ansi";
|
|
400
449
|
}
|
|
401
450
|
//#endregion
|
|
402
|
-
export { EditorTokenizer, renderLineTokens
|
|
451
|
+
export { EditorTokenizer, renderLineTokens };
|
|
403
452
|
|
|
404
453
|
//# sourceMappingURL=tokenzier.js.map
|