@adecore/editor-react 0.39.0 → 0.40.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/CallHierarchyPanel.js +30 -55
- package/dist/CodeAuthorsCard.js +0 -1
- package/dist/CompletionPopup.js +150 -181
- package/dist/DiffView.d.ts +1 -1
- package/dist/DiffView.js +2 -29
- package/dist/PickPopup.js +54 -80
- package/dist/SplitDiffView.js +27 -57
- package/dist/SymbolPicker.js +2 -8
- package/dist/code-actions-model.js +2 -9
- package/dist/code-actions.js +10 -11
- package/dist/code-vision.js +2 -1
- package/dist/completion-model.js +2 -2
- package/dist/completion.js +13 -34
- package/dist/definition-link.d.ts +1 -0
- package/dist/definition-link.js +22 -18
- package/dist/diagnostics-model.d.ts +1 -0
- package/dist/diagnostics-model.js +3 -1
- package/dist/diagnostics.js +1 -2
- package/dist/diff-syntax.d.ts +1 -0
- package/dist/diff-syntax.js +31 -0
- package/dist/doc-comment-model.d.ts +1 -0
- package/dist/doc-comment-model.js +1 -1
- package/dist/folding.js +2 -3
- package/dist/format-on-type.js +6 -10
- package/dist/hover-content.d.ts +0 -2
- package/dist/hover-content.js +1 -9
- package/dist/inlay-hints.d.ts +0 -1
- package/dist/inlay-hints.js +11 -41
- package/dist/navigation.js +4 -14
- package/dist/peek.js +2 -12
- package/dist/popup-focus.d.ts +3 -1
- package/dist/popup-focus.js +33 -0
- package/dist/recent-choices.d.ts +0 -1
- package/dist/recent-choices.js +0 -4
- package/dist/rendered-docs.d.ts +1 -0
- package/dist/rendered-docs.js +15 -28
- package/dist/snippet-field-transforms.d.ts +5 -0
- package/dist/snippet-field-transforms.js +27 -16
- package/dist/snippet-session.js +8 -20
- package/dist/symbol-links.js +3 -10
- package/dist/symbol-picker-model.d.ts +1 -0
- package/dist/symbol-picker-model.js +7 -0
- package/dist/unique.d.ts +10 -0
- package/dist/unique.js +15 -0
- package/package.json +9 -9
- package/src/AnchoredPopup.tsx +2 -0
- package/src/BaselineStatus.tsx +1 -0
- package/src/CallHierarchyPanel.tsx +16 -6
- package/src/ChangeReview.tsx +7 -0
- package/src/CodeAuthorsCard.tsx +2 -5
- package/src/CompletionPopup.tsx +21 -15
- package/src/DiffView.tsx +53 -30
- package/src/EditorContextMenu.tsx +7 -0
- package/src/EditorView.tsx +28 -0
- package/src/FindReplace.tsx +8 -0
- package/src/HoverCard.tsx +8 -0
- package/src/HoverSections.tsx +11 -0
- package/src/LanguagePopups.tsx +1 -0
- package/src/PanelResizeHandle.tsx +8 -0
- package/src/PathText.tsx +1 -0
- package/src/PeekPanel.tsx +13 -0
- package/src/PickPopup.tsx +7 -6
- package/src/RenameCard.tsx +13 -0
- package/src/RenderedDocs.tsx +11 -0
- package/src/SplitDiffView.tsx +91 -56
- package/src/SymbolPicker.tsx +10 -18
- package/src/call-hierarchy-model.ts +10 -0
- package/src/call-hierarchy.ts +82 -0
- package/src/code-actions-model.ts +21 -9
- package/src/code-actions.ts +83 -7
- package/src/code-authors.ts +20 -0
- package/src/code-vision-model.ts +37 -0
- package/src/code-vision.ts +47 -3
- package/src/completion-call.ts +19 -0
- package/src/completion-model.ts +58 -2
- package/src/completion.ts +156 -30
- package/src/conflict-model.ts +4 -0
- package/src/context-menu-rows.ts +1 -0
- package/src/context-menu.ts +7 -0
- package/src/definition-link.ts +42 -18
- package/src/diagnostics-model.ts +19 -1
- package/src/diagnostics.ts +31 -2
- package/src/diff-expansion.ts +9 -0
- package/src/diff-overlays.tsx +8 -0
- package/src/diff-syntax.ts +54 -0
- package/src/diff-wordmarks.ts +8 -0
- package/src/doc-comment-model.ts +51 -1
- package/src/editor-language.ts +11 -0
- package/src/find-query.ts +7 -0
- package/src/folding.ts +10 -3
- package/src/format-on-type.ts +23 -10
- package/src/highlight-layers.ts +3 -0
- package/src/highlights.ts +18 -0
- package/src/history.ts +11 -0
- package/src/holder.ts +3 -0
- package/src/hover-content.ts +34 -17
- package/src/hover.ts +57 -0
- package/src/index.ts +4 -0
- package/src/inlay-hints.ts +86 -47
- package/src/key-priority.ts +2 -0
- package/src/line-edits.ts +8 -0
- package/src/models.ts +6 -0
- package/src/navigation-history.ts +5 -0
- package/src/navigation.ts +28 -13
- package/src/panel-size.ts +7 -0
- package/src/patch-decorations.ts +39 -0
- package/src/patch-layout.ts +47 -0
- package/src/peek-model.ts +21 -0
- package/src/peek.ts +49 -13
- package/src/pick.ts +35 -0
- package/src/popup-focus.ts +11 -1
- package/src/popup-placement.ts +5 -0
- package/src/popups.ts +2 -6
- package/src/project-language.ts +33 -0
- package/src/project-problems.ts +11 -0
- package/src/proposal-model.ts +40 -0
- package/src/recent-choices.ts +3 -5
- package/src/refresher.ts +3 -0
- package/src/rename-model.ts +28 -0
- package/src/rename.ts +56 -0
- package/src/rendered-docs.ts +89 -19
- package/src/review-group.ts +4 -0
- package/src/row-host.ts +12 -0
- package/src/selection-ranges.ts +6 -0
- package/src/semantic-delta.ts +7 -0
- package/src/semantic-model.ts +11 -0
- package/src/semantic-tokens.ts +37 -0
- package/src/shortcut-keys.ts +3 -0
- package/src/signature-model.ts +7 -0
- package/src/signature.ts +27 -0
- package/src/snippet-field-transforms.ts +48 -13
- package/src/snippet-indent.ts +10 -0
- package/src/snippet-range-order.ts +5 -0
- package/src/snippet-session.ts +173 -31
- package/src/snippet-transform-client.ts +14 -0
- package/src/snippet-transform-format.ts +98 -0
- package/src/snippet-transform-protocol.ts +5 -0
- package/src/snippet-transform-queue.ts +53 -0
- package/src/snippet-transform-worker.ts +14 -0
- package/src/snippet-variables.ts +9 -0
- package/src/snippet.ts +107 -0
- package/src/split-alignment.ts +10 -0
- package/src/split-scroll.ts +8 -0
- package/src/symbol-links.ts +9 -12
- package/src/symbol-picker-model.ts +25 -0
- package/src/symbol-picker.ts +9 -0
- package/src/symbols.ts +21 -0
- package/src/testing.ts +7 -0
- package/src/timers.ts +5 -0
- package/src/unique.ts +23 -0
- package/src/use-problem-counts.ts +4 -0
- package/src/view-state.ts +9 -0
- package/src/word-diff.ts +5 -0
- package/src/workspace-edit.ts +84 -0
package/src/semantic-tokens.ts
CHANGED
|
@@ -16,6 +16,7 @@ const SCROLL_PAUSE_MS = 100;
|
|
|
16
16
|
|
|
17
17
|
/* A document with more lines or characters than this is first asked for what is in view, when the server answers ranges. */
|
|
18
18
|
export const DEFAULT_RANGE_LINES = 5000;
|
|
19
|
+
|
|
19
20
|
export const DEFAULT_RANGE_CHARACTERS = 500_000;
|
|
20
21
|
|
|
21
22
|
/* What decides when a document is too large to wait for in full. */
|
|
@@ -45,10 +46,13 @@ export class SemanticTokensFeature {
|
|
|
45
46
|
private readonly options: SemanticTokensOptions;
|
|
46
47
|
private held: Held | null = null;
|
|
47
48
|
private heldLegend = '';
|
|
49
|
+
|
|
48
50
|
/* True once the whole document is drawn from a full answer; a range answer then no longer paints. */
|
|
49
51
|
private complete = false;
|
|
52
|
+
|
|
50
53
|
/* The lines a range answer drew, null when nothing of that kind is on screen. */
|
|
51
54
|
private covered: EditorRange | null = null;
|
|
55
|
+
|
|
52
56
|
private drawn: EditorSemanticToken[] = [];
|
|
53
57
|
private epoch = 0;
|
|
54
58
|
|
|
@@ -58,22 +62,26 @@ export class SemanticTokensFeature {
|
|
|
58
62
|
this.options = options;
|
|
59
63
|
this.refresher = new Refresher((signal) => this.refresh(signal), PAUSE_MS, timers);
|
|
60
64
|
this.scrolled = new Refresher((signal) => this.fillViewport(signal), SCROLL_PAUSE_MS, timers);
|
|
65
|
+
|
|
61
66
|
const edits = editor.onTextChange(() => {
|
|
62
67
|
this.epoch++;
|
|
63
68
|
this.covered = null;
|
|
64
69
|
this.refresher.later();
|
|
65
70
|
});
|
|
71
|
+
|
|
66
72
|
const view = editor.onViewChange(() => {
|
|
67
73
|
if (!this.complete && this.covered !== null && !covers(this.covered, editor.getVisibleRange())) {
|
|
68
74
|
this.scrolled.later();
|
|
69
75
|
}
|
|
70
76
|
});
|
|
77
|
+
|
|
71
78
|
const providers = language.onProvidersChanged(() => {
|
|
72
79
|
this.epoch++;
|
|
73
80
|
this.scrolled.cancel();
|
|
74
81
|
this.forget();
|
|
75
82
|
this.refresher.now();
|
|
76
83
|
});
|
|
84
|
+
|
|
77
85
|
language.onDispose(() => {
|
|
78
86
|
edits();
|
|
79
87
|
view();
|
|
@@ -97,16 +105,19 @@ export class SemanticTokensFeature {
|
|
|
97
105
|
const { uri } = this.language;
|
|
98
106
|
const legend = (service.providerOptions(FULL, uri) ?? service.providerOptions(RANGE, uri))?.legend;
|
|
99
107
|
const key = legend === undefined ? '' : JSON.stringify(legend);
|
|
108
|
+
|
|
100
109
|
if (key !== this.heldLegend) {
|
|
101
110
|
this.heldLegend = key;
|
|
102
111
|
this.forget();
|
|
103
112
|
}
|
|
113
|
+
|
|
104
114
|
return legend;
|
|
105
115
|
}
|
|
106
116
|
|
|
107
117
|
private isLarge(): boolean {
|
|
108
118
|
const { editor } = this.language;
|
|
109
119
|
const end = editor.positionAt(Number.MAX_SAFE_INTEGER);
|
|
120
|
+
|
|
110
121
|
return (
|
|
111
122
|
end.line + 1 > (this.options.rangeLines ?? DEFAULT_RANGE_LINES) || editor.offsetAt(end) > (this.options.rangeCharacters ?? DEFAULT_RANGE_CHARACTERS)
|
|
112
123
|
);
|
|
@@ -114,6 +125,7 @@ export class SemanticTokensFeature {
|
|
|
114
125
|
|
|
115
126
|
private viewport(): EditorRange {
|
|
116
127
|
const { editor } = this.language;
|
|
128
|
+
|
|
117
129
|
return withMargin(editor.getVisibleRange(), editor.positionAt(Number.MAX_SAFE_INTEGER), (line) => ({
|
|
118
130
|
line,
|
|
119
131
|
character: lineTextOf(editor, line).length
|
|
@@ -126,20 +138,26 @@ export class SemanticTokensFeature {
|
|
|
126
138
|
const legend = this.legend();
|
|
127
139
|
const full = service.supports(FULL, uri);
|
|
128
140
|
const range = service.supports(RANGE, uri);
|
|
141
|
+
|
|
129
142
|
if (legend === undefined || (!full && !range)) {
|
|
130
143
|
return;
|
|
131
144
|
}
|
|
145
|
+
|
|
132
146
|
if (!full || (range && !this.complete && this.held === null && this.isLarge())) {
|
|
133
147
|
await this.askRange(this.viewport(), legend, signal, true);
|
|
148
|
+
|
|
134
149
|
if (!full) {
|
|
135
150
|
return;
|
|
136
151
|
}
|
|
137
152
|
}
|
|
153
|
+
|
|
138
154
|
const epoch = this.epoch;
|
|
139
155
|
const tokens = await this.askFull(signal);
|
|
156
|
+
|
|
140
157
|
if (signal.aborted || epoch !== this.epoch) {
|
|
141
158
|
return;
|
|
142
159
|
}
|
|
160
|
+
|
|
143
161
|
this.complete = tokens !== null;
|
|
144
162
|
this.covered = null;
|
|
145
163
|
this.drawn = [];
|
|
@@ -151,27 +169,36 @@ export class SemanticTokensFeature {
|
|
|
151
169
|
const { service } = this.language.project;
|
|
152
170
|
const { uri } = this.language;
|
|
153
171
|
const base = this.held;
|
|
172
|
+
|
|
154
173
|
if (base !== null && service.supports(DELTA, uri)) {
|
|
155
174
|
const answer = await service.semanticTokensDelta(uri, base.resultId, { signal }).catch((error: unknown) => {
|
|
156
175
|
if (signal.aborted || error instanceof StaleResultError) {
|
|
157
176
|
throw error;
|
|
158
177
|
}
|
|
178
|
+
|
|
159
179
|
// A server that no longer knows our result id is asked in full instead, once.
|
|
160
180
|
return null;
|
|
161
181
|
});
|
|
182
|
+
|
|
162
183
|
signal.throwIfAborted();
|
|
184
|
+
|
|
163
185
|
if (answer !== null && this.held === base) {
|
|
164
186
|
const tokens = this.fromDelta(base, answer);
|
|
187
|
+
|
|
165
188
|
if (tokens !== null) {
|
|
166
189
|
this.hold(tokens);
|
|
190
|
+
|
|
167
191
|
return tokens;
|
|
168
192
|
}
|
|
169
193
|
}
|
|
194
|
+
|
|
170
195
|
this.held = null;
|
|
171
196
|
}
|
|
197
|
+
|
|
172
198
|
const tokens = await service.semanticTokens(uri, { signal });
|
|
173
199
|
signal.throwIfAborted();
|
|
174
200
|
this.hold(tokens);
|
|
201
|
+
|
|
175
202
|
return tokens;
|
|
176
203
|
}
|
|
177
204
|
|
|
@@ -180,7 +207,9 @@ export class SemanticTokensFeature {
|
|
|
180
207
|
if (!('edits' in answer)) {
|
|
181
208
|
return answer;
|
|
182
209
|
}
|
|
210
|
+
|
|
183
211
|
const data = applySemanticEdits(base.data, answer.edits);
|
|
212
|
+
|
|
184
213
|
return data === null ? null : { ...(answer.resultId === undefined ? {} : { resultId: answer.resultId }), data };
|
|
185
214
|
}
|
|
186
215
|
|
|
@@ -195,22 +224,28 @@ export class SemanticTokensFeature {
|
|
|
195
224
|
const epoch = this.epoch;
|
|
196
225
|
const before = this.covered;
|
|
197
226
|
const tokens = await service.semanticTokensRange(uri, range, { signal });
|
|
227
|
+
|
|
198
228
|
if (signal.aborted || epoch !== this.epoch || this.complete) {
|
|
199
229
|
return;
|
|
200
230
|
}
|
|
231
|
+
|
|
201
232
|
const decoded = tokens === null ? [] : decodeSemanticTokens(tokens, legend);
|
|
233
|
+
|
|
202
234
|
if (fresh || before === null || range.start.line > before.end.line + 1 || range.end.line < before.start.line - 1) {
|
|
203
235
|
this.covered = range;
|
|
204
236
|
this.drawn = decoded;
|
|
205
237
|
} else {
|
|
206
238
|
const kept = this.drawn.filter((token) => token.line < range.start.line || token.line > range.end.line);
|
|
207
239
|
this.drawn = [...kept, ...decoded];
|
|
240
|
+
|
|
208
241
|
this.covered = {
|
|
209
242
|
start: before.start.line < range.start.line ? before.start : range.start,
|
|
210
243
|
end: before.end.line > range.end.line ? before.end : range.end
|
|
211
244
|
};
|
|
212
245
|
}
|
|
246
|
+
|
|
213
247
|
editor.setSemanticTokens(this.drawn);
|
|
248
|
+
|
|
214
249
|
if (!this.complete && this.covered !== null && !covers(this.covered, editor.getVisibleRange())) {
|
|
215
250
|
this.scrolled.later();
|
|
216
251
|
}
|
|
@@ -219,9 +254,11 @@ export class SemanticTokensFeature {
|
|
|
219
254
|
private async fillViewport(signal: AbortSignal): Promise<void> {
|
|
220
255
|
const { service } = this.language.project;
|
|
221
256
|
const legend = this.legend();
|
|
257
|
+
|
|
222
258
|
if (legend === undefined || !service.supports(RANGE, this.language.uri) || this.complete) {
|
|
223
259
|
return;
|
|
224
260
|
}
|
|
261
|
+
|
|
225
262
|
await this.askRange(this.viewport(), legend, signal, false);
|
|
226
263
|
}
|
|
227
264
|
}
|
package/src/shortcut-keys.ts
CHANGED
|
@@ -10,9 +10,11 @@ export function shortcutsOf(keymap: Keymap, apple: boolean) {
|
|
|
10
10
|
const shortcuts = Object.fromEntries(
|
|
11
11
|
KEYMAP_IDS.map((id) => {
|
|
12
12
|
const text = chordOf(id, apple, keymap);
|
|
13
|
+
|
|
13
14
|
return [id, text === null ? null : { ...parseChord(text), apple }];
|
|
14
15
|
})
|
|
15
16
|
) as Record<keyof Keymap, LanguageShortcut | null>;
|
|
17
|
+
|
|
16
18
|
return { ...shortcuts, rename: shortcuts.renameSymbol };
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -21,5 +23,6 @@ export function isShortcut(target: LanguageShortcut | null, event: KeyboardEvent
|
|
|
21
23
|
if (target === null || typesInsteadOfChord(event, target.apple)) {
|
|
22
24
|
return false;
|
|
23
25
|
}
|
|
26
|
+
|
|
24
27
|
return matchesShortcut(target, event, target.apple);
|
|
25
28
|
}
|
package/src/signature-model.ts
CHANGED
|
@@ -17,14 +17,19 @@ export interface SignatureViewModel {
|
|
|
17
17
|
/* Where a parameter's label stands in the signature's: given as a pair of offsets, or as the text to find in it. */
|
|
18
18
|
function parameterSpan(signature: SignatureInformation, index: number): { start: number; end: number } | null {
|
|
19
19
|
const parameter = signature.parameters?.[index];
|
|
20
|
+
|
|
20
21
|
if (parameter === undefined) {
|
|
21
22
|
return null;
|
|
22
23
|
}
|
|
24
|
+
|
|
23
25
|
if (typeof parameter.label !== 'string') {
|
|
24
26
|
const [start, end] = parameter.label;
|
|
27
|
+
|
|
25
28
|
return start >= 0 && end <= signature.label.length && start < end ? { start, end } : null;
|
|
26
29
|
}
|
|
30
|
+
|
|
27
31
|
const start = signature.label.indexOf(parameter.label);
|
|
32
|
+
|
|
28
33
|
return start < 0 ? null : { start, end: start + parameter.label.length };
|
|
29
34
|
}
|
|
30
35
|
|
|
@@ -32,11 +37,13 @@ export function signatureViewOf(help: SignatureHelp | null): SignatureViewModel
|
|
|
32
37
|
if (help === null || help.signatures.length === 0) {
|
|
33
38
|
return null;
|
|
34
39
|
}
|
|
40
|
+
|
|
35
41
|
const index = Math.min(Math.max(0, help.activeSignature ?? 0), help.signatures.length - 1);
|
|
36
42
|
const signature = help.signatures[index]!;
|
|
37
43
|
const parameterIndex = signature.activeParameter ?? help.activeParameter ?? 0;
|
|
38
44
|
const active = parameterSpan(signature, parameterIndex);
|
|
39
45
|
const parameter = signature.parameters?.[parameterIndex];
|
|
46
|
+
|
|
40
47
|
return {
|
|
41
48
|
label: signature.label,
|
|
42
49
|
active,
|
package/src/signature.ts
CHANGED
|
@@ -27,6 +27,7 @@ export class SignatureFeature {
|
|
|
27
27
|
this.language = language;
|
|
28
28
|
this.timers = timers;
|
|
29
29
|
const { editor } = language;
|
|
30
|
+
|
|
30
31
|
const offs = [
|
|
31
32
|
editor.onTextChange((change) => this.edited(change)),
|
|
32
33
|
editor.onCaret(() => this.retrigger()),
|
|
@@ -34,22 +35,29 @@ export class SignatureFeature {
|
|
|
34
35
|
language.onKey((event) => {
|
|
35
36
|
if (event.key === 'Escape' && this.isOpen && this.language.popups.getState().callHierarchy === null) {
|
|
36
37
|
this.close();
|
|
38
|
+
|
|
37
39
|
return true;
|
|
38
40
|
}
|
|
41
|
+
|
|
39
42
|
if (isShortcut(this.language.shortcuts.parameterInfo, event)) {
|
|
40
43
|
this.invoke();
|
|
44
|
+
|
|
41
45
|
return true;
|
|
42
46
|
}
|
|
47
|
+
|
|
43
48
|
if ((event.key === 'ArrowUp' || event.key === 'ArrowDown') && !event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
|
|
44
49
|
return this.cycle(event.key === 'ArrowDown' ? 1 : -1);
|
|
45
50
|
}
|
|
51
|
+
|
|
46
52
|
return false;
|
|
47
53
|
}, KEY_PRIORITY.signature)
|
|
48
54
|
];
|
|
55
|
+
|
|
49
56
|
language.onDispose(() => {
|
|
50
57
|
for (const off of offs) {
|
|
51
58
|
off();
|
|
52
59
|
}
|
|
60
|
+
|
|
53
61
|
this.close();
|
|
54
62
|
});
|
|
55
63
|
}
|
|
@@ -71,17 +79,22 @@ export class SignatureFeature {
|
|
|
71
79
|
/* Shows the next or previous overload; false when the card has fewer than two, which leaves the arrow to the caret. */
|
|
72
80
|
private cycle(step: 1 | -1): boolean {
|
|
73
81
|
const { help } = this;
|
|
82
|
+
|
|
74
83
|
if (help === null || help.signatures.length < 2 || !this.isOpen) {
|
|
75
84
|
return false;
|
|
76
85
|
}
|
|
86
|
+
|
|
77
87
|
const count = help.signatures.length;
|
|
78
88
|
const next = { ...help, activeSignature: (Math.min(Math.max(0, help.activeSignature ?? 0), count - 1) + step + count) % count };
|
|
79
89
|
const model = signatureViewOf(next);
|
|
90
|
+
|
|
80
91
|
if (model === null || this.anchor === null) {
|
|
81
92
|
return false;
|
|
82
93
|
}
|
|
94
|
+
|
|
83
95
|
this.help = next;
|
|
84
96
|
this.language.popups.setState({ signature: { anchor: this.anchor, model } });
|
|
97
|
+
|
|
85
98
|
return true;
|
|
86
99
|
}
|
|
87
100
|
|
|
@@ -91,6 +104,7 @@ export class SignatureFeature {
|
|
|
91
104
|
this.timers.clear(this.timer);
|
|
92
105
|
this.help = null;
|
|
93
106
|
this.anchor = null;
|
|
107
|
+
|
|
94
108
|
if (this.isOpen) {
|
|
95
109
|
this.language.popups.setState({ signature: null });
|
|
96
110
|
}
|
|
@@ -99,17 +113,22 @@ export class SignatureFeature {
|
|
|
99
113
|
private options(): { triggers: readonly string[]; retriggers: readonly string[] } | null {
|
|
100
114
|
const { project, uri } = this.language;
|
|
101
115
|
const options = project.service.providerOptions(METHOD, uri);
|
|
116
|
+
|
|
102
117
|
return options === undefined ? null : { triggers: options.triggerCharacters ?? [], retriggers: options.retriggerCharacters ?? [] };
|
|
103
118
|
}
|
|
104
119
|
|
|
105
120
|
private edited(change: EditorTextChange): void {
|
|
106
121
|
const options = this.options();
|
|
122
|
+
|
|
107
123
|
if (change.source !== 'input' || options === null) {
|
|
108
124
|
this.close();
|
|
125
|
+
|
|
109
126
|
return;
|
|
110
127
|
}
|
|
128
|
+
|
|
111
129
|
const text = change.changes.length === 1 ? change.changes[0]!.text : '';
|
|
112
130
|
const last = Array.from(text).at(-1) ?? '';
|
|
131
|
+
|
|
113
132
|
if (text.length === 1 && options.triggers.includes(last)) {
|
|
114
133
|
this.ask({ triggerKind: 2, triggerCharacter: last, isRetrigger: this.isOpen });
|
|
115
134
|
} else if (this.isOpen) {
|
|
@@ -129,9 +148,11 @@ export class SignatureFeature {
|
|
|
129
148
|
|
|
130
149
|
private ask(context: Omit<SignatureHelpContext, 'activeSignatureHelp'>): void {
|
|
131
150
|
const { project, uri } = this.language;
|
|
151
|
+
|
|
132
152
|
if (!project.service.supports(METHOD, uri)) {
|
|
133
153
|
return;
|
|
134
154
|
}
|
|
155
|
+
|
|
135
156
|
this.timers.clear(this.timer);
|
|
136
157
|
const request = ++this.request;
|
|
137
158
|
const full: SignatureHelpContext = { ...context, ...(this.help === null ? {} : { activeSignatureHelp: this.help }) };
|
|
@@ -145,19 +166,25 @@ export class SignatureFeature {
|
|
|
145
166
|
const controller = new AbortController();
|
|
146
167
|
this.controller = controller;
|
|
147
168
|
let help: SignatureHelp | null;
|
|
169
|
+
|
|
148
170
|
try {
|
|
149
171
|
help = await project.service.signatureHelp(uri, caret, context, { signal: controller.signal });
|
|
150
172
|
} catch {
|
|
151
173
|
return;
|
|
152
174
|
}
|
|
175
|
+
|
|
153
176
|
if (request !== this.request) {
|
|
154
177
|
return;
|
|
155
178
|
}
|
|
179
|
+
|
|
156
180
|
const model = signatureViewOf(help);
|
|
181
|
+
|
|
157
182
|
if (model === null) {
|
|
158
183
|
this.close();
|
|
184
|
+
|
|
159
185
|
return;
|
|
160
186
|
}
|
|
187
|
+
|
|
161
188
|
this.help = help;
|
|
162
189
|
this.anchor ??= caret;
|
|
163
190
|
this.language.popups.setState({ signature: { anchor: this.anchor, model } });
|
|
@@ -15,6 +15,28 @@ export interface FieldTransformTarget {
|
|
|
15
15
|
readonly order: readonly number[];
|
|
16
16
|
readonly ancestors?: readonly symbol[];
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
/* Each distinct job once, so a transform that several ranges share is computed once; `slotOf` is its index in `jobs`. */
|
|
20
|
+
export function jobTable(): { readonly jobs: SnippetTransformJob[]; readonly slotOf: (job: SnippetTransformJob) => number } {
|
|
21
|
+
const jobs: SnippetTransformJob[] = [];
|
|
22
|
+
const keys = new Map<string, number>();
|
|
23
|
+
|
|
24
|
+
const slotOf = (job: SnippetTransformJob): number => {
|
|
25
|
+
const key = JSON.stringify(job);
|
|
26
|
+
let slot = keys.get(key);
|
|
27
|
+
|
|
28
|
+
if (slot === undefined) {
|
|
29
|
+
slot = jobs.length;
|
|
30
|
+
keys.set(key, slot);
|
|
31
|
+
jobs.push(job);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return slot;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return { jobs, slotOf };
|
|
38
|
+
}
|
|
39
|
+
|
|
18
40
|
interface Entry {
|
|
19
41
|
readonly targets: FieldTransformTarget[];
|
|
20
42
|
readonly current: () => boolean;
|
|
@@ -30,51 +52,52 @@ export class SnippetFieldTransforms {
|
|
|
30
52
|
private readonly editor: Editor;
|
|
31
53
|
private readonly lease: SnippetTransformLease | null;
|
|
32
54
|
private readonly apply: (targets: readonly FieldTransformTarget[], values: readonly string[]) => void;
|
|
55
|
+
|
|
33
56
|
constructor(editor: Editor, lease: SnippetTransformLease | null, apply: (targets: readonly FieldTransformTarget[], values: readonly string[]) => void) {
|
|
34
57
|
this.editor = editor;
|
|
35
58
|
this.lease = lease;
|
|
36
59
|
this.apply = apply;
|
|
37
60
|
}
|
|
61
|
+
|
|
38
62
|
get pending(): boolean {
|
|
39
63
|
return this.entries.size > 0;
|
|
40
64
|
}
|
|
65
|
+
|
|
41
66
|
get ranges(): number {
|
|
42
67
|
return [...this.entries].reduce((sum, entry) => sum + entry.targets.length, 0);
|
|
43
68
|
}
|
|
69
|
+
|
|
44
70
|
async settle(): Promise<void> {
|
|
45
71
|
await Promise.all([...this.entries].map((entry) => entry.done));
|
|
46
72
|
}
|
|
73
|
+
|
|
47
74
|
enqueue(targets: FieldTransformTarget[], current: () => boolean): void {
|
|
48
75
|
if (targets.length === 0 || this.lease === null) {
|
|
49
76
|
return;
|
|
50
77
|
}
|
|
78
|
+
|
|
51
79
|
let finish!: () => void;
|
|
80
|
+
|
|
52
81
|
const done = new Promise<void>((resolve) => {
|
|
53
82
|
finish = resolve;
|
|
54
83
|
});
|
|
84
|
+
|
|
55
85
|
const entry: Entry = { targets, current, done, finish, cancelled: false };
|
|
56
86
|
this.entries.add(entry);
|
|
57
|
-
const jobs
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const job = { ...target.transform, value: target.value },
|
|
61
|
-
key = JSON.stringify(job);
|
|
62
|
-
let slot = keys.get(key);
|
|
63
|
-
if (slot === undefined) {
|
|
64
|
-
slot = jobs.length;
|
|
65
|
-
keys.set(key, slot);
|
|
66
|
-
jobs.push(job);
|
|
67
|
-
}
|
|
68
|
-
return slot;
|
|
69
|
-
});
|
|
87
|
+
const { jobs, slotOf } = jobTable();
|
|
88
|
+
const slots = targets.map((target) => slotOf({ ...target.transform, value: target.value }));
|
|
89
|
+
|
|
70
90
|
const valid = (): boolean => !entry.cancelled && entry.current() && targets.every((target) => this.editor.textInRange(target.range) === target.value);
|
|
91
|
+
|
|
71
92
|
void this.lease
|
|
72
93
|
.request(jobs, { kind: 'exit', current: valid })
|
|
73
94
|
.then((values) => {
|
|
74
95
|
if (!valid()) {
|
|
75
96
|
return;
|
|
76
97
|
}
|
|
98
|
+
|
|
77
99
|
this.applying = entry;
|
|
100
|
+
|
|
78
101
|
try {
|
|
79
102
|
this.apply(
|
|
80
103
|
targets,
|
|
@@ -92,6 +115,7 @@ export class SnippetFieldTransforms {
|
|
|
92
115
|
finish();
|
|
93
116
|
});
|
|
94
117
|
}
|
|
118
|
+
|
|
95
119
|
prune(): void {
|
|
96
120
|
for (const entry of this.entries) {
|
|
97
121
|
if (!entry.current()) {
|
|
@@ -99,26 +123,32 @@ export class SnippetFieldTransforms {
|
|
|
99
123
|
}
|
|
100
124
|
}
|
|
101
125
|
}
|
|
126
|
+
|
|
102
127
|
clear(): void {
|
|
103
128
|
for (const entry of this.entries) {
|
|
104
129
|
this.cancel(entry);
|
|
105
130
|
}
|
|
106
131
|
}
|
|
132
|
+
|
|
107
133
|
private cancel(entry: Entry): void {
|
|
108
134
|
entry.cancelled = true;
|
|
109
135
|
this.entries.delete(entry);
|
|
110
136
|
entry.finish();
|
|
111
137
|
}
|
|
138
|
+
|
|
112
139
|
edited(changes: readonly EditorContentChange[], detached?: <Range extends EditorRange>(range: Range) => Range): void {
|
|
113
140
|
for (const entry of this.entries) {
|
|
114
141
|
if (entry === this.applying) {
|
|
115
142
|
continue;
|
|
116
143
|
}
|
|
144
|
+
|
|
117
145
|
for (const target of entry.targets) {
|
|
118
146
|
let { start, end } = target.range;
|
|
147
|
+
|
|
119
148
|
for (const change of changes) {
|
|
120
149
|
const empty = comparePositions(start, end) === 0;
|
|
121
150
|
const insert = comparePositions(change.range.start, change.range.end) === 0;
|
|
151
|
+
|
|
122
152
|
const overlaps = insert
|
|
123
153
|
? empty
|
|
124
154
|
? comparePositions(start, change.range.start) === 0
|
|
@@ -126,15 +156,20 @@ export class SnippetFieldTransforms {
|
|
|
126
156
|
: empty
|
|
127
157
|
? comparePositions(change.range.start, start) <= 0 && comparePositions(start, change.range.end) < 0
|
|
128
158
|
: comparePositions(change.range.start, end) < 0 && comparePositions(start, change.range.end) < 0;
|
|
159
|
+
|
|
129
160
|
if (overlaps && !(detached !== undefined && empty && insert)) {
|
|
130
161
|
this.cancel(entry);
|
|
162
|
+
|
|
131
163
|
break;
|
|
132
164
|
}
|
|
165
|
+
|
|
133
166
|
const insertAtEnd = insert && !empty && comparePositions(end, change.range.start) === 0;
|
|
134
167
|
start = shiftPosition(start, change);
|
|
135
168
|
end = insertAtEnd ? end : shiftPosition(end, change);
|
|
136
169
|
}
|
|
170
|
+
|
|
137
171
|
target.range = detached === undefined ? { start, end } : detached({ ...target.range, order: target.order });
|
|
172
|
+
|
|
138
173
|
if (entry.cancelled) {
|
|
139
174
|
break;
|
|
140
175
|
}
|
package/src/snippet-indent.ts
CHANGED
|
@@ -4,29 +4,38 @@ import { MAX_SNIPPET_TEXT, type ParsedSnippet, type SnippetRange } from './snipp
|
|
|
4
4
|
/* Indentation edits map the offsets once, including child projections in copied parents. */
|
|
5
5
|
export function indentSnippet(parsed: ParsedSnippet, before: string): ParsedSnippet {
|
|
6
6
|
const prefix = /^[\t ]*/.exec(before)?.[0] ?? '';
|
|
7
|
+
|
|
7
8
|
if (prefix === '' || !parsed.text.includes('\n')) {
|
|
8
9
|
return parsed;
|
|
9
10
|
}
|
|
11
|
+
|
|
10
12
|
const insertions: number[] = [];
|
|
13
|
+
|
|
11
14
|
for (let offset = parsed.text.indexOf('\n'); offset !== -1; offset = parsed.text.indexOf('\n', offset + 1)) {
|
|
12
15
|
insertions.push(offset + 1);
|
|
13
16
|
}
|
|
17
|
+
|
|
14
18
|
if (parsed.text.length + insertions.length * prefix.length > MAX_SNIPPET_TEXT) {
|
|
15
19
|
return { text: parsed.text, stops: [], limited: true };
|
|
16
20
|
}
|
|
21
|
+
|
|
17
22
|
const shift = (offset: number, after = true): number => {
|
|
18
23
|
let low = 0;
|
|
19
24
|
let high = insertions.length;
|
|
25
|
+
|
|
20
26
|
while (low < high) {
|
|
21
27
|
const middle = (low + high) >>> 1;
|
|
28
|
+
|
|
22
29
|
if (insertions[middle]! < offset || (after && insertions[middle] === offset)) {
|
|
23
30
|
low = middle + 1;
|
|
24
31
|
} else {
|
|
25
32
|
high = middle;
|
|
26
33
|
}
|
|
27
34
|
}
|
|
35
|
+
|
|
28
36
|
return offset + low * prefix.length;
|
|
29
37
|
};
|
|
38
|
+
|
|
30
39
|
const range = <Range extends SnippetRange>(range: Range): Range =>
|
|
31
40
|
withSnippetOrder(
|
|
32
41
|
{
|
|
@@ -36,6 +45,7 @@ export function indentSnippet(parsed: ParsedSnippet, before: string): ParsedSnip
|
|
|
36
45
|
},
|
|
37
46
|
snippetOrderOf(range)
|
|
38
47
|
);
|
|
48
|
+
|
|
39
49
|
return {
|
|
40
50
|
text: parsed.text.replace(/\n/g, '\n' + prefix),
|
|
41
51
|
stops: parsed.stops.map((stop) => range(stop)),
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
/* Source order disambiguates empty stops without changing the public parsed range shape. */
|
|
2
2
|
const orders = new WeakMap<object, number>();
|
|
3
|
+
|
|
3
4
|
export function snippetOrderOf(range: object): number | undefined {
|
|
4
5
|
return orders.get(range);
|
|
5
6
|
}
|
|
7
|
+
|
|
6
8
|
export function withSnippetOrder<Range extends object>(range: Range, order: number | undefined): Range {
|
|
7
9
|
if (order !== undefined) {
|
|
8
10
|
orders.set(range, order);
|
|
9
11
|
}
|
|
12
|
+
|
|
10
13
|
return range;
|
|
11
14
|
}
|
|
15
|
+
|
|
12
16
|
export function compareSnippetOrder(left: readonly number[], right: readonly number[]): number {
|
|
13
17
|
for (let index = 0; index < Math.min(left.length, right.length); index++) {
|
|
14
18
|
if (left[index] !== right[index]) {
|
|
15
19
|
return left[index]! - right[index]!;
|
|
16
20
|
}
|
|
17
21
|
}
|
|
22
|
+
|
|
18
23
|
return left.length - right.length;
|
|
19
24
|
}
|