@pierre/diffs 1.2.5 → 1.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CodeView.d.ts +0 -1
- package/dist/components/CodeView.d.ts.map +1 -1
- package/dist/components/CodeView.js +0 -23
- package/dist/components/CodeView.js.map +1 -1
- package/dist/components/File.d.ts +7 -2
- package/dist/components/File.d.ts.map +1 -1
- package/dist/components/File.js +36 -2
- package/dist/components/File.js.map +1 -1
- package/dist/components/FileDiff.d.ts +8 -2
- package/dist/components/FileDiff.d.ts.map +1 -1
- package/dist/components/FileDiff.js +73 -1
- package/dist/components/FileDiff.js.map +1 -1
- package/dist/components/UnresolvedFile.js +2 -2
- package/dist/components/VirtualizedFile.d.ts +2 -1
- package/dist/components/VirtualizedFile.d.ts.map +1 -1
- package/dist/components/VirtualizedFile.js +48 -48
- package/dist/components/VirtualizedFile.js.map +1 -1
- package/dist/components/VirtualizedFileDiff.js +1 -6
- package/dist/components/VirtualizedFileDiff.js.map +1 -1
- package/dist/components/Virtualizer.d.ts +1 -1
- package/dist/components/Virtualizer.d.ts.map +1 -1
- package/dist/components/Virtualizer.js.map +1 -1
- package/dist/editor/command.d.ts +6 -0
- package/dist/editor/command.d.ts.map +1 -0
- package/dist/editor/command.js +31 -0
- package/dist/editor/command.js.map +1 -0
- package/dist/editor/css.d.ts +6 -0
- package/dist/editor/css.d.ts.map +1 -0
- package/dist/editor/css.js +218 -0
- package/dist/editor/css.js.map +1 -0
- package/dist/editor/editStack.d.ts +66 -0
- package/dist/editor/editStack.d.ts.map +1 -0
- package/dist/editor/editStack.js +218 -0
- package/dist/editor/editStack.js.map +1 -0
- package/dist/editor/editor.d.ts +22 -0
- package/dist/editor/editor.d.ts.map +1 -0
- package/dist/editor/editor.js +1323 -0
- package/dist/editor/editor.js.map +1 -0
- package/dist/editor/index.d.ts +3 -0
- package/dist/editor/index.js +4 -0
- package/dist/editor/lineAnnotations.d.ts +8 -0
- package/dist/editor/lineAnnotations.d.ts.map +1 -0
- package/dist/editor/lineAnnotations.js +32 -0
- package/dist/editor/lineAnnotations.js.map +1 -0
- package/dist/editor/pieceTable.d.ts +33 -0
- package/dist/editor/pieceTable.d.ts.map +1 -0
- package/dist/editor/pieceTable.js +590 -0
- package/dist/editor/pieceTable.js.map +1 -0
- package/dist/editor/platform.d.ts +12 -0
- package/dist/editor/platform.d.ts.map +1 -0
- package/dist/editor/platform.js +44 -0
- package/dist/editor/platform.js.map +1 -0
- package/dist/editor/quickEdit.d.ts +29 -0
- package/dist/editor/quickEdit.d.ts.map +1 -0
- package/dist/editor/quickEdit.js +81 -0
- package/dist/editor/quickEdit.js.map +1 -0
- package/dist/editor/searchPanel.d.ts +30 -0
- package/dist/editor/searchPanel.d.ts.map +1 -0
- package/dist/editor/searchPanel.js +219 -0
- package/dist/editor/searchPanel.js.map +1 -0
- package/dist/editor/selection.d.ts +126 -0
- package/dist/editor/selection.d.ts.map +1 -0
- package/dist/editor/selection.js +900 -0
- package/dist/editor/selection.js.map +1 -0
- package/dist/editor/textDocument.d.ts +139 -0
- package/dist/editor/textDocument.d.ts.map +1 -0
- package/dist/editor/textDocument.js +202 -0
- package/dist/editor/textDocument.js.map +1 -0
- package/dist/editor/textMeasure.d.ts +32 -0
- package/dist/editor/textMeasure.d.ts.map +1 -0
- package/dist/editor/textMeasure.js +108 -0
- package/dist/editor/textMeasure.js.map +1 -0
- package/dist/editor/tokenzier.d.ts +37 -0
- package/dist/editor/tokenzier.d.ts.map +1 -0
- package/dist/editor/tokenzier.js +348 -0
- package/dist/editor/tokenzier.js.map +1 -0
- package/dist/editor/utils.d.ts +16 -0
- package/dist/editor/utils.d.ts.map +1 -0
- package/dist/editor/utils.js +37 -0
- package/dist/editor/utils.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/react/EditorContext.d.ts +16 -0
- package/dist/react/EditorContext.d.ts.map +1 -0
- package/dist/react/EditorContext.js +26 -0
- package/dist/react/EditorContext.js.map +1 -0
- package/dist/react/File.d.ts +2 -1
- package/dist/react/File.d.ts.map +1 -1
- package/dist/react/File.js +3 -2
- package/dist/react/File.js.map +1 -1
- package/dist/react/FileDiff.d.ts +3 -1
- package/dist/react/FileDiff.d.ts.map +1 -1
- package/dist/react/FileDiff.js +3 -2
- package/dist/react/FileDiff.js.map +1 -1
- package/dist/react/MultiFileDiff.d.ts +3 -1
- package/dist/react/MultiFileDiff.d.ts.map +1 -1
- package/dist/react/MultiFileDiff.js +3 -2
- package/dist/react/MultiFileDiff.js.map +1 -1
- package/dist/react/PatchDiff.d.ts +3 -1
- package/dist/react/PatchDiff.d.ts.map +1 -1
- package/dist/react/PatchDiff.js +3 -2
- package/dist/react/PatchDiff.js.map +1 -1
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +2 -1
- package/dist/react/jsx.d.ts +0 -1
- package/dist/react/jsx.d.ts.map +1 -1
- package/dist/react/types.d.ts +1 -0
- package/dist/react/types.d.ts.map +1 -1
- package/dist/react/types.js +0 -1
- package/dist/react/utils/useFileDiffInstance.d.ts +3 -1
- package/dist/react/utils/useFileDiffInstance.d.ts.map +1 -1
- package/dist/react/utils/useFileDiffInstance.js +31 -5
- package/dist/react/utils/useFileDiffInstance.js.map +1 -1
- package/dist/react/utils/useFileInstance.d.ts +4 -1
- package/dist/react/utils/useFileInstance.d.ts.map +1 -1
- package/dist/react/utils/useFileInstance.js +30 -5
- package/dist/react/utils/useFileInstance.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.map +1 -1
- package/dist/renderers/FileRenderer.d.ts +5 -1
- package/dist/renderers/FileRenderer.d.ts.map +1 -1
- package/dist/renderers/FileRenderer.js +108 -41
- package/dist/renderers/FileRenderer.js.map +1 -1
- package/dist/ssr/index.d.ts +2 -2
- package/dist/style.js +1 -1
- package/dist/style.js.map +1 -1
- package/dist/types.d.ts +45 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/cleanLastNewline.js +6 -1
- package/dist/utils/cleanLastNewline.js.map +1 -1
- package/dist/utils/computeFileOffsets.d.ts +13 -0
- package/dist/utils/computeFileOffsets.d.ts.map +1 -0
- package/dist/utils/computeFileOffsets.js +33 -0
- package/dist/utils/computeFileOffsets.js.map +1 -0
- package/dist/utils/createTransformerWithState.js +9 -0
- package/dist/utils/createTransformerWithState.js.map +1 -1
- package/dist/utils/renderDiffWithHighlighter.js +1 -1
- package/dist/utils/renderFileWithHighlighter.js +5 -14
- package/dist/utils/renderFileWithHighlighter.js.map +1 -1
- package/dist/worker/WorkerPoolManager.js +1 -1
- package/dist/worker/{wasm-BaDzIkIn.js → wasm-D4DU5jgR.js} +2 -2
- package/dist/worker/wasm-D4DU5jgR.js.map +1 -0
- package/dist/worker/worker-portable.js +138 -153
- package/dist/worker/worker-portable.js.map +1 -1
- package/dist/worker/worker.js +41 -64
- package/dist/worker/worker.js.map +1 -1
- package/package.json +28 -20
- package/dist/utils/iterateOverFile.d.ts +0 -50
- package/dist/utils/iterateOverFile.d.ts.map +0 -1
- package/dist/utils/iterateOverFile.js +0 -49
- package/dist/utils/iterateOverFile.js.map +0 -1
- package/dist/worker/wasm-BaDzIkIn.js.map +0 -1
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
import { applyDocumentChangeToLineAnnotations } from "./lineAnnotations.js";
|
|
2
|
+
|
|
3
|
+
//#region src/editor/selection.ts
|
|
4
|
+
const DirectionBackward = -1;
|
|
5
|
+
const DirectionNone = 0;
|
|
6
|
+
const DirectionForward = 1;
|
|
7
|
+
/**
|
|
8
|
+
* Converts a selection from a web selection to an editor selection.
|
|
9
|
+
*/
|
|
10
|
+
function convertSelection(range, direction = DirectionNone) {
|
|
11
|
+
const start = boundaryToPosition(range.startContainer, range.startOffset);
|
|
12
|
+
const end = boundaryToPosition(range.endContainer, range.endOffset);
|
|
13
|
+
if (start === null || end === null) return;
|
|
14
|
+
return {
|
|
15
|
+
start,
|
|
16
|
+
end,
|
|
17
|
+
direction
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolves the indent edits for a selection.
|
|
22
|
+
*/
|
|
23
|
+
function resolveIndentEdits(textDocument, selection, tabSize, outdent) {
|
|
24
|
+
if (textDocument === void 0) return [[], selection];
|
|
25
|
+
const { start, end } = selection;
|
|
26
|
+
const edits = [];
|
|
27
|
+
let newSelection = { ...selection };
|
|
28
|
+
let endLine = end.line;
|
|
29
|
+
if (start.line < end.line && end.character === 0) endLine--;
|
|
30
|
+
for (let line = start.line; line <= endLine; line++) {
|
|
31
|
+
const lineText = textDocument.getLineText(line);
|
|
32
|
+
if (lineText === void 0) continue;
|
|
33
|
+
const indentUnit = lineText.startsWith(" ") ? " " : " ".repeat(tabSize);
|
|
34
|
+
let deleteLength = 0;
|
|
35
|
+
let newText = indentUnit;
|
|
36
|
+
if (outdent) {
|
|
37
|
+
if (lineText.startsWith(" ")) deleteLength = 1;
|
|
38
|
+
else if (lineText.startsWith(" ")) {
|
|
39
|
+
const leadingSpacesLength = lineText.length - lineText.trimStart().length;
|
|
40
|
+
deleteLength = Math.min(indentUnit.length, leadingSpacesLength);
|
|
41
|
+
}
|
|
42
|
+
if (deleteLength === 0) continue;
|
|
43
|
+
newText = "";
|
|
44
|
+
}
|
|
45
|
+
edits.push({
|
|
46
|
+
range: {
|
|
47
|
+
start: {
|
|
48
|
+
line,
|
|
49
|
+
character: 0
|
|
50
|
+
},
|
|
51
|
+
end: {
|
|
52
|
+
line,
|
|
53
|
+
character: deleteLength
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
newText
|
|
57
|
+
});
|
|
58
|
+
const delta = newText.length - deleteLength;
|
|
59
|
+
if (line === start.line) newSelection = {
|
|
60
|
+
...newSelection,
|
|
61
|
+
start: {
|
|
62
|
+
...start,
|
|
63
|
+
character: Math.max(0, start.character + delta)
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
if (line === end.line) newSelection = {
|
|
67
|
+
...newSelection,
|
|
68
|
+
end: {
|
|
69
|
+
...end,
|
|
70
|
+
character: Math.max(0, end.character + delta)
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return [edits, newSelection];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Maps the cursor move to all selections.
|
|
78
|
+
*/
|
|
79
|
+
function mapCursorMove(textDocument, selections, shortcut) {
|
|
80
|
+
const lineCount = textDocument.lineCount;
|
|
81
|
+
return selections.map((selection) => {
|
|
82
|
+
let { line, character } = shortcut === "up" || shortcut === "left" ? selection.start : selection.end;
|
|
83
|
+
if (shortcut === "textStart" || shortcut === "start" || shortcut === "end") {
|
|
84
|
+
if (shortcut === "textStart") {
|
|
85
|
+
const indent = getLeadingSpaces(textDocument.getLineText(line));
|
|
86
|
+
character = character === indent ? 0 : indent;
|
|
87
|
+
} else character = shortcut === "start" ? 0 : textDocument.getLineText(line).length;
|
|
88
|
+
if (selection.direction === DirectionBackward) line = selection.start.line;
|
|
89
|
+
else line = selection.end.line;
|
|
90
|
+
} else if (shortcut === "up") line = Math.max(0, line - 1);
|
|
91
|
+
else if (shortcut === "down") line = Math.min(Math.max(lineCount - 1, 0), line + 1);
|
|
92
|
+
else if (isCollapsedSelection(selection)) if (shortcut === "left") {
|
|
93
|
+
character--;
|
|
94
|
+
if (character < 0) if (line === 0) character = 0;
|
|
95
|
+
else {
|
|
96
|
+
line = Math.max(0, line - 1);
|
|
97
|
+
character = textDocument.getLineText(line).length;
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
character++;
|
|
101
|
+
if (character > textDocument.getLineText(line).length) if (line === lineCount - 1) character--;
|
|
102
|
+
else {
|
|
103
|
+
line = Math.min(Math.max(lineCount - 1, 0), line + 1);
|
|
104
|
+
character = 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const pos = {
|
|
108
|
+
line,
|
|
109
|
+
character
|
|
110
|
+
};
|
|
111
|
+
return {
|
|
112
|
+
start: pos,
|
|
113
|
+
end: pos,
|
|
114
|
+
direction: DirectionNone
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Same as mapCursorMove, but with shift key pressed.
|
|
120
|
+
*/
|
|
121
|
+
function mapSelectionShift(textDocument, selections, shortcut) {
|
|
122
|
+
return selections.map((selection) => {
|
|
123
|
+
const [anchorOffset, focusOffset] = getSelectionAnchorAndFocusOffsets(textDocument, selection);
|
|
124
|
+
const focusPosition = textDocument.positionAt(focusOffset);
|
|
125
|
+
const [movedFocusSelection] = mapCursorMove(textDocument, [{
|
|
126
|
+
start: focusPosition,
|
|
127
|
+
end: focusPosition,
|
|
128
|
+
direction: DirectionNone
|
|
129
|
+
}], shortcut);
|
|
130
|
+
return createSelectionFromAnchorAndFocusOffsets(textDocument, anchorOffset, textDocument.offsetAt(movedFocusSelection.start));
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Applies a text change to the given text document
|
|
135
|
+
*/
|
|
136
|
+
function applyTextChangeToSelections(textDocument, selections, edit, lineAnnotations, tabSize = 2) {
|
|
137
|
+
if (selections[selections.length - 1] === void 0) return { nextSelections: [] };
|
|
138
|
+
const selectionPositions = [];
|
|
139
|
+
for (const selection of selections) selectionPositions.push(selection.start, selection.end);
|
|
140
|
+
const selectionOffsets = textDocument.offsetsAt(selectionPositions);
|
|
141
|
+
const primaryStartOffset = selectionOffsets[(selections.length - 1) * 2];
|
|
142
|
+
const primaryEndOffset = selectionOffsets[(selections.length - 1) * 2 + 1];
|
|
143
|
+
const ordered = [];
|
|
144
|
+
let isAlreadyOrdered = true;
|
|
145
|
+
for (let index = 0; index < selections.length; index++) {
|
|
146
|
+
const entry = {
|
|
147
|
+
index,
|
|
148
|
+
start: selectionOffsets[index * 2],
|
|
149
|
+
end: selectionOffsets[index * 2 + 1]
|
|
150
|
+
};
|
|
151
|
+
const previous = ordered[ordered.length - 1];
|
|
152
|
+
if (previous !== void 0 && (entry.start < previous.start || entry.start === previous.start && entry.end < previous.end)) isAlreadyOrdered = false;
|
|
153
|
+
ordered.push(entry);
|
|
154
|
+
}
|
|
155
|
+
if (!isAlreadyOrdered) ordered.sort((a, b) => {
|
|
156
|
+
const startOrder = a.start - b.start;
|
|
157
|
+
if (startOrder !== 0) return startOrder;
|
|
158
|
+
const endOrder = a.end - b.end;
|
|
159
|
+
if (endOrder !== 0) return endOrder;
|
|
160
|
+
return a.index - b.index;
|
|
161
|
+
});
|
|
162
|
+
const adjustedChange = normalizeLeadingIndentForChange(textDocument, edit, tabSize);
|
|
163
|
+
const edits = [];
|
|
164
|
+
const nextSelectionOffsets = Array.from({ length: selections.length });
|
|
165
|
+
let offsetDelta = 0;
|
|
166
|
+
let mergedGroup;
|
|
167
|
+
const finalizeMergedGroup = () => {
|
|
168
|
+
if (mergedGroup === void 0) return;
|
|
169
|
+
const perGroupChange = normalizeLeadingIndentForChange(textDocument, {
|
|
170
|
+
start: mergedGroup.start,
|
|
171
|
+
end: mergedGroup.end,
|
|
172
|
+
text: adjustedChange.text
|
|
173
|
+
}, tabSize);
|
|
174
|
+
const newText = expandSingleNewlineInsert(textDocument, perGroupChange.text, perGroupChange.start);
|
|
175
|
+
edits.push({
|
|
176
|
+
start: perGroupChange.start,
|
|
177
|
+
end: perGroupChange.end,
|
|
178
|
+
text: newText
|
|
179
|
+
});
|
|
180
|
+
const nextOffsets = [mergedGroup.start + offsetDelta + newText.length, mergedGroup.start + offsetDelta + newText.length];
|
|
181
|
+
for (const index of mergedGroup.indices) nextSelectionOffsets[index] = nextOffsets;
|
|
182
|
+
offsetDelta += newText.length - (perGroupChange.end - perGroupChange.start);
|
|
183
|
+
mergedGroup = void 0;
|
|
184
|
+
};
|
|
185
|
+
for (const entry of ordered) {
|
|
186
|
+
const startOffset = Math.max(0, entry.start + (adjustedChange.start - primaryStartOffset));
|
|
187
|
+
const endOffset = Math.max(startOffset, entry.end + (adjustedChange.end - primaryEndOffset));
|
|
188
|
+
if (mergedGroup !== void 0 && startOffset < mergedGroup.end) {
|
|
189
|
+
mergedGroup.end = Math.max(mergedGroup.end, endOffset);
|
|
190
|
+
mergedGroup.indices.push(entry.index);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
finalizeMergedGroup();
|
|
194
|
+
mergedGroup = {
|
|
195
|
+
start: startOffset,
|
|
196
|
+
end: endOffset,
|
|
197
|
+
indices: [entry.index]
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
finalizeMergedGroup();
|
|
201
|
+
const change = textDocument.applyResolvedEdits(edits, true, selections);
|
|
202
|
+
const nextSelections = createSelectionsFromOffsetPairs(textDocument, nextSelectionOffsets.map((offsets) => {
|
|
203
|
+
if (offsets === void 0) throw new Error("Missing next selection offsets");
|
|
204
|
+
return offsets;
|
|
205
|
+
}));
|
|
206
|
+
textDocument.setLastUndoSelectionsAfter(nextSelections);
|
|
207
|
+
if (change !== void 0 && lineAnnotations !== void 0) {
|
|
208
|
+
const nextLineAnnotations = applyDocumentChangeToLineAnnotations(change, lineAnnotations);
|
|
209
|
+
if (nextLineAnnotations !== void 0) textDocument.setLastUndoLineAnnotations(lineAnnotations, nextLineAnnotations);
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
nextSelections,
|
|
213
|
+
change
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Applies a text replace to multiple selections.
|
|
218
|
+
*/
|
|
219
|
+
function applyTextReplaceToSelections(textDocument, selections, texts, lineAnnotations) {
|
|
220
|
+
if (selections.length !== texts.length) throw new Error("Selection text replacements must match the selection count");
|
|
221
|
+
const selectionPositions = [];
|
|
222
|
+
for (const selection of selections) selectionPositions.push(selection.start, selection.end);
|
|
223
|
+
const selectionOffsets = textDocument.offsetsAt(selectionPositions);
|
|
224
|
+
const ordered = [];
|
|
225
|
+
let isAlreadyOrdered = true;
|
|
226
|
+
for (let index = 0; index < selections.length; index++) {
|
|
227
|
+
const entry = {
|
|
228
|
+
index,
|
|
229
|
+
start: selectionOffsets[index * 2],
|
|
230
|
+
end: selectionOffsets[index * 2 + 1],
|
|
231
|
+
text: texts[index]
|
|
232
|
+
};
|
|
233
|
+
const previous = ordered[ordered.length - 1];
|
|
234
|
+
if (previous !== void 0 && (entry.start < previous.start || entry.start === previous.start && entry.end < previous.end)) isAlreadyOrdered = false;
|
|
235
|
+
ordered.push(entry);
|
|
236
|
+
}
|
|
237
|
+
if (!isAlreadyOrdered) ordered.sort((a, b) => {
|
|
238
|
+
const startOrder = a.start - b.start;
|
|
239
|
+
if (startOrder !== 0) return startOrder;
|
|
240
|
+
const endOrder = a.end - b.end;
|
|
241
|
+
if (endOrder !== 0) return endOrder;
|
|
242
|
+
return a.index - b.index;
|
|
243
|
+
});
|
|
244
|
+
const edits = [];
|
|
245
|
+
const nextSelectionOffsets = Array.from({ length: selections.length });
|
|
246
|
+
let offsetDelta = 0;
|
|
247
|
+
let previousEditEnd = -1;
|
|
248
|
+
for (const entry of ordered) {
|
|
249
|
+
if (entry.start < previousEditEnd) throw new Error("Overlapping multi-selection edits are not supported");
|
|
250
|
+
previousEditEnd = entry.end;
|
|
251
|
+
const newText = expandSingleNewlineInsert(textDocument, entry.text, entry.start);
|
|
252
|
+
edits.push({
|
|
253
|
+
start: entry.start,
|
|
254
|
+
end: entry.end,
|
|
255
|
+
text: newText
|
|
256
|
+
});
|
|
257
|
+
nextSelectionOffsets[entry.index] = entry.start + offsetDelta + newText.length;
|
|
258
|
+
offsetDelta += newText.length - (entry.end - entry.start);
|
|
259
|
+
}
|
|
260
|
+
const change = textDocument.applyResolvedEdits(edits, true, selections);
|
|
261
|
+
const nextSelections = createSelectionsFromOffsetPairs(textDocument, nextSelectionOffsets.map((offset) => [offset, offset]));
|
|
262
|
+
textDocument.setLastUndoSelectionsAfter(nextSelections);
|
|
263
|
+
if (change !== void 0 && lineAnnotations !== void 0) {
|
|
264
|
+
const nextLineAnnotations = applyDocumentChangeToLineAnnotations(change, lineAnnotations);
|
|
265
|
+
if (nextLineAnnotations !== void 0) textDocument.setLastUndoLineAnnotations(lineAnnotations, nextLineAnnotations);
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
nextSelections,
|
|
269
|
+
change
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Swaps the two characters adjacent to a collapsed selection, matching browser
|
|
274
|
+
* insertTranspose (Ctrl+T) behavior.
|
|
275
|
+
*/
|
|
276
|
+
function applyTransposeToSelections(textDocument, selections, lineAnnotations) {
|
|
277
|
+
const text = textDocument.getText();
|
|
278
|
+
const edits = [];
|
|
279
|
+
const nextOffsetPairs = [];
|
|
280
|
+
for (const selection of selections) {
|
|
281
|
+
const [anchor, focus] = getSelectionAnchorAndFocusOffsets(textDocument, selection);
|
|
282
|
+
if (!isCollapsedSelection(selection)) {
|
|
283
|
+
nextOffsetPairs.push([anchor, focus]);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const { line, character } = selection.start;
|
|
287
|
+
const offset = anchor;
|
|
288
|
+
const lineLength = textDocument.getLineText(line).length;
|
|
289
|
+
let edit;
|
|
290
|
+
if (character > 0 && character < lineLength) {
|
|
291
|
+
edit = {
|
|
292
|
+
start: offset - 1,
|
|
293
|
+
end: offset + 1,
|
|
294
|
+
text: text[offset] + text[offset - 1]
|
|
295
|
+
};
|
|
296
|
+
nextOffsetPairs.push([offset + 1, offset + 1]);
|
|
297
|
+
} else if (character === lineLength && lineLength >= 2) {
|
|
298
|
+
edit = {
|
|
299
|
+
start: offset - 2,
|
|
300
|
+
end: offset,
|
|
301
|
+
text: text[offset - 1] + text[offset - 2]
|
|
302
|
+
};
|
|
303
|
+
nextOffsetPairs.push([offset, offset]);
|
|
304
|
+
} else if (character === 0 && line > 0 && lineLength > 0) {
|
|
305
|
+
const prevLine = line - 1;
|
|
306
|
+
const prevLength = textDocument.getLineText(prevLine).length;
|
|
307
|
+
const prevEnd = textDocument.offsetAt({
|
|
308
|
+
line: prevLine,
|
|
309
|
+
character: prevLength
|
|
310
|
+
});
|
|
311
|
+
const prevStart = prevLength > 0 ? prevEnd - 1 : prevEnd;
|
|
312
|
+
edit = {
|
|
313
|
+
start: prevStart,
|
|
314
|
+
end: offset + 1,
|
|
315
|
+
text: text[offset] + text.slice(prevEnd, offset) + text.slice(prevStart, prevEnd)
|
|
316
|
+
};
|
|
317
|
+
nextOffsetPairs.push([offset + 1, offset + 1]);
|
|
318
|
+
} else {
|
|
319
|
+
nextOffsetPairs.push([anchor, focus]);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
edits.push(edit);
|
|
323
|
+
}
|
|
324
|
+
if (edits.length === 0) return { nextSelections: selections };
|
|
325
|
+
edits.sort((a, b) => a.start - b.start);
|
|
326
|
+
for (let index = 1; index < edits.length; index++) if (edits[index].start < edits[index - 1].end) throw new Error("Overlapping multi-selection edits are not supported");
|
|
327
|
+
const change = textDocument.applyResolvedEdits(edits, true, selections);
|
|
328
|
+
const nextSelections = createSelectionsFromOffsetPairs(textDocument, nextOffsetPairs);
|
|
329
|
+
textDocument.setLastUndoSelectionsAfter(nextSelections);
|
|
330
|
+
if (change !== void 0 && lineAnnotations !== void 0) {
|
|
331
|
+
const nextLineAnnotations = applyDocumentChangeToLineAnnotations(change, lineAnnotations);
|
|
332
|
+
if (nextLineAnnotations !== void 0) textDocument.setLastUndoLineAnnotations(lineAnnotations, nextLineAnnotations);
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
nextSelections,
|
|
336
|
+
change
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Deletes from each selection to the end of its line, including the line break
|
|
341
|
+
* when the caret is already at the end of a non-final line. Non-collapsed
|
|
342
|
+
* selections delete their selected text instead.
|
|
343
|
+
*/
|
|
344
|
+
function applyDeleteHardLineForwardToSelections(textDocument, selections, lineAnnotations) {
|
|
345
|
+
const deleteSelections = selections.map((selection) => {
|
|
346
|
+
const range = resolveDeleteHardLineForwardRange(textDocument, selection);
|
|
347
|
+
return {
|
|
348
|
+
start: range.start,
|
|
349
|
+
end: range.end,
|
|
350
|
+
direction: DirectionNone
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
if (!deleteSelections.some((selection) => comparePosition(selection.start, selection.end) !== 0)) return { nextSelections: selections };
|
|
354
|
+
return applyTextReplaceToSelections(textDocument, deleteSelections, deleteSelections.map(() => ""), lineAnnotations);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Checks if a selection is collapsed.
|
|
358
|
+
*/
|
|
359
|
+
function isCollapsedSelection(selection) {
|
|
360
|
+
return selection.start.line === selection.end.line && selection.start.character === selection.end.character;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Returns the caret (focus) position for a selection.
|
|
364
|
+
*/
|
|
365
|
+
function getCaretPosition(selection) {
|
|
366
|
+
const { start, end, direction } = selection;
|
|
367
|
+
return direction === DirectionBackward ? start : end;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Checks if a line is editable.
|
|
371
|
+
*/
|
|
372
|
+
function isLineEditable(lineType) {
|
|
373
|
+
return lineType === "context" || lineType === "change-addition" || lineType === "context-expanded";
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Checks whether selections `a` and `b` intersect.
|
|
377
|
+
*/
|
|
378
|
+
function selectionIntersects(a, b) {
|
|
379
|
+
const aCollapsed = isCollapsedSelection(a);
|
|
380
|
+
const bCollapsed = isCollapsedSelection(b);
|
|
381
|
+
if (aCollapsed && bCollapsed) return comparePosition(a.start, b.start) === 0;
|
|
382
|
+
if (aCollapsed) return comparePosition(b.start, a.start) <= 0 && comparePosition(a.start, b.end) <= 0;
|
|
383
|
+
if (bCollapsed) return comparePosition(a.start, b.start) <= 0 && comparePosition(b.start, a.end) <= 0;
|
|
384
|
+
return comparePosition(a.start, b.end) < 0 && comparePosition(b.start, a.end) < 0;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Compares two positions.
|
|
388
|
+
*/
|
|
389
|
+
function comparePosition(a, b) {
|
|
390
|
+
if (a.line !== b.line) return a.line - b.line;
|
|
391
|
+
return a.character - b.character;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Creates a selection from anchor and focus offsets.
|
|
395
|
+
*/
|
|
396
|
+
function createSelectionFromAnchorAndFocusOffsets(textDocument, anchorOffset, focusOffset) {
|
|
397
|
+
const direction = anchorOffset === focusOffset ? DirectionNone : anchorOffset < focusOffset ? DirectionForward : DirectionBackward;
|
|
398
|
+
const start = Math.min(anchorOffset, focusOffset);
|
|
399
|
+
const end = Math.max(anchorOffset, focusOffset);
|
|
400
|
+
return {
|
|
401
|
+
start: textDocument.positionAt(start),
|
|
402
|
+
end: textDocument.positionAt(end),
|
|
403
|
+
direction
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Creates a selection from a anchor and focus selection.
|
|
408
|
+
*/
|
|
409
|
+
function createSelectionFrom(anchorSelection, focusSelection) {
|
|
410
|
+
const anchor = anchorSelection.direction === DirectionBackward ? anchorSelection.end : anchorSelection.start;
|
|
411
|
+
const currentStartOrder = comparePosition(anchor, focusSelection.start);
|
|
412
|
+
const currentEndOrder = comparePosition(anchor, focusSelection.end);
|
|
413
|
+
let focus = focusSelection.end;
|
|
414
|
+
if (currentStartOrder <= 0) focus = focusSelection.end;
|
|
415
|
+
else if (currentEndOrder >= 0) focus = focusSelection.start;
|
|
416
|
+
else focus = currentStartOrder === 0 ? focusSelection.end : focusSelection.start;
|
|
417
|
+
const anchorVsFocus = comparePosition(anchor, focus);
|
|
418
|
+
return {
|
|
419
|
+
start: anchorVsFocus <= 0 ? anchor : focus,
|
|
420
|
+
end: anchorVsFocus <= 0 ? focus : anchor,
|
|
421
|
+
direction: anchorVsFocus === 0 ? DirectionNone : anchorVsFocus < 0 ? DirectionForward : DirectionBackward
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Extends or shrinks the selection `original` using the endpoints of `target`, \
|
|
426
|
+
* matching contenteditable shift + click extend behavior.
|
|
427
|
+
*/
|
|
428
|
+
function extendSelection(original, target) {
|
|
429
|
+
const leftExtended = comparePosition(target.start, original.start) < 0;
|
|
430
|
+
const rightExtended = comparePosition(target.end, original.end) > 0;
|
|
431
|
+
if (leftExtended && !rightExtended) return {
|
|
432
|
+
start: target.start,
|
|
433
|
+
end: original.end,
|
|
434
|
+
direction: DirectionBackward
|
|
435
|
+
};
|
|
436
|
+
if (rightExtended && !leftExtended) return {
|
|
437
|
+
start: original.start,
|
|
438
|
+
end: target.end,
|
|
439
|
+
direction: DirectionForward
|
|
440
|
+
};
|
|
441
|
+
if (original.direction === DirectionBackward) return {
|
|
442
|
+
start: target.start,
|
|
443
|
+
end: original.end,
|
|
444
|
+
direction: comparePosition(target.start, original.end) === 0 ? DirectionNone : DirectionBackward
|
|
445
|
+
};
|
|
446
|
+
return {
|
|
447
|
+
start: original.start,
|
|
448
|
+
end: target.end,
|
|
449
|
+
direction: comparePosition(original.start, target.end) === 0 ? DirectionNone : DirectionForward
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Extends multiple selections.
|
|
454
|
+
*/
|
|
455
|
+
function extendSelections(selections, target) {
|
|
456
|
+
return mergeOverlappingSelections(selections.map((selection) => {
|
|
457
|
+
return extendSelection(selection, target);
|
|
458
|
+
}));
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Merges overlapping selections.
|
|
462
|
+
*/
|
|
463
|
+
function mergeOverlappingSelections(selections) {
|
|
464
|
+
if (selections.length <= 1) return selections;
|
|
465
|
+
const selected = /* @__PURE__ */ new Set();
|
|
466
|
+
const accepted = [];
|
|
467
|
+
for (let i = selections.length - 1; i >= 0; i--) {
|
|
468
|
+
const selection = selections[i];
|
|
469
|
+
if (selection === void 0) continue;
|
|
470
|
+
let left = 0;
|
|
471
|
+
let right = accepted.length;
|
|
472
|
+
while (left < right) {
|
|
473
|
+
const mid = Math.floor((left + right) / 2);
|
|
474
|
+
const candidate = accepted[mid]?.selection;
|
|
475
|
+
if (candidate === void 0) break;
|
|
476
|
+
if (comparePosition(candidate.start, selection.start) < 0) left = mid + 1;
|
|
477
|
+
else right = mid;
|
|
478
|
+
}
|
|
479
|
+
const previous = accepted[left - 1]?.selection;
|
|
480
|
+
const next = accepted[left]?.selection;
|
|
481
|
+
if (previous !== void 0 && selectionIntersects(previous, selection) || next !== void 0 && selectionIntersects(next, selection)) continue;
|
|
482
|
+
accepted.splice(left, 0, {
|
|
483
|
+
index: i,
|
|
484
|
+
selection
|
|
485
|
+
});
|
|
486
|
+
selected.add(i);
|
|
487
|
+
}
|
|
488
|
+
return selections.filter((_, index) => selected.has(index));
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Finds the next matching word and updates the selections.
|
|
492
|
+
*/
|
|
493
|
+
function findNexMatch(textDocument, selections) {
|
|
494
|
+
if (selections.length === 0) return;
|
|
495
|
+
const normalizedSelections = selections.map((selection) => isCollapsedSelection(selection) ? expandCollapsedSelectionToWord(textDocument, selection) : selection);
|
|
496
|
+
const texts = normalizedSelections.map((s) => textDocument.getText(s));
|
|
497
|
+
const needle = texts[0];
|
|
498
|
+
if (needle.length === 0 || texts.some((t) => t !== needle)) return;
|
|
499
|
+
const occupied = normalizedSelections.map((s) => [textDocument.offsetAt(s.start), textDocument.offsetAt(s.end)]);
|
|
500
|
+
const nextOffset = textDocument.findNextNonOverlappingSubstring(needle, occupied);
|
|
501
|
+
if (nextOffset === void 0) return normalizedSelections.some((selection, index) => {
|
|
502
|
+
const original = selections[index];
|
|
503
|
+
return comparePosition(selection.start, original.start) !== 0 || comparePosition(selection.end, original.end) !== 0 || selection.direction !== original.direction;
|
|
504
|
+
}) ? normalizedSelections : void 0;
|
|
505
|
+
const added = createSelectionFromAnchorAndFocusOffsets(textDocument, nextOffset, nextOffset + needle.length);
|
|
506
|
+
return [...normalizedSelections, added];
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Get the full selection of the document.
|
|
510
|
+
*/
|
|
511
|
+
function getDocumentFullSelection(textDocument) {
|
|
512
|
+
const lastLine = textDocument.lineCount - 1;
|
|
513
|
+
return {
|
|
514
|
+
start: {
|
|
515
|
+
line: 0,
|
|
516
|
+
character: 0
|
|
517
|
+
},
|
|
518
|
+
end: {
|
|
519
|
+
line: lastLine,
|
|
520
|
+
character: textDocument.getLineText(lastLine)?.length ?? 0
|
|
521
|
+
},
|
|
522
|
+
direction: DirectionForward
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Get the boundary selection of the document.
|
|
527
|
+
*/
|
|
528
|
+
function getDocumentBoundarySelection(textDocument, atEnd) {
|
|
529
|
+
const line = atEnd ? textDocument.lineCount - 1 : 0;
|
|
530
|
+
const start = {
|
|
531
|
+
line,
|
|
532
|
+
character: atEnd ? textDocument.getLineText(line)?.length ?? 0 : 0
|
|
533
|
+
};
|
|
534
|
+
return {
|
|
535
|
+
start,
|
|
536
|
+
end: start,
|
|
537
|
+
direction: DirectionForward
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Get the text of the selections for the given text document.
|
|
542
|
+
*/
|
|
543
|
+
function getSelectionText(textDocument, selections) {
|
|
544
|
+
return [...selections].sort((a, b) => {
|
|
545
|
+
const startOrder = comparePosition(a.start, b.start);
|
|
546
|
+
if (startOrder !== 0) return startOrder;
|
|
547
|
+
return comparePosition(a.end, b.end);
|
|
548
|
+
}).map((selection) => {
|
|
549
|
+
if (isCollapsedSelection(selection)) return textDocument.getLineText(selection.start.line, false);
|
|
550
|
+
return textDocument.getText(selection);
|
|
551
|
+
}).join("\n");
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Get the anchor node and offset for a selection.
|
|
555
|
+
*/
|
|
556
|
+
function getSelectionAnchor(lineElement, character) {
|
|
557
|
+
const ch = Math.max(0, character);
|
|
558
|
+
const tokens = collectTokens(lineElement);
|
|
559
|
+
let last = null;
|
|
560
|
+
for (const token of tokens) {
|
|
561
|
+
last = token;
|
|
562
|
+
const base = getCharacterIndex(token);
|
|
563
|
+
if (ch <= base + (token.textContent?.length ?? 0)) {
|
|
564
|
+
const anchor = textAt(token, ch < base ? 0 : ch - base);
|
|
565
|
+
if (anchor !== null) return anchor;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (last !== null) {
|
|
569
|
+
const anchor = textAt(last, last.textContent?.length ?? 0);
|
|
570
|
+
if (anchor !== null) return anchor;
|
|
571
|
+
return [last, 0];
|
|
572
|
+
}
|
|
573
|
+
let textOffset = 0;
|
|
574
|
+
let lastTextNode = null;
|
|
575
|
+
for (const child of lineElement.childNodes) {
|
|
576
|
+
if (child.nodeType === 1 && child.tagName === "BR") return [child, 0];
|
|
577
|
+
if (child.nodeType !== 3) continue;
|
|
578
|
+
lastTextNode = child;
|
|
579
|
+
const len = getTextOffset(lastTextNode.textContent, lastTextNode.textContent?.length ?? 0);
|
|
580
|
+
if (ch <= textOffset + len) return [lastTextNode, getTextOffset(lastTextNode.textContent, ch - textOffset)];
|
|
581
|
+
textOffset += len;
|
|
582
|
+
}
|
|
583
|
+
if (lastTextNode !== null) return [lastTextNode, getTextOffset(lastTextNode.textContent, lastTextNode.textContent?.length ?? 0)];
|
|
584
|
+
return [lineElement, 0];
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Expands a zero-width selection to the word-like segment that contains the caret.
|
|
588
|
+
*/
|
|
589
|
+
function expandCollapsedSelectionToWord(textDocument, selection) {
|
|
590
|
+
const { line, character } = selection.start;
|
|
591
|
+
const lineText = textDocument.getLineText(line);
|
|
592
|
+
const span = expandCollapsedLineWord(lineText, Math.max(0, Math.min(character, lineText.length)));
|
|
593
|
+
if (span === void 0) return selection;
|
|
594
|
+
return {
|
|
595
|
+
start: {
|
|
596
|
+
line,
|
|
597
|
+
character: span.start
|
|
598
|
+
},
|
|
599
|
+
end: {
|
|
600
|
+
line,
|
|
601
|
+
character: span.end
|
|
602
|
+
},
|
|
603
|
+
direction: DirectionForward
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function expandCollapsedLineWord(lineText, character) {
|
|
607
|
+
const segmenter = new Intl.Segmenter(void 0, { granularity: "word" });
|
|
608
|
+
for (const seg of segmenter.segment(lineText)) {
|
|
609
|
+
if (seg.isWordLike !== true) continue;
|
|
610
|
+
const lo = seg.index;
|
|
611
|
+
const hi = lo + seg.segment.length;
|
|
612
|
+
if (character >= lo && character <= hi) return {
|
|
613
|
+
start: lo,
|
|
614
|
+
end: hi
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function getSelectionAnchorAndFocusOffsets(textDocument, selection) {
|
|
619
|
+
const isBackward = selection.direction === DirectionBackward;
|
|
620
|
+
return [textDocument.offsetAt(isBackward ? selection.end : selection.start), textDocument.offsetAt(getCaretPosition(selection))];
|
|
621
|
+
}
|
|
622
|
+
function resolveDeleteHardLineForwardRange(textDocument, selection) {
|
|
623
|
+
if (!isCollapsedSelection(selection)) return {
|
|
624
|
+
start: selection.start,
|
|
625
|
+
end: selection.end
|
|
626
|
+
};
|
|
627
|
+
const { line, character } = selection.start;
|
|
628
|
+
const lineLength = textDocument.getLineText(line).length;
|
|
629
|
+
if (character < lineLength) return {
|
|
630
|
+
start: {
|
|
631
|
+
line,
|
|
632
|
+
character
|
|
633
|
+
},
|
|
634
|
+
end: {
|
|
635
|
+
line,
|
|
636
|
+
character: lineLength
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
if (line < textDocument.lineCount - 1) return {
|
|
640
|
+
start: {
|
|
641
|
+
line,
|
|
642
|
+
character
|
|
643
|
+
},
|
|
644
|
+
end: {
|
|
645
|
+
line: line + 1,
|
|
646
|
+
character: 0
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
return {
|
|
650
|
+
start: {
|
|
651
|
+
line,
|
|
652
|
+
character
|
|
653
|
+
},
|
|
654
|
+
end: {
|
|
655
|
+
line,
|
|
656
|
+
character
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function expandSingleNewlineInsert(textDocument, insertText, insertStartOffset) {
|
|
661
|
+
if (insertText !== "\n" && insertText !== "\r\n") return insertText;
|
|
662
|
+
const line = textDocument.positionAt(insertStartOffset).line;
|
|
663
|
+
const lineText = textDocument.getLineText(line);
|
|
664
|
+
const indentLen = getLeadingSpaces(lineText);
|
|
665
|
+
if (indentLen === 0) return insertText;
|
|
666
|
+
return insertText + lineText.slice(0, indentLen);
|
|
667
|
+
}
|
|
668
|
+
function getLeadingSpaces(text) {
|
|
669
|
+
let indent = 0;
|
|
670
|
+
for (; indent < text.length; indent++) {
|
|
671
|
+
const c = text.charCodeAt(indent);
|
|
672
|
+
if (c !== 32 && c !== 9) break;
|
|
673
|
+
}
|
|
674
|
+
return indent;
|
|
675
|
+
}
|
|
676
|
+
function createSelectionsFromOffsetPairs(textDocument, offsetPairs) {
|
|
677
|
+
const normalizedOffsets = [];
|
|
678
|
+
for (const [anchorOffset, focusOffset] of offsetPairs) normalizedOffsets.push(Math.min(anchorOffset, focusOffset), Math.max(anchorOffset, focusOffset));
|
|
679
|
+
const positions = textDocument.positionsAt(normalizedOffsets);
|
|
680
|
+
return offsetPairs.map(([anchorOffset, focusOffset], index) => {
|
|
681
|
+
const direction = anchorOffset === focusOffset ? DirectionNone : anchorOffset < focusOffset ? DirectionForward : DirectionBackward;
|
|
682
|
+
return {
|
|
683
|
+
start: positions[index * 2],
|
|
684
|
+
end: positions[index * 2 + 1],
|
|
685
|
+
direction
|
|
686
|
+
};
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
function normalizeLeadingIndentForChange(textDocument, change, tabSize) {
|
|
690
|
+
if (change.text !== "" || change.start !== change.end - 1) return change;
|
|
691
|
+
const caretPosition = textDocument.positionAt(change.end);
|
|
692
|
+
if (caretPosition.character === 0) return change;
|
|
693
|
+
const lineText = textDocument.getLineText(caretPosition.line);
|
|
694
|
+
const leadingText = lineText.slice(0, caretPosition.character);
|
|
695
|
+
if (/[^ \t]/.test(leadingText)) return change;
|
|
696
|
+
if (lineText[caretPosition.character - 1] === " ") return change;
|
|
697
|
+
const softTabStart = Math.max(0, caretPosition.character - tabSize);
|
|
698
|
+
const softTabText = lineText.slice(softTabStart, caretPosition.character);
|
|
699
|
+
if (softTabText.length === tabSize && /^ +$/.test(softTabText)) return {
|
|
700
|
+
...change,
|
|
701
|
+
start: change.end - softTabText.length
|
|
702
|
+
};
|
|
703
|
+
return change;
|
|
704
|
+
}
|
|
705
|
+
function boundaryToPosition(node, offset) {
|
|
706
|
+
let lineEl = node.nodeType === 1 ? node : node.parentElement;
|
|
707
|
+
while (lineEl !== null && getLineIndex(lineEl) === void 0) lineEl = lineEl.parentElement;
|
|
708
|
+
if (lineEl === null) return null;
|
|
709
|
+
const line = getLineIndex(lineEl);
|
|
710
|
+
if (line === void 0) return null;
|
|
711
|
+
if (node.nodeType === 3) {
|
|
712
|
+
if (node.parentElement === null) return null;
|
|
713
|
+
if (findTokenSpan(node.parentElement) !== null) return {
|
|
714
|
+
line,
|
|
715
|
+
character: getLineChildEnd(node, offset)
|
|
716
|
+
};
|
|
717
|
+
return {
|
|
718
|
+
line,
|
|
719
|
+
character: offsetBefore(lineEl, node) + getTextOffset(node.textContent, offset)
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
if (node.nodeType === 1) {
|
|
723
|
+
const el = node;
|
|
724
|
+
if (el.tagName === "DIV") {
|
|
725
|
+
let character = 0;
|
|
726
|
+
for (let i = 0; i < offset; i++) character = getLineChildEnd(el.childNodes[i]);
|
|
727
|
+
return {
|
|
728
|
+
line,
|
|
729
|
+
character
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
if (el.tagName === "BR") return {
|
|
733
|
+
line,
|
|
734
|
+
character: 0
|
|
735
|
+
};
|
|
736
|
+
if (el.tagName === "SPAN") {
|
|
737
|
+
if (offset < el.childNodes.length) {
|
|
738
|
+
const next = el.childNodes[offset];
|
|
739
|
+
if (next?.nodeType === 1) {
|
|
740
|
+
const nextBase = getCharacterIndex(next);
|
|
741
|
+
if (nextBase !== void 0) return {
|
|
742
|
+
line,
|
|
743
|
+
character: nextBase
|
|
744
|
+
};
|
|
745
|
+
const token = findTokenSpan(next);
|
|
746
|
+
const tokenBase = token === null ? void 0 : getCharacterIndex(token);
|
|
747
|
+
if (tokenBase !== void 0) return {
|
|
748
|
+
line,
|
|
749
|
+
character: tokenBase
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
line,
|
|
755
|
+
character: offset > 0 ? getLineChildEnd(el.childNodes[offset - 1]) : offsetBefore(lineEl, el)
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
return {
|
|
759
|
+
line,
|
|
760
|
+
character: offsetBefore(lineEl, el)
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
function collectTokens(line) {
|
|
766
|
+
const tokens = [];
|
|
767
|
+
for (const child of line.childNodes) {
|
|
768
|
+
if (child.nodeType !== 1) continue;
|
|
769
|
+
const el = child;
|
|
770
|
+
if (el.tagName !== "SPAN") continue;
|
|
771
|
+
if (getCharacterIndex(el) !== void 0) {
|
|
772
|
+
tokens.push(el);
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
for (const nested of el.childNodes) if (nested.nodeType === 1 && getCharacterIndex(nested) !== void 0) tokens.push(nested);
|
|
776
|
+
}
|
|
777
|
+
return tokens;
|
|
778
|
+
}
|
|
779
|
+
function textAt(token, offset) {
|
|
780
|
+
let remaining = Math.max(0, offset);
|
|
781
|
+
const stack = [{
|
|
782
|
+
container: token,
|
|
783
|
+
index: 0
|
|
784
|
+
}];
|
|
785
|
+
while (stack.length > 0) {
|
|
786
|
+
const frame = stack[stack.length - 1];
|
|
787
|
+
if (frame.index >= frame.container.childNodes.length) {
|
|
788
|
+
stack.pop();
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
const walkNode = frame.container.childNodes[frame.index];
|
|
792
|
+
frame.index++;
|
|
793
|
+
if (walkNode.nodeType === 3) {
|
|
794
|
+
const len = getTextOffset(walkNode.textContent, walkNode.textContent?.length ?? 0);
|
|
795
|
+
if (remaining <= len) return [walkNode, remaining];
|
|
796
|
+
remaining -= len;
|
|
797
|
+
} else if (walkNode.nodeType === 1) stack.push({
|
|
798
|
+
container: walkNode,
|
|
799
|
+
index: 0
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
function textLengthBefore(root, target) {
|
|
805
|
+
let before = 0;
|
|
806
|
+
const stack = [{
|
|
807
|
+
container: root,
|
|
808
|
+
index: 0
|
|
809
|
+
}];
|
|
810
|
+
while (stack.length > 0) {
|
|
811
|
+
const frame = stack[stack.length - 1];
|
|
812
|
+
if (frame.index >= frame.container.childNodes.length) {
|
|
813
|
+
stack.pop();
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
const walkNode = frame.container.childNodes[frame.index];
|
|
817
|
+
if (walkNode === target) return before;
|
|
818
|
+
frame.index++;
|
|
819
|
+
if (walkNode.nodeType === 3) before += getTextOffset(walkNode.textContent, walkNode.textContent?.length ?? 0);
|
|
820
|
+
else if (walkNode.nodeType === 1) stack.push({
|
|
821
|
+
container: walkNode,
|
|
822
|
+
index: 0
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
return before;
|
|
826
|
+
}
|
|
827
|
+
function isInside(token, node) {
|
|
828
|
+
let current = node;
|
|
829
|
+
while (current !== null) {
|
|
830
|
+
if (current === token) return true;
|
|
831
|
+
current = current.parentElement;
|
|
832
|
+
}
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
function offsetBefore(line, node) {
|
|
836
|
+
if (node.parentElement === line) {
|
|
837
|
+
let offset$1 = 0;
|
|
838
|
+
const index = Array.prototype.indexOf.call(line.childNodes, node);
|
|
839
|
+
for (let i = 0; i < index; i++) offset$1 = getLineChildEnd(line.childNodes[i]);
|
|
840
|
+
return offset$1;
|
|
841
|
+
}
|
|
842
|
+
for (const token of collectTokens(line)) if (isInside(token, node)) return getCharacterIndex(token) + (node.nodeType === 3 ? textLengthBefore(token, node) : 0);
|
|
843
|
+
let offset = 0;
|
|
844
|
+
let target = node.nodeType === 1 ? node : node.parentElement;
|
|
845
|
+
while (target !== null && target.parentElement !== null) {
|
|
846
|
+
if (getLineIndex(target.parentElement) !== void 0) break;
|
|
847
|
+
const parent = target.parentElement;
|
|
848
|
+
const index = Array.prototype.indexOf.call(parent.childNodes, target);
|
|
849
|
+
for (let i = 0; i < index; i++) offset = getLineChildEnd(parent.childNodes[i]);
|
|
850
|
+
target = parent;
|
|
851
|
+
}
|
|
852
|
+
return offset;
|
|
853
|
+
}
|
|
854
|
+
function findTokenSpan(el) {
|
|
855
|
+
let current = el;
|
|
856
|
+
while (current !== null) {
|
|
857
|
+
if (getLineIndex(current) !== void 0) return null;
|
|
858
|
+
if (getCharacterIndex(current) !== void 0) return current;
|
|
859
|
+
current = current.parentElement;
|
|
860
|
+
}
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
function getLineChildEnd(child, textOffsetInChild) {
|
|
864
|
+
if (child === void 0) return 0;
|
|
865
|
+
if (child.nodeType === 3) {
|
|
866
|
+
const parent = child.parentElement;
|
|
867
|
+
if (parent === null) return 0;
|
|
868
|
+
const token = findTokenSpan(parent);
|
|
869
|
+
if (token === null) return 0;
|
|
870
|
+
const base$1 = getCharacterIndex(token);
|
|
871
|
+
if (base$1 === void 0) return 0;
|
|
872
|
+
const length = textOffsetInChild === void 0 ? getTextOffset(child.textContent, child.textContent?.length ?? 0) : getTextOffset(child.textContent, textOffsetInChild);
|
|
873
|
+
return base$1 + textLengthBefore(token, child) + length;
|
|
874
|
+
}
|
|
875
|
+
if (child.nodeType !== 1) return 0;
|
|
876
|
+
const el = child;
|
|
877
|
+
if (el.tagName !== "SPAN" && el.tagName !== "BR") return 0;
|
|
878
|
+
const base = getCharacterIndex(el);
|
|
879
|
+
if (base !== void 0) return base + (el.textContent?.length ?? 0);
|
|
880
|
+
let end = 0;
|
|
881
|
+
for (const token of el.childNodes) end = Math.max(end, getLineChildEnd(token));
|
|
882
|
+
return end;
|
|
883
|
+
}
|
|
884
|
+
function getLineIndex(el) {
|
|
885
|
+
const { line } = el.dataset;
|
|
886
|
+
if (line !== void 0) return parseInt(line) - 1;
|
|
887
|
+
}
|
|
888
|
+
function getCharacterIndex(el) {
|
|
889
|
+
const { char } = el.dataset;
|
|
890
|
+
return char !== void 0 ? parseInt(char) : void 0;
|
|
891
|
+
}
|
|
892
|
+
function getTextOffset(text, offset) {
|
|
893
|
+
const value = text ?? "";
|
|
894
|
+
const lineBreakIndex = value.search(/[\r\n]/);
|
|
895
|
+
return Math.min(offset, lineBreakIndex === -1 ? value.length : lineBreakIndex);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
//#endregion
|
|
899
|
+
export { DirectionBackward, DirectionForward, DirectionNone, applyDeleteHardLineForwardToSelections, applyTextChangeToSelections, applyTextReplaceToSelections, applyTransposeToSelections, comparePosition, convertSelection, createSelectionFrom, createSelectionFromAnchorAndFocusOffsets, expandCollapsedSelectionToWord, extendSelection, extendSelections, findNexMatch, getCaretPosition, getDocumentBoundarySelection, getDocumentFullSelection, getSelectionAnchor, getSelectionText, isCollapsedSelection, isLineEditable, mapCursorMove, mapSelectionShift, mergeOverlappingSelections, resolveIndentEdits, selectionIntersects };
|
|
900
|
+
//# sourceMappingURL=selection.js.map
|