@adecore/editor-react 0.22.0 → 0.23.0
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/CallHierarchyPanel.d.ts +6 -0
- package/dist/CallHierarchyPanel.js +88 -0
- package/dist/CompletionPopup.js +11 -3
- package/dist/DiffView.d.ts +22 -0
- package/dist/DiffView.js +203 -0
- package/dist/EditorContextMenu.js +1 -0
- package/dist/EditorView.js +18 -1
- package/dist/FindReplace.d.ts +1 -1
- package/dist/FindReplace.js +41 -11
- package/dist/HoverCard.js +2 -2
- package/dist/LanguagePopups.js +4 -1
- package/dist/Markdown.d.ts +2 -1
- package/dist/Markdown.js +8 -4
- package/dist/PeekPanel.d.ts +3 -0
- package/dist/PeekPanel.js +2 -3
- package/dist/PickPopup.js +15 -2
- package/dist/RenameCard.js +10 -5
- package/dist/RenderedDocs.d.ts +9 -0
- package/dist/RenderedDocs.js +84 -0
- package/dist/SplitDiffView.d.ts +9 -0
- package/dist/SplitDiffView.js +276 -0
- package/dist/SymbolPicker.d.ts +3 -0
- package/dist/SymbolPicker.js +4 -3
- package/dist/call-hierarchy-model.d.ts +52 -0
- package/dist/call-hierarchy-model.js +99 -0
- package/dist/call-hierarchy.d.ts +42 -0
- package/dist/call-hierarchy.js +399 -0
- package/dist/code-actions-model.d.ts +2 -0
- package/dist/code-actions-model.js +12 -10
- package/dist/code-actions.js +3 -1
- package/dist/completion-model.d.ts +14 -4
- package/dist/completion-model.js +40 -33
- package/dist/completion.d.ts +4 -0
- package/dist/completion.js +257 -42
- package/dist/diagnostics.js +8 -6
- package/dist/diff-expansion.d.ts +9 -0
- package/dist/diff-expansion.js +41 -0
- package/dist/diff-overlays.d.ts +15 -0
- package/dist/diff-overlays.js +28 -0
- package/dist/diff-syntax.d.ts +16 -0
- package/dist/diff-syntax.js +49 -0
- package/dist/diff-wordmarks.d.ts +5 -0
- package/dist/diff-wordmarks.js +26 -0
- package/dist/doc-comment-model.d.ts +23 -0
- package/dist/doc-comment-model.js +291 -0
- package/dist/editor-language.d.ts +13 -1
- package/dist/editor-language.js +14 -4
- package/dist/editor-react.css +79 -0
- package/dist/format-on-type.d.ts +14 -0
- package/dist/format-on-type.js +96 -0
- package/dist/host-types.d.ts +17 -0
- package/dist/hover.d.ts +4 -0
- package/dist/hover.js +15 -2
- package/dist/index.d.ts +19 -5
- package/dist/index.js +9 -1
- package/dist/inlay-hints.d.ts +47 -2
- package/dist/inlay-hints.js +353 -16
- package/dist/language-ids.js +4 -2
- package/dist/locales/en.json +114 -3
- package/dist/locales/nl.json +114 -3
- package/dist/models.d.ts +6 -2
- package/dist/models.js +5 -1
- package/dist/patch-decorations.d.ts +27 -0
- package/dist/patch-decorations.js +182 -0
- package/dist/patch-layout.d.ts +40 -0
- package/dist/patch-layout.js +304 -0
- package/dist/peek.js +1 -0
- package/dist/pick.d.ts +1 -0
- package/dist/pick.js +11 -1
- package/dist/popups.d.ts +17 -0
- package/dist/popups.js +1 -0
- package/dist/project-language.d.ts +8 -0
- package/dist/project-language.js +34 -2
- package/dist/refresher.d.ts +1 -0
- package/dist/refresher.js +5 -0
- package/dist/rename-model.d.ts +1 -1
- package/dist/rename-model.js +3 -7
- package/dist/rename.js +5 -8
- package/dist/rendered-docs.d.ts +62 -0
- package/dist/rendered-docs.js +368 -0
- package/dist/row-host.d.ts +2 -0
- package/dist/row-host.js +11 -3
- package/dist/semantic-delta.d.ts +2 -0
- package/dist/semantic-delta.js +27 -0
- package/dist/semantic-model.js +16 -0
- package/dist/semantic-tokens.d.ts +26 -1
- package/dist/semantic-tokens.js +174 -14
- package/dist/shortcut-keys.d.ts +2 -0
- package/dist/shortcut-keys.js +6 -2
- package/dist/signature.js +1 -1
- package/dist/snippet-field-transforms.d.ts +30 -0
- package/dist/snippet-field-transforms.js +116 -0
- package/dist/snippet-indent.d.ts +2 -0
- package/dist/snippet-indent.js +49 -0
- package/dist/snippet-range-order.d.ts +3 -0
- package/dist/snippet-range-order.js +19 -0
- package/dist/snippet-session.d.ts +42 -6
- package/dist/snippet-session.js +650 -53
- package/dist/snippet-transform-client.d.ts +19 -0
- package/dist/snippet-transform-client.js +66 -0
- package/dist/snippet-transform-format.d.ts +15 -0
- package/dist/snippet-transform-format.js +302 -0
- package/dist/snippet-transform-protocol.d.ts +45 -0
- package/dist/snippet-transform-protocol.js +8 -0
- package/dist/snippet-transform-queue.d.ts +32 -0
- package/dist/snippet-transform-queue.js +173 -0
- package/dist/snippet-transform-worker.d.ts +10 -0
- package/dist/snippet-transform-worker.js +41 -0
- package/dist/snippet-variable-names.d.ts +1 -0
- package/dist/snippet-variable-names.js +36 -0
- package/dist/snippet-variables.d.ts +7 -0
- package/dist/snippet-variables.js +66 -0
- package/dist/snippet.d.ts +30 -3
- package/dist/snippet.js +402 -87
- package/dist/split-alignment.d.ts +6 -0
- package/dist/split-alignment.js +38 -0
- package/dist/split-scroll.d.ts +14 -0
- package/dist/split-scroll.js +45 -0
- package/dist/symbols.d.ts +2 -1
- package/dist/symbols.js +39 -7
- package/dist/testing.d.ts +5 -1
- package/dist/testing.js +12 -0
- package/dist/workspace-edit.d.ts +25 -3
- package/dist/workspace-edit.js +282 -143
- package/package.json +10 -5
- package/src/CallHierarchyPanel.tsx +238 -0
- package/src/CompletionPopup.tsx +12 -1
- package/src/DiffView.tsx +262 -0
- package/src/EditorContextMenu.tsx +1 -0
- package/src/EditorView.tsx +21 -2
- package/src/FindReplace.tsx +51 -13
- package/src/HoverCard.tsx +23 -21
- package/src/LanguagePopups.tsx +5 -0
- package/src/Markdown.tsx +10 -3
- package/src/PeekPanel.tsx +2 -3
- package/src/PickPopup.tsx +18 -2
- package/src/RenameCard.tsx +18 -0
- package/src/RenderedDocs.tsx +155 -0
- package/src/SplitDiffView.tsx +337 -0
- package/src/SymbolPicker.tsx +4 -3
- package/src/call-hierarchy-model.ts +158 -0
- package/src/call-hierarchy.ts +441 -0
- package/src/code-actions-model.ts +17 -11
- package/src/code-actions.ts +3 -1
- package/src/completion-model.ts +61 -35
- package/src/completion.ts +302 -42
- package/src/diagnostics.ts +8 -6
- package/src/diff-expansion.ts +49 -0
- package/src/diff-overlays.tsx +69 -0
- package/src/diff-syntax.ts +67 -0
- package/src/diff-wordmarks.ts +31 -0
- package/src/doc-comment-model.ts +336 -0
- package/src/editor-language.ts +21 -5
- package/src/editor-react.css +79 -0
- package/src/format-on-type.ts +111 -0
- package/src/host-types.ts +19 -0
- package/src/hover.ts +18 -3
- package/src/index.ts +27 -4
- package/src/inlay-hints.ts +394 -18
- package/src/language-ids.ts +4 -2
- package/src/locales/en.json +114 -3
- package/src/locales/nl.json +114 -3
- package/src/models.ts +40 -1
- package/src/patch-decorations.ts +224 -0
- package/src/patch-layout.ts +388 -0
- package/src/peek.ts +1 -0
- package/src/pick.ts +12 -1
- package/src/popups.ts +24 -0
- package/src/project-language.ts +43 -2
- package/src/refresher.ts +6 -0
- package/src/rename-model.ts +4 -8
- package/src/rename.ts +5 -8
- package/src/rendered-docs.ts +422 -0
- package/src/row-host.ts +12 -3
- package/src/semantic-delta.ts +29 -0
- package/src/semantic-model.ts +17 -0
- package/src/semantic-tokens.ts +202 -19
- package/src/shortcut-keys.ts +6 -2
- package/src/signature.ts +1 -1
- package/src/snippet-field-transforms.ts +144 -0
- package/src/snippet-indent.ts +53 -0
- package/src/snippet-range-order.ts +19 -0
- package/src/snippet-session.ts +797 -56
- package/src/snippet-transform-client.ts +84 -0
- package/src/snippet-transform-format.ts +325 -0
- package/src/snippet-transform-protocol.ts +24 -0
- package/src/snippet-transform-queue.ts +199 -0
- package/src/snippet-transform-worker.ts +58 -0
- package/src/snippet-variable-names.ts +36 -0
- package/src/snippet-variables.ts +74 -0
- package/src/snippet.ts +452 -89
- package/src/split-alignment.ts +45 -0
- package/src/split-scroll.ts +54 -0
- package/src/symbols.ts +43 -7
- package/src/testing.ts +25 -0
- package/src/workspace-edit.ts +347 -169
package/dist/snippet.js
CHANGED
|
@@ -1,134 +1,449 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
1
|
+
import { withSnippetOrder } from './snippet-range-order.js';
|
|
2
|
+
import { scanSnippetTransform } from './snippet-transform-format.js';
|
|
3
|
+
import { SNIPPET_VARIABLES } from './snippet-variable-names.js';
|
|
4
|
+
export const MAX_SNIPPET_TEXT = 100_000;
|
|
5
|
+
const MAX_SNIPPET_NODES = 10_000;
|
|
6
|
+
const MAX_SNIPPET_WORK = 50_000;
|
|
7
|
+
const MAX_SNIPPET_RANGES = 1_000;
|
|
8
|
+
const MAX_SNIPPET_DEPTH = 64;
|
|
9
|
+
class SnippetLimitError extends Error {
|
|
10
|
+
}
|
|
11
|
+
class MalformedSnippetError extends Error {
|
|
12
|
+
}
|
|
8
13
|
class SnippetParser {
|
|
9
|
-
text = '';
|
|
10
|
-
stops = [];
|
|
11
14
|
at = 0;
|
|
15
|
+
nodes = 0;
|
|
16
|
+
depth = 0;
|
|
17
|
+
name = /(?:\d+|[A-Za-z_]\w*)/y;
|
|
12
18
|
source;
|
|
13
19
|
constructor(source) {
|
|
14
20
|
this.source = source;
|
|
15
21
|
}
|
|
16
22
|
parse() {
|
|
17
|
-
this.sequence(false);
|
|
18
|
-
return { text: this.text, stops: this.stops };
|
|
23
|
+
return this.sequence(false);
|
|
19
24
|
}
|
|
20
|
-
/* Text up to the `}` that closes the placeholder this is in, which is left for the caller. */
|
|
21
25
|
sequence(nested) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
if (++this.depth > MAX_SNIPPET_DEPTH) {
|
|
27
|
+
throw new SnippetLimitError();
|
|
28
|
+
}
|
|
29
|
+
const nodes = [];
|
|
30
|
+
let text = '';
|
|
31
|
+
const flush = () => {
|
|
32
|
+
if (text !== '') {
|
|
33
|
+
this.push(nodes, { kind: 'text', text });
|
|
34
|
+
text = '';
|
|
26
35
|
}
|
|
36
|
+
};
|
|
37
|
+
while (this.at < this.source.length && !(nested && this.source[this.at] === '}')) {
|
|
38
|
+
const character = this.source[this.at];
|
|
27
39
|
if (character === '\\' && /[\\$}]/.test(this.source[this.at + 1] ?? '')) {
|
|
28
|
-
|
|
40
|
+
text += this.source[this.at + 1];
|
|
29
41
|
this.at += 2;
|
|
30
42
|
}
|
|
31
43
|
else if (character === '$') {
|
|
32
|
-
this.dollar();
|
|
44
|
+
const node = this.dollar();
|
|
45
|
+
if (node === null) {
|
|
46
|
+
text += '$';
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
flush();
|
|
50
|
+
this.push(nodes, node);
|
|
51
|
+
}
|
|
33
52
|
}
|
|
34
53
|
else {
|
|
35
|
-
|
|
54
|
+
text += character;
|
|
36
55
|
this.at++;
|
|
37
56
|
}
|
|
38
57
|
}
|
|
58
|
+
flush();
|
|
59
|
+
this.depth--;
|
|
60
|
+
return nodes;
|
|
61
|
+
}
|
|
62
|
+
push(nodes, node) {
|
|
63
|
+
if (++this.nodes > MAX_SNIPPET_NODES) {
|
|
64
|
+
throw new SnippetLimitError();
|
|
65
|
+
}
|
|
66
|
+
nodes.push(node);
|
|
39
67
|
}
|
|
40
68
|
dollar() {
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
const choice = CHOICE.exec(rest);
|
|
45
|
-
if (tabstop !== null) {
|
|
46
|
-
this.at += tabstop[0].length;
|
|
47
|
-
this.stops.push({ index: Number(tabstop[1] ?? tabstop[2]), start: this.text.length, end: this.text.length });
|
|
48
|
-
}
|
|
49
|
-
else if (placeholder !== null) {
|
|
50
|
-
this.at += placeholder[0].length;
|
|
51
|
-
const start = this.text.length;
|
|
52
|
-
this.sequence(true);
|
|
53
|
-
this.closeBrace();
|
|
54
|
-
this.stops.push({ index: Number(placeholder[1]), start, end: this.text.length });
|
|
55
|
-
}
|
|
56
|
-
else if (choice !== null) {
|
|
57
|
-
this.at += choice[0].length;
|
|
58
|
-
const start = this.text.length;
|
|
59
|
-
this.firstChoice();
|
|
60
|
-
this.stops.push({ index: Number(choice[1]), start, end: this.text.length });
|
|
61
|
-
}
|
|
62
|
-
else if (DEFAULT_VARIABLE.test(rest)) {
|
|
63
|
-
this.at += rest.indexOf(':') + 1;
|
|
64
|
-
this.sequence(true);
|
|
65
|
-
this.closeBrace();
|
|
66
|
-
}
|
|
67
|
-
else if (TRANSFORMED_VARIABLE.test(rest)) {
|
|
68
|
-
this.skipBraces();
|
|
69
|
-
}
|
|
70
|
-
else if (BARE_VARIABLE.test(rest)) {
|
|
71
|
-
this.at += BARE_VARIABLE.exec(rest)[0].length;
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
this.text += '$';
|
|
69
|
+
const start = this.at++;
|
|
70
|
+
const brace = this.source[this.at] === '{';
|
|
71
|
+
if (brace) {
|
|
75
72
|
this.at++;
|
|
76
73
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (
|
|
74
|
+
this.name.lastIndex = this.at;
|
|
75
|
+
const name = this.name.exec(this.source)?.[0];
|
|
76
|
+
if (name === undefined) {
|
|
77
|
+
this.at = start + 1;
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const index = /^\d+$/.test(name) ? Number(name) : null;
|
|
81
|
+
if (index !== null && !Number.isSafeInteger(index)) {
|
|
82
|
+
this.at = start + 1;
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
this.at += name.length;
|
|
86
|
+
const field = () => ({ kind: 'field', index: index });
|
|
87
|
+
if (!brace) {
|
|
88
|
+
return index === null ? { kind: 'variable', name } : field();
|
|
89
|
+
}
|
|
90
|
+
const next = this.source[this.at];
|
|
91
|
+
if (next === '}') {
|
|
80
92
|
this.at++;
|
|
93
|
+
return index === null ? { kind: 'variable', name } : field();
|
|
81
94
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
while (this.at < this.source.length && !(this.source[this.at] === '|' && this.source[this.at + 1] === '}')) {
|
|
87
|
-
const character = this.source[this.at];
|
|
88
|
-
if (character === '\\' && /[\\,|]/.test(this.source[this.at + 1] ?? '')) {
|
|
95
|
+
if (next === ':') {
|
|
96
|
+
this.at++;
|
|
97
|
+
const body = this.sequence(true);
|
|
98
|
+
if (this.source[this.at] === '}') {
|
|
89
99
|
this.at++;
|
|
90
|
-
|
|
91
|
-
|
|
100
|
+
}
|
|
101
|
+
return index === null ? { kind: 'variable', name, body } : { ...field(), body };
|
|
102
|
+
}
|
|
103
|
+
if (next === '|' && index !== null) {
|
|
104
|
+
this.at++;
|
|
105
|
+
return { ...field(), choices: this.choices() };
|
|
106
|
+
}
|
|
107
|
+
if (next === '/') {
|
|
108
|
+
try {
|
|
109
|
+
const { end, ...transform } = scanSnippetTransform(this.source, this.at);
|
|
110
|
+
this.at = end;
|
|
111
|
+
return index === null ? { kind: 'variable', name, transform } : { ...field(), transform };
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (error instanceof RangeError) {
|
|
115
|
+
throw new SnippetLimitError();
|
|
116
|
+
}
|
|
117
|
+
if (error instanceof SyntaxError) {
|
|
118
|
+
throw new MalformedSnippetError();
|
|
92
119
|
}
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
this.at = start + 1;
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
choices() {
|
|
127
|
+
const values = [''];
|
|
128
|
+
while (this.at < this.source.length && !(this.source[this.at] === '|' && this.source[this.at + 1] === '}')) {
|
|
129
|
+
const character = this.source[this.at++];
|
|
130
|
+
if (character === '\\' && /[\\,|]/.test(this.source[this.at] ?? '')) {
|
|
131
|
+
values[values.length - 1] += this.source[this.at++];
|
|
93
132
|
}
|
|
94
133
|
else if (character === ',') {
|
|
95
|
-
|
|
134
|
+
if (values.length >= MAX_SNIPPET_NODES) {
|
|
135
|
+
throw new SnippetLimitError();
|
|
136
|
+
}
|
|
137
|
+
values.push('');
|
|
96
138
|
}
|
|
97
|
-
else
|
|
98
|
-
|
|
139
|
+
else {
|
|
140
|
+
values[values.length - 1] += character;
|
|
99
141
|
}
|
|
100
|
-
this.at++;
|
|
101
142
|
}
|
|
102
|
-
this.at
|
|
143
|
+
this.at = Math.min(this.source.length, this.at + 2);
|
|
144
|
+
return values;
|
|
103
145
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
146
|
+
}
|
|
147
|
+
function renderSnippet(nodes, variableTransforms) {
|
|
148
|
+
const definitions = new Map();
|
|
149
|
+
const collect = (nodes) => {
|
|
150
|
+
for (const node of nodes) {
|
|
151
|
+
if (node.kind === 'field') {
|
|
152
|
+
const known = definitions.get(node.index);
|
|
153
|
+
if (known === undefined ||
|
|
154
|
+
(known.body === undefined &&
|
|
155
|
+
known.choices === undefined &&
|
|
156
|
+
(node.body !== undefined || node.choices !== undefined || (known.transform !== undefined && node.transform === undefined)))) {
|
|
157
|
+
definitions.set(node.index, node);
|
|
158
|
+
}
|
|
112
159
|
}
|
|
113
|
-
|
|
114
|
-
|
|
160
|
+
if (node.kind !== 'text' && node.body !== undefined) {
|
|
161
|
+
collect(node.body);
|
|
115
162
|
}
|
|
116
163
|
}
|
|
164
|
+
};
|
|
165
|
+
collect(nodes);
|
|
166
|
+
const fields = new Map();
|
|
167
|
+
const stops = [];
|
|
168
|
+
const transforms = [];
|
|
169
|
+
let text = '';
|
|
170
|
+
let work = 0;
|
|
171
|
+
let ranges = 0;
|
|
172
|
+
let occurrence = 0;
|
|
173
|
+
let tailEvent = 0;
|
|
174
|
+
let tailOffset = -1;
|
|
175
|
+
const recordTail = (range) => {
|
|
176
|
+
if (range.start === range.end || range.tail === true) {
|
|
177
|
+
tailEvent++;
|
|
178
|
+
tailOffset = range.end;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const append = (value) => {
|
|
182
|
+
if (text.length + value.length > MAX_SNIPPET_TEXT) {
|
|
183
|
+
throw new SnippetLimitError();
|
|
184
|
+
}
|
|
185
|
+
text += value;
|
|
186
|
+
};
|
|
187
|
+
const render = (nodes, linked, parents, visiting) => {
|
|
188
|
+
if (parents.length > MAX_SNIPPET_DEPTH) {
|
|
189
|
+
throw new SnippetLimitError();
|
|
190
|
+
}
|
|
191
|
+
for (const node of nodes) {
|
|
192
|
+
if (++work > MAX_SNIPPET_WORK) {
|
|
193
|
+
throw new SnippetLimitError();
|
|
194
|
+
}
|
|
195
|
+
const order = occurrence++;
|
|
196
|
+
if (node.kind === 'text') {
|
|
197
|
+
const start = text.length;
|
|
198
|
+
const value = node.transform === undefined || variableTransforms === undefined ? node.text : variableTransforms[transforms.length];
|
|
199
|
+
if (typeof value !== 'string') {
|
|
200
|
+
throw new RangeError('The variable transform count does not match the snippet.');
|
|
201
|
+
}
|
|
202
|
+
append(value);
|
|
203
|
+
if (node.transform !== undefined) {
|
|
204
|
+
if (++ranges > MAX_SNIPPET_RANGES) {
|
|
205
|
+
throw new SnippetLimitError();
|
|
206
|
+
}
|
|
207
|
+
const range = { start, end: text.length, transform: node.transform, ...(parents.length === 0 ? {} : { parents: [...parents] }) };
|
|
208
|
+
withSnippetOrder(range, order);
|
|
209
|
+
transforms.push(range);
|
|
210
|
+
recordTail(range);
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (node.kind === 'variable') {
|
|
215
|
+
render(node.body ?? [], linked, parents, visiting);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (++ranges > MAX_SNIPPET_RANGES) {
|
|
219
|
+
throw new SnippetLimitError();
|
|
220
|
+
}
|
|
221
|
+
const definition = definitions.get(node.index);
|
|
222
|
+
const from = text.length;
|
|
223
|
+
const beforeTail = tailEvent;
|
|
224
|
+
if (!visiting.has(node.index)) {
|
|
225
|
+
if (definition.choices !== undefined) {
|
|
226
|
+
append(definition.choices[0] ?? '');
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
render(definition.body ?? [], linked || node !== definition, [...parents, node.index], new Set([...visiting, node.index]));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const range = {
|
|
233
|
+
start: from,
|
|
234
|
+
end: text.length,
|
|
235
|
+
...(parents.length === 0 ? {} : { parents: [...parents] }),
|
|
236
|
+
...(node.transform === undefined ? {} : { transform: node.transform }),
|
|
237
|
+
...(tailEvent !== beforeTail && tailOffset === text.length ? { tail: true } : {})
|
|
238
|
+
};
|
|
239
|
+
withSnippetOrder(range, order);
|
|
240
|
+
recordTail(range);
|
|
241
|
+
const field = fields.get(node.index) ?? {
|
|
242
|
+
index: node.index,
|
|
243
|
+
ranges: [],
|
|
244
|
+
linked: [],
|
|
245
|
+
primary: 0,
|
|
246
|
+
parents: new Set(),
|
|
247
|
+
choices: definition.choices
|
|
248
|
+
};
|
|
249
|
+
parents.filter((parent) => parent !== node.index).forEach((parent) => field.parents.add(parent));
|
|
250
|
+
if (linked) {
|
|
251
|
+
field.linked.push(range);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
if (node === definition) {
|
|
255
|
+
field.primary = field.ranges.length;
|
|
256
|
+
}
|
|
257
|
+
field.ranges.push(range);
|
|
258
|
+
stops.push(withSnippetOrder({
|
|
259
|
+
index: node.index,
|
|
260
|
+
start: range.start,
|
|
261
|
+
end: range.end,
|
|
262
|
+
...(node.transform === undefined ? {} : { transform: node.transform }),
|
|
263
|
+
...(range.tail === undefined ? {} : { tail: range.tail })
|
|
264
|
+
}, order));
|
|
265
|
+
}
|
|
266
|
+
fields.set(node.index, field);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
render(nodes, false, [], new Set());
|
|
270
|
+
if (variableTransforms !== undefined && variableTransforms.length !== transforms.length) {
|
|
271
|
+
throw new RangeError('The variable transform count does not match the snippet.');
|
|
117
272
|
}
|
|
273
|
+
stops.sort((left, right) => left.start - right.start || right.end - left.end);
|
|
274
|
+
return {
|
|
275
|
+
text,
|
|
276
|
+
stops,
|
|
277
|
+
...(transforms.length === 0 ? {} : { transforms }),
|
|
278
|
+
fields: [...fields.values()]
|
|
279
|
+
.filter((field) => field.ranges.length > 0)
|
|
280
|
+
.map((field) => ({
|
|
281
|
+
index: field.index,
|
|
282
|
+
ranges: field.ranges,
|
|
283
|
+
primary: field.primary,
|
|
284
|
+
...(field.linked.length === 0 ? {} : { linked: field.linked }),
|
|
285
|
+
...(field.parents.size === 0 ? {} : { parents: [...field.parents] }),
|
|
286
|
+
...(field.choices === undefined ? {} : { choices: field.choices })
|
|
287
|
+
}))
|
|
288
|
+
};
|
|
118
289
|
}
|
|
119
|
-
|
|
120
|
-
|
|
290
|
+
function variableIndices(nodes) {
|
|
291
|
+
let highest = 0;
|
|
292
|
+
const names = new Set();
|
|
293
|
+
const scan = (nodes) => {
|
|
294
|
+
for (const node of nodes) {
|
|
295
|
+
if (node.kind === 'field') {
|
|
296
|
+
highest = Math.max(highest, node.index);
|
|
297
|
+
}
|
|
298
|
+
else if (node.kind === 'variable' && !SNIPPET_VARIABLES.has(node.name)) {
|
|
299
|
+
names.add(node.name);
|
|
300
|
+
}
|
|
301
|
+
if (node.kind !== 'text' && node.body !== undefined) {
|
|
302
|
+
scan(node.body);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
scan(nodes);
|
|
307
|
+
return new Map([...names].map((name) => {
|
|
308
|
+
if (!Number.isSafeInteger(++highest)) {
|
|
309
|
+
throw new SnippetLimitError();
|
|
310
|
+
}
|
|
311
|
+
return [name, highest];
|
|
312
|
+
}));
|
|
121
313
|
}
|
|
122
|
-
|
|
314
|
+
function resolveVariables(nodes, indices, resolve) {
|
|
315
|
+
const unknown = new Map();
|
|
316
|
+
const values = new Map();
|
|
317
|
+
const valueOf = (name) => {
|
|
318
|
+
if (!values.has(name)) {
|
|
319
|
+
values.set(name, resolve?.(name));
|
|
320
|
+
}
|
|
321
|
+
return values.get(name);
|
|
322
|
+
};
|
|
323
|
+
const collect = (nodes) => {
|
|
324
|
+
for (const node of nodes) {
|
|
325
|
+
if (node.kind === 'variable' && valueOf(node.name) === undefined && !SNIPPET_VARIABLES.has(node.name)) {
|
|
326
|
+
const previous = unknown.get(node.name);
|
|
327
|
+
if (previous === undefined) {
|
|
328
|
+
unknown.set(node.name, {
|
|
329
|
+
index: indices.get(node.name),
|
|
330
|
+
body: node.body ?? [{ kind: 'text', text: node.name }],
|
|
331
|
+
defaulted: node.body !== undefined,
|
|
332
|
+
definition: node
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
else if (node.body !== undefined && !previous.defaulted) {
|
|
336
|
+
previous.body = node.body;
|
|
337
|
+
previous.defaulted = true;
|
|
338
|
+
previous.definition = node;
|
|
339
|
+
}
|
|
340
|
+
else if (!previous.defaulted && node.transform === undefined && previous.definition.transform !== undefined) {
|
|
341
|
+
previous.definition = node;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (node.kind !== 'text' && node.body !== undefined) {
|
|
345
|
+
collect(node.body);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
collect(nodes);
|
|
350
|
+
// Resolved empty variables retain their defaults without adding a navigable field.
|
|
351
|
+
let work = 0;
|
|
352
|
+
const normalize = (nodes, visiting, depth = 0) => {
|
|
353
|
+
if (depth > MAX_SNIPPET_DEPTH) {
|
|
354
|
+
throw new SnippetLimitError();
|
|
355
|
+
}
|
|
356
|
+
return nodes.flatMap((node) => {
|
|
357
|
+
if (++work > MAX_SNIPPET_WORK) {
|
|
358
|
+
throw new SnippetLimitError();
|
|
359
|
+
}
|
|
360
|
+
if (node.kind === 'variable' && !unknown.has(node.name)) {
|
|
361
|
+
const value = valueOf(node.name);
|
|
362
|
+
if (node.transform !== undefined) {
|
|
363
|
+
return [{ kind: 'text', text: value ?? '', transform: node.transform }];
|
|
364
|
+
}
|
|
365
|
+
return value !== undefined && value !== '' ? [{ kind: 'text', text: value }] : normalize(node.body ?? [], visiting, depth + 1);
|
|
366
|
+
}
|
|
367
|
+
if (node.kind === 'field') {
|
|
368
|
+
return [{ ...node, ...(node.body === undefined ? {} : { body: normalize(node.body, visiting, depth + 1) }) }];
|
|
369
|
+
}
|
|
370
|
+
if (node.kind === 'variable') {
|
|
371
|
+
const field = unknown.get(node.name);
|
|
372
|
+
return [
|
|
373
|
+
{
|
|
374
|
+
kind: 'field',
|
|
375
|
+
index: field.index,
|
|
376
|
+
...(node.transform === undefined ? {} : { transform: node.transform }),
|
|
377
|
+
...(node !== field.definition
|
|
378
|
+
? {}
|
|
379
|
+
: { body: visiting.has(node.name) ? [] : normalize(field.body, new Set([...visiting, node.name]), depth + 1) })
|
|
380
|
+
}
|
|
381
|
+
];
|
|
382
|
+
}
|
|
383
|
+
return [node];
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
return normalize(nodes, new Set());
|
|
387
|
+
}
|
|
388
|
+
export function compileSnippet(source) {
|
|
389
|
+
let nodes = null;
|
|
390
|
+
let indices = new Map();
|
|
391
|
+
if (source.length <= MAX_SNIPPET_TEXT) {
|
|
392
|
+
try {
|
|
393
|
+
nodes = new SnippetParser(source).parse();
|
|
394
|
+
indices = variableIndices(nodes);
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
if (error instanceof MalformedSnippetError) {
|
|
398
|
+
return { source, render: () => ({ text: source, stops: [] }) };
|
|
399
|
+
}
|
|
400
|
+
if (!(error instanceof SnippetLimitError)) {
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
nodes = null;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
source,
|
|
408
|
+
render: (resolve, variableTransforms) => {
|
|
409
|
+
if (nodes === null) {
|
|
410
|
+
return { text: source, stops: [], limited: true };
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
return renderSnippet(resolveVariables(nodes, indices, resolve), variableTransforms);
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
if (!(error instanceof SnippetLimitError)) {
|
|
417
|
+
throw error;
|
|
418
|
+
}
|
|
419
|
+
// Language-server snippets may expand exponentially; retain the original text without a session.
|
|
420
|
+
return { text: source, stops: [], limited: true };
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
export function parseSnippet(snippet, resolve) {
|
|
426
|
+
return compileSnippet(snippet).render(resolve);
|
|
427
|
+
}
|
|
428
|
+
/* One defining occurrence per index, then $0 or the end of the inserted text. */
|
|
123
429
|
export function tabOrder(parsed) {
|
|
124
430
|
const first = new Map();
|
|
125
431
|
for (const stop of parsed.stops) {
|
|
126
|
-
|
|
127
|
-
if (known === undefined || stop.start < known.start) {
|
|
432
|
+
if (!first.has(stop.index)) {
|
|
128
433
|
first.set(stop.index, stop);
|
|
129
434
|
}
|
|
130
435
|
}
|
|
436
|
+
for (const field of parsed.fields ?? []) {
|
|
437
|
+
const range = field.ranges[field.primary];
|
|
438
|
+
if (range !== undefined) {
|
|
439
|
+
first.set(field.index, {
|
|
440
|
+
index: field.index,
|
|
441
|
+
start: range.start,
|
|
442
|
+
end: range.end,
|
|
443
|
+
...(range.transform === undefined ? {} : { transform: range.transform })
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
131
447
|
const visited = [...first.values()].filter((stop) => stop.index > 0).sort((left, right) => left.index - right.index);
|
|
132
|
-
|
|
133
|
-
return [...visited, final];
|
|
448
|
+
return [...visited, first.get(0) ?? { index: 0, start: parsed.text.length, end: parsed.text.length }];
|
|
134
449
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DiffSplitRow } from '@adecore/merge';
|
|
2
|
+
import type { HostedRow } from './row-host.ts';
|
|
3
|
+
export declare function splitAlignment(aligned: readonly DiffSplitRow[], oldHeight: (line: number) => number, newHeight: (line: number) => number, gapHeight?: number): {
|
|
4
|
+
old: HostedRow[];
|
|
5
|
+
new: HostedRow[];
|
|
6
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* Intrinsic heights exclude spacers, so repeated alignment converges without adding its own padding. */
|
|
2
|
+
export function splitAlignment(aligned, oldHeight, newHeight, gapHeight = 32) {
|
|
3
|
+
const rows = { old: [], new: [] };
|
|
4
|
+
const previous = { old: -1, new: -1 };
|
|
5
|
+
const add = (side, index, height, gap) => {
|
|
6
|
+
if (height <= 0) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const line = Math.max(0, previous[side]);
|
|
10
|
+
const placement = previous[side] < 0 ? 'above' : 'below';
|
|
11
|
+
const last = rows[side].at(-1);
|
|
12
|
+
if (gap === undefined && last?.filler && last.line === line && last.placement === placement) {
|
|
13
|
+
last.height += height;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
rows[side].push({ id: gap === undefined ? `spacer-${index}` : `gap-${gap}`, line, placement, height, fixed: true, filler: gap === undefined });
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
for (const [index, entry] of aligned.entries()) {
|
|
20
|
+
if (entry.kind === 'gap') {
|
|
21
|
+
add('old', index, gapHeight, entry.row.id);
|
|
22
|
+
add('new', index, gapHeight, entry.row.id);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const old = entry.old === undefined ? 0 : oldHeight(entry.old);
|
|
26
|
+
const fresh = entry.new === undefined ? 0 : newHeight(entry.new);
|
|
27
|
+
if (entry.old !== undefined) {
|
|
28
|
+
previous.old = entry.old;
|
|
29
|
+
}
|
|
30
|
+
if (entry.new !== undefined) {
|
|
31
|
+
previous.new = entry.new;
|
|
32
|
+
}
|
|
33
|
+
const target = Math.max(old, fresh);
|
|
34
|
+
add('old', index, target - old);
|
|
35
|
+
add('new', index, target - fresh);
|
|
36
|
+
}
|
|
37
|
+
return rows;
|
|
38
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Editor } from '@adecore/editor';
|
|
2
|
+
import type { DiffSourceSide } from './diff-syntax.ts';
|
|
3
|
+
type ScrollEditor = Pick<Editor, 'getScrollTop' | 'setScrollTop'>;
|
|
4
|
+
export declare class SplitScroll {
|
|
5
|
+
private leader;
|
|
6
|
+
private syncing;
|
|
7
|
+
private readonly expected;
|
|
8
|
+
private readonly editors;
|
|
9
|
+
constructor(editors: Record<DiffSourceSide, ScrollEditor>);
|
|
10
|
+
drive(side: DiffSourceSide): void;
|
|
11
|
+
changed(side: DiffSourceSide): void;
|
|
12
|
+
sync(): void;
|
|
13
|
+
}
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export class SplitScroll {
|
|
2
|
+
leader = 'new';
|
|
3
|
+
syncing = false;
|
|
4
|
+
expected;
|
|
5
|
+
editors;
|
|
6
|
+
constructor(editors) {
|
|
7
|
+
this.editors = editors;
|
|
8
|
+
this.expected = { old: editors.old.getScrollTop(), new: editors.new.getScrollTop() };
|
|
9
|
+
}
|
|
10
|
+
drive(side) {
|
|
11
|
+
this.leader = side;
|
|
12
|
+
}
|
|
13
|
+
changed(side) {
|
|
14
|
+
if (this.syncing) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const top = this.editors[side].getScrollTop();
|
|
18
|
+
if (side !== this.leader) {
|
|
19
|
+
// Native scroll echoes arrive after setScrollTop returns, including a clamped follower position.
|
|
20
|
+
if (Math.abs(top - this.expected[side]) < 1) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
this.leader = side;
|
|
24
|
+
}
|
|
25
|
+
this.sync();
|
|
26
|
+
}
|
|
27
|
+
sync() {
|
|
28
|
+
const side = this.leader;
|
|
29
|
+
const other = side === 'old' ? 'new' : 'old';
|
|
30
|
+
const top = this.editors[side].getScrollTop();
|
|
31
|
+
this.expected[side] = top;
|
|
32
|
+
if (Math.abs(this.editors[other].getScrollTop() - top) < 1) {
|
|
33
|
+
this.expected[other] = this.editors[other].getScrollTop();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
this.syncing = true;
|
|
37
|
+
try {
|
|
38
|
+
this.editors[other].setScrollTop(top);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
this.expected[other] = this.editors[other].getScrollTop();
|
|
42
|
+
this.syncing = false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
package/dist/symbols.d.ts
CHANGED
|
@@ -6,9 +6,10 @@ export interface SymbolSpan {
|
|
|
6
6
|
readonly name: string;
|
|
7
7
|
readonly kind: number;
|
|
8
8
|
readonly range: Range;
|
|
9
|
+
readonly nameLine?: number;
|
|
9
10
|
}
|
|
10
11
|
export declare function symbolSpansOf(result: DocumentSymbolResult): SymbolSpan[];
|
|
11
|
-
export declare function blocksOf(result: DocumentSymbolResult): EditorBlock[];
|
|
12
|
+
export declare function blocksOf(result: DocumentSymbolResult, text?: string): EditorBlock[];
|
|
12
13
|
export declare class SymbolsFeature {
|
|
13
14
|
private readonly refresher;
|
|
14
15
|
private readonly listeners;
|