@adecore/editor-react 0.31.12 → 0.33.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/AnchoredPopup.js +73 -19
- package/dist/AttributionCard.js +162 -4
- package/dist/BaselineStatus.js +62 -8
- package/dist/CallHierarchyPanel.js +977 -60
- package/dist/ChangeReview.js +76 -25
- package/dist/CodeAuthorsCard.js +314 -14
- package/dist/CompletionPopup.js +490 -83
- package/dist/DiffView.js +548 -181
- package/dist/EditorContextMenu.js +352 -34
- package/dist/EditorView.js +276 -99
- package/dist/FindReplace.js +525 -67
- package/dist/HoverCard.js +378 -38
- package/dist/HoverSections.js +367 -56
- package/dist/LanguagePopups.js +95 -34
- package/dist/Markdown.js +41 -6
- package/dist/PanelResizeHandle.js +199 -66
- package/dist/PathText.js +49 -7
- package/dist/PeekPanel.js +675 -72
- package/dist/PickPopup.js +304 -34
- package/dist/RenameCard.js +679 -85
- package/dist/RenderedDocs.js +329 -58
- package/dist/SignatureCard.js +91 -7
- package/dist/SplitDiffView.js +344 -254
- package/dist/SymbolPicker.js +440 -85
- package/dist/call-hierarchy-model.js +102 -68
- package/dist/call-hierarchy.js +458 -359
- package/dist/code-actions-model.js +88 -76
- package/dist/code-actions.js +372 -283
- package/dist/code-authors.js +64 -55
- package/dist/code-vision-model.js +134 -127
- package/dist/code-vision.js +224 -206
- package/dist/completion-call.js +94 -67
- package/dist/completion-model.js +334 -244
- package/dist/completion.js +840 -649
- package/dist/conflict-model.js +35 -29
- package/dist/context-menu-rows.js +42 -17
- package/dist/context-menu.js +64 -46
- package/dist/definition-link.js +88 -85
- package/dist/diagnostics-model.js +74 -55
- package/dist/diagnostics.js +121 -100
- package/dist/diff-expansion.js +38 -35
- package/dist/diff-overlays.js +144 -22
- package/dist/diff-syntax.js +65 -42
- package/dist/diff-wordmarks.js +26 -23
- package/dist/doc-comment-model.js +265 -216
- package/dist/editor-language.js +177 -152
- package/dist/error-message.js +2 -2
- package/dist/find-query.js +49 -36
- package/dist/folding.js +81 -62
- package/dist/format-on-type.js +86 -67
- package/dist/highlight-layers.js +19 -20
- package/dist/highlights.js +105 -83
- package/dist/history.js +44 -38
- package/dist/holder.js +18 -18
- package/dist/host-types.js +1 -1
- package/dist/hover-content.js +167 -129
- package/dist/hover.js +285 -245
- package/dist/index.js +10 -10
- package/dist/inlay-hints.js +369 -324
- package/dist/key-priority.js +34 -31
- package/dist/language-ids.js +45 -42
- package/dist/line-edits.js +74 -28
- package/dist/line-text.js +18 -3
- package/dist/locales/en.json +38 -38
- package/dist/locales/nl.json +41 -41
- package/dist/models.js +1 -1
- package/dist/navigation-history.js +40 -41
- package/dist/navigation.js +141 -123
- package/dist/panel-size.js +8 -8
- package/dist/patch-decorations.js +236 -160
- package/dist/patch-layout.js +331 -262
- package/dist/paths.js +3 -3
- package/dist/peek-model.js +86 -69
- package/dist/peek.js +254 -192
- package/dist/pick.js +164 -157
- package/dist/popup-focus.js +5 -5
- package/dist/popup-placement.js +36 -21
- package/dist/popups.js +13 -13
- package/dist/project-language.js +228 -211
- package/dist/project-problems.js +81 -69
- package/dist/proposal-model.js +163 -126
- package/dist/recent-choices.js +25 -25
- package/dist/refresher.js +44 -49
- package/dist/rename-model.js +113 -113
- package/dist/rename.js +321 -233
- package/dist/rendered-docs.d.ts +4 -2
- package/dist/rendered-docs.js +384 -318
- package/dist/rendering-context.js +5 -3
- package/dist/rendering.js +22 -4
- package/dist/review-group.js +42 -39
- package/dist/row-host.d.ts +4 -2
- package/dist/row-host.js +90 -78
- package/dist/selection-ranges.js +21 -17
- package/dist/semantic-delta.js +22 -19
- package/dist/semantic-model.js +86 -73
- package/dist/semantic-tokens.js +201 -162
- package/dist/shortcut-keys.js +16 -10
- package/dist/signature-model.js +34 -28
- package/dist/signature.js +177 -129
- package/dist/snippet-field-transforms.js +118 -106
- package/dist/snippet-indent.js +48 -43
- package/dist/snippet-range-order.js +11 -11
- package/dist/snippet-session.js +703 -667
- package/dist/snippet-transform-client.js +59 -59
- package/dist/snippet-transform-format.js +285 -263
- package/dist/snippet-transform-protocol.js +1 -1
- package/dist/snippet-transform-queue.js +163 -153
- package/dist/snippet-transform-worker.js +42 -35
- package/dist/snippet-variable-names.js +1 -36
- package/dist/snippet-variables.js +53 -63
- package/dist/snippet.js +477 -405
- package/dist/split-alignment.js +47 -35
- package/dist/split-scroll.js +42 -40
- package/dist/symbol-links.js +33 -25
- package/dist/symbol-picker-model.js +126 -116
- package/dist/symbol-picker.js +98 -59
- package/dist/symbols.js +117 -88
- package/dist/testing.js +248 -165
- package/dist/timers.js +35 -30
- package/dist/use-panel-body.js +49 -8
- package/dist/use-problem-counts.js +29 -15
- package/dist/view-state.js +54 -33
- package/dist/word-diff.js +18 -18
- package/dist/workspace-edit.js +300 -249
- package/package.json +6 -6
- package/src/CallHierarchyPanel.tsx +2 -2
- package/src/DiffView.tsx +3 -3
- package/src/FindReplace.tsx +2 -2
- package/src/LanguagePopups.tsx +2 -0
- package/src/PeekPanel.tsx +3 -3
- package/src/RenderedDocs.tsx +9 -10
- package/src/SymbolPicker.tsx +2 -2
- package/src/diff-overlays.tsx +6 -4
- package/src/locales/en.json +38 -38
- package/src/locales/nl.json +41 -41
- package/src/rendered-docs.ts +14 -3
- package/src/row-host.ts +14 -3
package/dist/completion.js
CHANGED
|
@@ -24,37 +24,49 @@ const LINES_BACK = 60;
|
|
|
24
24
|
const TOAST_ID = 'language-completion';
|
|
25
25
|
const MAX_COMPLETION_CHARACTERS = 2_000_000;
|
|
26
26
|
const MAX_COMPLETION_RANGES = 1_000;
|
|
27
|
-
class CompletionLimitError extends Error {
|
|
28
|
-
}
|
|
27
|
+
class CompletionLimitError extends Error {}
|
|
29
28
|
function mapCompletionEdits(item, changes, mapRange) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
29
|
+
// Changes apply one after another, so each is checked against the range as the earlier ones left it.
|
|
30
|
+
const overlapsChange = range => {
|
|
31
|
+
let {
|
|
32
|
+
start,
|
|
33
|
+
end
|
|
34
|
+
} = range;
|
|
35
|
+
for (const change of changes) {
|
|
36
|
+
if (comparePositions(start, change.range.end) < 0 && comparePositions(change.range.start, end) < 0) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
start = shiftPosition(start, change);
|
|
40
|
+
end = shiftPosition(end, change);
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
};
|
|
44
|
+
let valid = true;
|
|
45
|
+
const map = range => {
|
|
46
|
+
valid &&= !overlapsChange(range);
|
|
47
|
+
return mapRange(range);
|
|
48
|
+
};
|
|
49
|
+
const edit = item.textEdit;
|
|
50
|
+
const mapped = {
|
|
51
|
+
...item,
|
|
52
|
+
...(edit === undefined ? {} : {
|
|
53
|
+
textEdit: 'range' in edit ? {
|
|
54
|
+
...edit,
|
|
55
|
+
range: map(edit.range)
|
|
56
|
+
} : {
|
|
57
|
+
...edit,
|
|
58
|
+
insert: map(edit.insert),
|
|
59
|
+
replace: map(edit.replace)
|
|
60
|
+
}
|
|
61
|
+
}),
|
|
62
|
+
...(item.additionalTextEdits === undefined ? {} : {
|
|
63
|
+
additionalTextEdits: item.additionalTextEdits.map(edit => ({
|
|
64
|
+
...edit,
|
|
65
|
+
range: map(edit.range)
|
|
66
|
+
}))
|
|
67
|
+
})
|
|
68
|
+
};
|
|
69
|
+
return valid ? mapped : null;
|
|
58
70
|
}
|
|
59
71
|
/*
|
|
60
72
|
* The suggestions that open while a word is typed. A single typed identifier character or a trigger
|
|
@@ -62,642 +74,821 @@ function mapCompletionEdits(item, changes, mapRange) {
|
|
|
62
74
|
* is already there, unless the server called it incomplete. Enter inserts, Tab replaces, Escape closes.
|
|
63
75
|
*/
|
|
64
76
|
export class CompletionFeature {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
77
|
+
language;
|
|
78
|
+
timers;
|
|
79
|
+
pool = [];
|
|
80
|
+
incomplete = false;
|
|
81
|
+
ranked = [];
|
|
82
|
+
active = 0;
|
|
83
|
+
/* Whether the person moved in the list; until then the active row is the best match, whatever the typing does to the list. */
|
|
84
|
+
touched = false;
|
|
85
|
+
activeKey = null;
|
|
86
|
+
/* Asked for with the keyboard, which is as good as moving in the list when a commit character is typed. */
|
|
87
|
+
explicit = false;
|
|
88
|
+
detailsOpen = true;
|
|
89
|
+
start = null;
|
|
90
|
+
server = '';
|
|
91
|
+
request = 0;
|
|
92
|
+
controller = null;
|
|
93
|
+
resolving = null;
|
|
94
|
+
accepting = null;
|
|
95
|
+
/* What the list was ranked for last: the caret event and the text event of one keystroke both refilter, and only the first has to rank. */
|
|
96
|
+
rankedFor = null;
|
|
97
|
+
requestTimer;
|
|
98
|
+
resolveTimer;
|
|
99
|
+
textRevision = 0;
|
|
100
|
+
acceptSequence = 0;
|
|
101
|
+
disposed = false;
|
|
102
|
+
resolved = new Map();
|
|
103
|
+
constructor(language, timers = realTimers) {
|
|
104
|
+
this.language = language;
|
|
105
|
+
this.timers = timers;
|
|
106
|
+
const {
|
|
107
|
+
editor
|
|
108
|
+
} = language;
|
|
109
|
+
const offs = [language.onKey(event => this.key(event), KEY_PRIORITY.completion), editor.onTextChange(change => this.edited(change)), editor.onCaret(position => this.caretMoved(position)), editor.onBlur(() => {
|
|
110
|
+
this.acceptSequence++;
|
|
111
|
+
this.accepting?.abort();
|
|
112
|
+
this.close();
|
|
113
|
+
})];
|
|
114
|
+
language.onDispose(() => {
|
|
115
|
+
this.disposed = true;
|
|
116
|
+
this.accepting?.abort();
|
|
117
|
+
for (const off of offs) {
|
|
118
|
+
off();
|
|
119
|
+
}
|
|
120
|
+
this.close();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
get isOpen() {
|
|
124
|
+
return this.language.popups.getState().completion !== null;
|
|
125
|
+
}
|
|
126
|
+
/* Asks for suggestions at the caret now, as Ctrl+Space does. */
|
|
127
|
+
invoke() {
|
|
128
|
+
this.language.pick.close();
|
|
129
|
+
this.explicit = true;
|
|
130
|
+
this.beginRequest({
|
|
131
|
+
triggerKind: 1
|
|
132
|
+
}, 0);
|
|
133
|
+
}
|
|
134
|
+
move(delta) {
|
|
135
|
+
if (this.ranked.length === 0) {
|
|
136
|
+
return;
|
|
113
137
|
}
|
|
114
|
-
|
|
115
|
-
|
|
138
|
+
const last = this.ranked.length - 1;
|
|
139
|
+
// One step past either end goes round, a page does not.
|
|
140
|
+
this.active = Math.abs(delta) === 1 ? (this.active + delta + last + 1) % (last + 1) : Math.max(0, Math.min(last, this.active + delta));
|
|
141
|
+
this.touched = true;
|
|
142
|
+
this.publish();
|
|
143
|
+
this.scheduleResolve();
|
|
144
|
+
}
|
|
145
|
+
/* Picks a row by a click or a key. It never rejects: a failure is told to the person, so a caller can leave the promise alone. */
|
|
146
|
+
async accept(replace, index = this.active, commit) {
|
|
147
|
+
try {
|
|
148
|
+
await this.insert(replace, index, commit);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
const {
|
|
151
|
+
project
|
|
152
|
+
} = this.language;
|
|
153
|
+
project.host.notify?.({
|
|
154
|
+
id: TOAST_ID,
|
|
155
|
+
kind: 'error',
|
|
156
|
+
title: project.i18n.t('editor:language.completion.failed', {
|
|
157
|
+
message: messageOf(error)
|
|
158
|
+
})
|
|
159
|
+
});
|
|
116
160
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
161
|
+
}
|
|
162
|
+
async insert(replace, index, commit) {
|
|
163
|
+
const entry = this.ranked[index];
|
|
164
|
+
if (entry === undefined) {
|
|
165
|
+
return;
|
|
122
166
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
167
|
+
const {
|
|
168
|
+
editor,
|
|
169
|
+
project,
|
|
170
|
+
uri,
|
|
171
|
+
snippets
|
|
172
|
+
} = this.language;
|
|
173
|
+
const acceptSequence = ++this.acceptSequence;
|
|
174
|
+
this.accepting?.abort();
|
|
175
|
+
const accepting = new AbortController();
|
|
176
|
+
this.accepting = accepting;
|
|
177
|
+
const snippetToken = snippets.token;
|
|
178
|
+
const inSnippet = snippets.isActive;
|
|
179
|
+
const exitChanges = [];
|
|
180
|
+
if (snippets.hasPendingTransforms) {
|
|
181
|
+
this.close();
|
|
182
|
+
if (!(await snippets.settleTransforms(() => !this.disposed && acceptSequence === this.acceptSequence && snippets.token === snippetToken, (changes, map) => exitChanges.push({
|
|
183
|
+
changes,
|
|
184
|
+
map
|
|
185
|
+
})))) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
133
188
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
189
|
+
const acceptedSelections = editor.getSelections();
|
|
190
|
+
const revision = this.textRevision;
|
|
191
|
+
const current = () => {
|
|
192
|
+
if (this.disposed || acceptSequence !== this.acceptSequence || revision !== this.textRevision || inSnippet && snippets.token !== snippetToken) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
const selections = editor.getSelections();
|
|
196
|
+
return selections.length === acceptedSelections.length && selections.every((range, index) => comparePositions(range.start, acceptedSelections[index].start) === 0 && comparePositions(range.end, acceptedSelections[index].end) === 0);
|
|
197
|
+
};
|
|
198
|
+
recentChoicesOf(this.language.languageId).record(entry.item);
|
|
199
|
+
let item = this.resolved.get(entry.item) ?? entry.item;
|
|
200
|
+
const needsResolve = !this.resolved.has(entry.item) && project.service.providerOptions('completionItem/resolve', uri) !== undefined;
|
|
201
|
+
this.close();
|
|
202
|
+
if (needsResolve) {
|
|
203
|
+
// Resolving can add the import an item needs; an item that cannot be resolved is still inserted as it is.
|
|
204
|
+
item = await this.resolveNow(entry.item, accepting.signal);
|
|
143
205
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
206
|
+
if (!current()) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
for (const {
|
|
210
|
+
changes,
|
|
211
|
+
map
|
|
212
|
+
} of exitChanges) {
|
|
213
|
+
const mapped = mapCompletionEdits(item, changes, map);
|
|
214
|
+
if (mapped === null) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
item = mapped;
|
|
218
|
+
}
|
|
219
|
+
const caret = editor.getCaret();
|
|
220
|
+
const lineBefore = textBeforeOf(editor, caret);
|
|
221
|
+
const lineAfter = editor.textInRange({
|
|
222
|
+
start: caret,
|
|
223
|
+
end: {
|
|
224
|
+
line: caret.line,
|
|
225
|
+
character: Number.MAX_SAFE_INTEGER
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
const carets = editor.getSelections();
|
|
229
|
+
const source = item.textEdit?.newText ?? item.insertText ?? item.label;
|
|
230
|
+
if (source.length * carets.length > MAX_COMPLETION_CHARACTERS) {
|
|
231
|
+
this.refuseLargeCompletion();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
let characters = 0;
|
|
235
|
+
let ranges = 0;
|
|
236
|
+
const now = new Date();
|
|
237
|
+
const template = item.insertTextFormat === 2 ? compileSnippet(item.textEdit?.newText ?? item.insertText ?? item.label) : undefined;
|
|
238
|
+
const finishes = new Map();
|
|
239
|
+
const renderAt = (position, selection) => {
|
|
240
|
+
const line = editor.textInRange({
|
|
241
|
+
start: {
|
|
242
|
+
line: position.line,
|
|
243
|
+
character: 0
|
|
244
|
+
},
|
|
245
|
+
end: {
|
|
246
|
+
line: position.line,
|
|
247
|
+
character: Number.MAX_SAFE_INTEGER
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
const word = wordRangeAt(line, position);
|
|
251
|
+
const resolver = createSnippetResolver({
|
|
252
|
+
uri,
|
|
253
|
+
languageId: this.language.languageId,
|
|
254
|
+
position,
|
|
255
|
+
selection,
|
|
256
|
+
selectedText: editor.textInRange(selection),
|
|
257
|
+
currentLine: line,
|
|
258
|
+
currentWord: word === null ? '' : editor.textInRange(word),
|
|
259
|
+
cursorIndex: Math.max(0, carets.findIndex(range => comparePositions(range.start, selection.start) === 0 && comparePositions(range.end, selection.end) === 0)),
|
|
260
|
+
cursorCount: carets.length,
|
|
261
|
+
folder: project.host.folder,
|
|
262
|
+
path: fileUriToPath(uri) ?? project.host.pathOfUri?.(uri) ?? undefined
|
|
263
|
+
}, project.host.resolveSnippetVariable?.bind(project.host), {
|
|
264
|
+
now
|
|
265
|
+
});
|
|
266
|
+
const atItem = position === caret || item.textEdit === undefined ? item : {
|
|
267
|
+
...item,
|
|
268
|
+
textEdit: undefined,
|
|
269
|
+
insertText: item.textEdit?.newText ?? item.insertText
|
|
270
|
+
};
|
|
271
|
+
const editStart = editRangeOf(atItem, replace)?.start;
|
|
272
|
+
// A commit character follows the name, so accepting must not also add a call.
|
|
273
|
+
const finish = variableTransforms => {
|
|
274
|
+
const insertion = insertionOf(atItem, position, line.slice(0, position.character), replace, commit === undefined ? line.slice(position.character) : '(', {
|
|
275
|
+
template,
|
|
276
|
+
variableTransforms,
|
|
277
|
+
resolve: resolver,
|
|
278
|
+
indentationBefore: editStart === undefined ? undefined : editor.textInRange({
|
|
279
|
+
start: {
|
|
280
|
+
line: editStart.line,
|
|
281
|
+
character: 0
|
|
282
|
+
},
|
|
283
|
+
end: editStart
|
|
284
|
+
})
|
|
285
|
+
});
|
|
286
|
+
return commit === undefined ? insertion : {
|
|
287
|
+
...insertion,
|
|
288
|
+
text: insertion.text + commit
|
|
173
289
|
};
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
currentLine: line,
|
|
216
|
-
currentWord: word === null ? '' : editor.textInRange(word),
|
|
217
|
-
cursorIndex: Math.max(0, carets.findIndex((range) => comparePositions(range.start, selection.start) === 0 && comparePositions(range.end, selection.end) === 0)),
|
|
218
|
-
cursorCount: carets.length,
|
|
219
|
-
folder: project.host.folder,
|
|
220
|
-
path: fileUriToPath(uri) ?? project.host.pathOfUri?.(uri) ?? undefined
|
|
221
|
-
}, project.host.resolveSnippetVariable?.bind(project.host), { now });
|
|
222
|
-
const atItem = position === caret || item.textEdit === undefined
|
|
223
|
-
? item
|
|
224
|
-
: { ...item, textEdit: undefined, insertText: item.textEdit?.newText ?? item.insertText };
|
|
225
|
-
const editStart = editRangeOf(atItem, replace)?.start;
|
|
226
|
-
// A commit character follows the name, so accepting must not also add a call.
|
|
227
|
-
const finish = (variableTransforms) => {
|
|
228
|
-
const insertion = insertionOf(atItem, position, line.slice(0, position.character), replace, commit === undefined ? line.slice(position.character) : '(', {
|
|
229
|
-
template,
|
|
230
|
-
variableTransforms,
|
|
231
|
-
resolve: resolver,
|
|
232
|
-
indentationBefore: editStart === undefined ? undefined : editor.textInRange({ start: { line: editStart.line, character: 0 }, end: editStart })
|
|
233
|
-
});
|
|
234
|
-
return commit === undefined ? insertion : { ...insertion, text: insertion.text + commit };
|
|
235
|
-
};
|
|
236
|
-
const rendered = finish();
|
|
237
|
-
finishes.set(rendered, finish);
|
|
238
|
-
characters += rendered.text.length;
|
|
239
|
-
ranges +=
|
|
240
|
-
(rendered.variableTransforms?.length ?? 0) +
|
|
241
|
-
(rendered.fields?.reduce((count, field) => count + field.ranges.length + (field.linked?.length ?? 0), 1) ?? rendered.stops.length);
|
|
242
|
-
if (rendered.limited || characters > MAX_COMPLETION_CHARACTERS || ranges > MAX_COMPLETION_RANGES) {
|
|
243
|
-
throw new CompletionLimitError();
|
|
244
|
-
}
|
|
245
|
-
return rendered;
|
|
290
|
+
};
|
|
291
|
+
const rendered = finish();
|
|
292
|
+
finishes.set(rendered, finish);
|
|
293
|
+
characters += rendered.text.length;
|
|
294
|
+
ranges += (rendered.variableTransforms?.length ?? 0) + (rendered.fields?.reduce((count, field) => count + field.ranges.length + (field.linked?.length ?? 0), 1) ?? rendered.stops.length);
|
|
295
|
+
if (rendered.limited || characters > MAX_COMPLETION_CHARACTERS || ranges > MAX_COMPLETION_RANGES) {
|
|
296
|
+
throw new CompletionLimitError();
|
|
297
|
+
}
|
|
298
|
+
return rendered;
|
|
299
|
+
};
|
|
300
|
+
try {
|
|
301
|
+
const planned = renderAt(caret, editor.getSelection());
|
|
302
|
+
const {
|
|
303
|
+
before,
|
|
304
|
+
rest
|
|
305
|
+
} = this.surroundings(planned, caret, lineAfter);
|
|
306
|
+
const plan = commit === undefined ? planCall({
|
|
307
|
+
item,
|
|
308
|
+
languageId: this.language.languageId,
|
|
309
|
+
insertion: planned,
|
|
310
|
+
before,
|
|
311
|
+
rest,
|
|
312
|
+
replace
|
|
313
|
+
}) : {
|
|
314
|
+
action: 'none'
|
|
315
|
+
};
|
|
316
|
+
let main = plan.action === 'add' ? withParentheses(planned, plan.inside) : planned;
|
|
317
|
+
const extras = (item.additionalTextEdits ?? []).map(edit => ({
|
|
318
|
+
range: edit.range,
|
|
319
|
+
text: edit.newText
|
|
320
|
+
}));
|
|
321
|
+
const instances = new Map();
|
|
322
|
+
const continuing = inSnippet && snippets.isActive && snippetToken === snippets.token;
|
|
323
|
+
const mirrored = mirroredInsertions(carets, editor.getSelection(), caret, range => editor.textInRange(range), main, continuing).map(insertion => {
|
|
324
|
+
const selection = carets.find(range => range.start.line === insertion.range.start.line && range.start.character >= insertion.range.start.character && range.start.character <= insertion.range.end.character);
|
|
325
|
+
const position = selection[continuing ? 'end' : 'start'];
|
|
326
|
+
const own = renderAt(position, selection);
|
|
327
|
+
const rendered = plan.action === 'add' ? withParentheses(own, plan.inside) : own;
|
|
328
|
+
const edit = {
|
|
329
|
+
...insertion,
|
|
330
|
+
text: rendered.text
|
|
246
331
|
};
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if (commit === undefined) {
|
|
321
|
-
this.language.snippets.beginInstances([...mirrored, primary].map((insertion, index) => {
|
|
322
|
-
const rendered = insertion === primary ? main : instances.get(insertion);
|
|
323
|
-
return { start: starts[index], text: rendered.text, stops: rendered.stops, fields: rendered.fields };
|
|
324
|
-
}), { nested: continuing && snippets.isActive });
|
|
325
|
-
if (plan.action === 'add' && plan.inside && mirrored.length > 0 && main.fields === undefined) {
|
|
326
|
-
this.placeMirroredCarets(carets, mirrored, landed);
|
|
327
|
-
}
|
|
328
|
-
if (plan.action === 'enter') {
|
|
329
|
-
const end = editor.getCaret();
|
|
330
|
-
editor.setCaret({ line: end.line, character: end.character + plan.shift });
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
334
|
-
if (this.triggerCharacters().includes(commit)) {
|
|
335
|
-
this.beginRequest({ triggerKind: 2, triggerCharacter: commit }, 0);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
this.afterInsert(item, commit, lineBefore);
|
|
339
|
-
editor.focus();
|
|
340
|
-
}
|
|
341
|
-
catch (error) {
|
|
342
|
-
if (!current()) {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
if (error instanceof SnippetTransformQueueError) {
|
|
346
|
-
project.host.notify?.({ id: TOAST_ID, kind: 'error', title: project.i18n.t('editor:language.completion.transformBusy') });
|
|
347
|
-
}
|
|
348
|
-
else if (error instanceof CompletionLimitError || (error instanceof SnippetTransformError && error.kind === 'range')) {
|
|
349
|
-
this.refuseLargeCompletion();
|
|
350
|
-
}
|
|
351
|
-
else if (error instanceof SnippetTransformError) {
|
|
352
|
-
project.host.notify?.({ id: TOAST_ID, kind: 'error', title: project.i18n.t('editor:language.completion.transformFailed') });
|
|
353
|
-
}
|
|
354
|
-
else {
|
|
355
|
-
throw error;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
refuseLargeCompletion() {
|
|
360
|
-
this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title: this.language.project.i18n.t('editor:language.completion.tooLarge') });
|
|
361
|
-
}
|
|
362
|
-
/* The carets that got the same `()` go inside it like the primary one, which the snippet already placed. */
|
|
363
|
-
placeMirroredCarets(before, mirrored, landed) {
|
|
364
|
-
const { editor } = this.language;
|
|
365
|
-
const primary = before.length - 1;
|
|
366
|
-
const selections = landed.map((range, index) => {
|
|
367
|
-
const caret = before[index].start;
|
|
368
|
-
const got = mirrored.some((edit) => edit.range.start.line === caret.line && edit.range.start.character <= caret.character && caret.character <= edit.range.end.character);
|
|
369
|
-
if (index === primary || !got) {
|
|
370
|
-
return range;
|
|
371
|
-
}
|
|
372
|
-
const stop = { line: range.end.line, character: range.end.character - 1 };
|
|
373
|
-
return { start: stop, end: stop };
|
|
332
|
+
const instance = {
|
|
333
|
+
...rendered,
|
|
334
|
+
range: insertion.range
|
|
335
|
+
};
|
|
336
|
+
instances.set(edit, instance);
|
|
337
|
+
const finish = finishes.get(own);
|
|
338
|
+
if (finish !== undefined) {
|
|
339
|
+
finishes.set(instance, values => ({
|
|
340
|
+
...finish(values),
|
|
341
|
+
range: insertion.range
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
344
|
+
return edit;
|
|
345
|
+
});
|
|
346
|
+
const drafts = [...mirrored.map(edit => instances.get(edit)), main];
|
|
347
|
+
const jobs = [];
|
|
348
|
+
const keys = new Map();
|
|
349
|
+
const slots = drafts.map(draft => (draft.variableTransforms ?? []).map(job => {
|
|
350
|
+
const key = JSON.stringify(job);
|
|
351
|
+
let slot = keys.get(key);
|
|
352
|
+
if (slot === undefined) {
|
|
353
|
+
slot = jobs.length;
|
|
354
|
+
keys.set(key, slot);
|
|
355
|
+
jobs.push(job);
|
|
356
|
+
}
|
|
357
|
+
return slot;
|
|
358
|
+
}));
|
|
359
|
+
if (jobs.length > MAX_TRANSFORM_JOBS || jobs.reduce((sum, job) => sum + job.value.length, 0) > MAX_TRANSFORM_WORK) {
|
|
360
|
+
throw new CompletionLimitError();
|
|
361
|
+
}
|
|
362
|
+
if (jobs.length > 0 && project.host.snippetTransformWorker !== undefined) {
|
|
363
|
+
const values = await snippets.transformVariables(jobs, current);
|
|
364
|
+
if (!current()) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (values !== null) {
|
|
368
|
+
const transformed = drafts.map((draft, index) => slots[index].length === 0 ? draft : finishes.get(draft)(slots[index].map(slot => values[slot])));
|
|
369
|
+
if (transformed.some(draft => draft.limited) || transformed.reduce((sum, draft) => sum + draft.text.length, 0) > MAX_COMPLETION_CHARACTERS) {
|
|
370
|
+
throw new CompletionLimitError();
|
|
371
|
+
}
|
|
372
|
+
main = transformed.at(-1);
|
|
373
|
+
mirrored.forEach((edit, index) => {
|
|
374
|
+
edit.text = transformed[index].text;
|
|
375
|
+
instances.set(edit, transformed[index]);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const primary = {
|
|
380
|
+
range: main.range,
|
|
381
|
+
text: main.text
|
|
382
|
+
};
|
|
383
|
+
const changes = [primary, ...mirrored, ...extras];
|
|
384
|
+
if (changes.reduce((total, edit) => total + edit.text.length, 0) > MAX_COMPLETION_CHARACTERS) {
|
|
385
|
+
throw new CompletionLimitError();
|
|
386
|
+
}
|
|
387
|
+
// Imports and earlier carets move each insertion independently; the primary instance stays last.
|
|
388
|
+
const starts = [...mirrored, primary].map(insertion => changes.filter(change => change !== insertion && comparePositions(change.range.end, insertion.range.start) <= 0).sort((left, right) => comparePositions(right.range.start, left.range.start)).reduce((place, change) => shiftPosition(place, change), insertion.range.start));
|
|
389
|
+
const applied = inSnippet ? snippets.applyEdits(changes, snippetToken, extras) : editor.applyEdits(changes);
|
|
390
|
+
if (!applied) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const landed = editor.getSelections();
|
|
394
|
+
if (commit === undefined) {
|
|
395
|
+
this.language.snippets.beginInstances([...mirrored, primary].map((insertion, index) => {
|
|
396
|
+
const rendered = insertion === primary ? main : instances.get(insertion);
|
|
397
|
+
return {
|
|
398
|
+
start: starts[index],
|
|
399
|
+
text: rendered.text,
|
|
400
|
+
stops: rendered.stops,
|
|
401
|
+
fields: rendered.fields
|
|
402
|
+
};
|
|
403
|
+
}), {
|
|
404
|
+
nested: continuing && snippets.isActive
|
|
374
405
|
});
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
406
|
+
if (plan.action === 'add' && plan.inside && mirrored.length > 0 && main.fields === undefined) {
|
|
407
|
+
this.placeMirroredCarets(carets, mirrored, landed);
|
|
408
|
+
}
|
|
409
|
+
if (plan.action === 'enter') {
|
|
410
|
+
const end = editor.getCaret();
|
|
411
|
+
editor.setCaret({
|
|
412
|
+
line: end.line,
|
|
413
|
+
character: end.character + plan.shift
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
if (this.triggerCharacters().includes(commit)) {
|
|
418
|
+
this.beginRequest({
|
|
419
|
+
triggerKind: 2,
|
|
420
|
+
triggerCharacter: commit
|
|
421
|
+
}, 0);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
this.afterInsert(item, commit, lineBefore);
|
|
425
|
+
editor.focus();
|
|
426
|
+
} catch (error) {
|
|
427
|
+
if (!current()) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (error instanceof SnippetTransformQueueError) {
|
|
431
|
+
project.host.notify?.({
|
|
432
|
+
id: TOAST_ID,
|
|
433
|
+
kind: 'error',
|
|
434
|
+
title: project.i18n.t('editor:language.completion.transformBusy')
|
|
435
|
+
});
|
|
436
|
+
} else if (error instanceof CompletionLimitError || error instanceof SnippetTransformError && error.kind === 'range') {
|
|
437
|
+
this.refuseLargeCompletion();
|
|
438
|
+
} else if (error instanceof SnippetTransformError) {
|
|
439
|
+
project.host.notify?.({
|
|
440
|
+
id: TOAST_ID,
|
|
441
|
+
kind: 'error',
|
|
442
|
+
title: project.i18n.t('editor:language.completion.transformFailed')
|
|
443
|
+
});
|
|
444
|
+
} else {
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
404
447
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
448
|
+
}
|
|
449
|
+
refuseLargeCompletion() {
|
|
450
|
+
this.language.project.host.notify?.({
|
|
451
|
+
id: TOAST_ID,
|
|
452
|
+
kind: 'error',
|
|
453
|
+
title: this.language.project.i18n.t('editor:language.completion.tooLarge')
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
/* The carets that got the same `()` go inside it like the primary one, which the snippet already placed. */
|
|
457
|
+
placeMirroredCarets(before, mirrored, landed) {
|
|
458
|
+
const {
|
|
459
|
+
editor
|
|
460
|
+
} = this.language;
|
|
461
|
+
const primary = before.length - 1;
|
|
462
|
+
const selections = landed.map((range, index) => {
|
|
463
|
+
const caret = before[index].start;
|
|
464
|
+
const got = mirrored.some(edit => edit.range.start.line === caret.line && edit.range.start.character <= caret.character && caret.character <= edit.range.end.character);
|
|
465
|
+
if (index === primary || !got) {
|
|
466
|
+
return range;
|
|
467
|
+
}
|
|
468
|
+
const stop = {
|
|
469
|
+
line: range.end.line,
|
|
470
|
+
character: range.end.character - 1
|
|
471
|
+
};
|
|
472
|
+
return {
|
|
473
|
+
start: stop,
|
|
474
|
+
end: stop
|
|
475
|
+
};
|
|
476
|
+
});
|
|
477
|
+
selections[primary] = editor.getSelection();
|
|
478
|
+
editor.setSelections(selections);
|
|
479
|
+
}
|
|
480
|
+
/* The lines above the insertion down to where it starts, and the rest of the line past what it replaces. */
|
|
481
|
+
surroundings(insertion, caret, lineAfter) {
|
|
482
|
+
const {
|
|
483
|
+
editor
|
|
484
|
+
} = this.language;
|
|
485
|
+
const {
|
|
486
|
+
start,
|
|
487
|
+
end
|
|
488
|
+
} = insertion.range;
|
|
489
|
+
const before = editor.textInRange({
|
|
490
|
+
start: {
|
|
491
|
+
line: Math.max(0, start.line - LINES_BACK),
|
|
492
|
+
character: 0
|
|
493
|
+
},
|
|
494
|
+
end: start
|
|
495
|
+
});
|
|
496
|
+
const rest = end.line === caret.line ? lineAfter.slice(Math.max(0, end.character - caret.character)) : '';
|
|
497
|
+
return {
|
|
498
|
+
before,
|
|
499
|
+
rest
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
/* Parameter info for a call that was opened (or that the server asks for), then the item's own command. */
|
|
503
|
+
afterInsert(item, commit, lineBefore) {
|
|
504
|
+
const {
|
|
505
|
+
editor,
|
|
506
|
+
project,
|
|
507
|
+
uri,
|
|
508
|
+
signature
|
|
509
|
+
} = this.language;
|
|
510
|
+
const {
|
|
511
|
+
start
|
|
512
|
+
} = editor.getSelection();
|
|
513
|
+
const insideCall = start.character > 0 && editor.textInRange({
|
|
514
|
+
start: {
|
|
515
|
+
line: start.line,
|
|
516
|
+
character: start.character - 1
|
|
517
|
+
},
|
|
518
|
+
end: start
|
|
519
|
+
}) === '(';
|
|
520
|
+
if (commit === '(' || insideCall && isCallItem(item, lineBefore)) {
|
|
521
|
+
signature.trigger();
|
|
427
522
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
this.timers.clear(this.resolveTimer);
|
|
434
|
-
this.pool = [];
|
|
435
|
-
this.ranked = [];
|
|
436
|
-
this.rankedFor = null;
|
|
437
|
-
this.touched = false;
|
|
438
|
-
this.explicit = false;
|
|
439
|
-
this.activeKey = null;
|
|
440
|
-
this.start = null;
|
|
441
|
-
this.resolved.clear();
|
|
442
|
-
if (this.language.popups.getState().completion !== null) {
|
|
443
|
-
this.language.popups.setState({ completion: null });
|
|
444
|
-
}
|
|
523
|
+
const {
|
|
524
|
+
command
|
|
525
|
+
} = item;
|
|
526
|
+
if (command === undefined) {
|
|
527
|
+
return;
|
|
445
528
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
this.publish();
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
this.invoke();
|
|
454
|
-
}
|
|
455
|
-
return true;
|
|
456
|
-
}
|
|
457
|
-
if (!this.isOpen || event.ctrlKey || event.metaKey || event.altKey) {
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
460
|
-
if (this.commits(event)) {
|
|
461
|
-
void this.accept(false, this.active, event.key);
|
|
462
|
-
return true;
|
|
463
|
-
}
|
|
464
|
-
switch (event.key) {
|
|
465
|
-
case 'ArrowDown':
|
|
466
|
-
this.move(1);
|
|
467
|
-
return true;
|
|
468
|
-
case 'ArrowUp':
|
|
469
|
-
this.move(-1);
|
|
470
|
-
return true;
|
|
471
|
-
case 'PageDown':
|
|
472
|
-
this.move(PAGE);
|
|
473
|
-
return true;
|
|
474
|
-
case 'PageUp':
|
|
475
|
-
this.move(-PAGE);
|
|
476
|
-
return true;
|
|
477
|
-
case 'Enter':
|
|
478
|
-
case 'Tab':
|
|
479
|
-
if (event.shiftKey) {
|
|
480
|
-
return false;
|
|
481
|
-
}
|
|
482
|
-
void this.accept(event.key === 'Tab');
|
|
483
|
-
return true;
|
|
484
|
-
case 'Escape':
|
|
485
|
-
this.close();
|
|
486
|
-
return true;
|
|
487
|
-
default:
|
|
488
|
-
return false;
|
|
489
|
-
}
|
|
529
|
+
if (command.command === PARAMETER_HINTS_COMMAND) {
|
|
530
|
+
signature.trigger();
|
|
531
|
+
} else {
|
|
532
|
+
project.service.executeCommand(uri, command).catch(() => undefined);
|
|
490
533
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
534
|
+
}
|
|
535
|
+
/*
|
|
536
|
+
* Resolving gives the item its documentation and the import it needs, but only for the text it was
|
|
537
|
+
* made for. When the person typed on since, the list is asked for again at the new text and the same
|
|
538
|
+
* suggestion is resolved in that one; an item that cannot be resolved is still inserted as it is.
|
|
539
|
+
*/
|
|
540
|
+
async resolveNow(item, signal) {
|
|
541
|
+
const {
|
|
542
|
+
editor,
|
|
543
|
+
project,
|
|
544
|
+
uri
|
|
545
|
+
} = this.language;
|
|
546
|
+
try {
|
|
547
|
+
return {
|
|
548
|
+
...item,
|
|
549
|
+
...(await project.service.resolveCompletion(uri, item, {
|
|
550
|
+
signal
|
|
551
|
+
}))
|
|
552
|
+
};
|
|
553
|
+
} catch (error) {
|
|
554
|
+
if (!(error instanceof StaleResultError)) {
|
|
555
|
+
return item;
|
|
556
|
+
}
|
|
497
557
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
else if (isEmptyRange(range) && opensCompletion(textBeforeOf(this.language.editor, range.start), text)) {
|
|
513
|
-
this.close();
|
|
514
|
-
this.beginRequest({ triggerKind: 1 }, START_DELAY_MS);
|
|
515
|
-
}
|
|
516
|
-
else if (this.isOpen) {
|
|
517
|
-
if (text === '' || typedIdentifier) {
|
|
518
|
-
this.refilter();
|
|
519
|
-
}
|
|
520
|
-
else {
|
|
521
|
-
this.close();
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
else if (text.length === 1 && typedIdentifier && isEmptyRange(range)) {
|
|
525
|
-
this.beginRequest({ triggerKind: 1 }, START_DELAY_MS);
|
|
526
|
-
}
|
|
527
|
-
else if (text !== '') {
|
|
528
|
-
// A request the last letter started, still waiting or out, would open the list after a character that ends the word.
|
|
529
|
-
this.close();
|
|
530
|
-
}
|
|
558
|
+
try {
|
|
559
|
+
const fresh = itemsOf(await project.service.completion(uri, editor.getCaret(), {
|
|
560
|
+
triggerKind: 1
|
|
561
|
+
}, {
|
|
562
|
+
signal
|
|
563
|
+
})).items.find(candidate => candidate.label === item.label && candidate.kind === item.kind);
|
|
564
|
+
return fresh === undefined ? item : {
|
|
565
|
+
...fresh,
|
|
566
|
+
...(await project.service.resolveCompletion(uri, fresh, {
|
|
567
|
+
signal
|
|
568
|
+
}).catch(() => fresh))
|
|
569
|
+
};
|
|
570
|
+
} catch {
|
|
571
|
+
return item;
|
|
531
572
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
573
|
+
}
|
|
574
|
+
close() {
|
|
575
|
+
this.request++;
|
|
576
|
+
this.controller?.abort();
|
|
577
|
+
this.resolving?.abort();
|
|
578
|
+
this.timers.clear(this.requestTimer);
|
|
579
|
+
this.timers.clear(this.resolveTimer);
|
|
580
|
+
this.pool = [];
|
|
581
|
+
this.ranked = [];
|
|
582
|
+
this.rankedFor = null;
|
|
583
|
+
this.touched = false;
|
|
584
|
+
this.explicit = false;
|
|
585
|
+
this.activeKey = null;
|
|
586
|
+
this.start = null;
|
|
587
|
+
this.resolved.clear();
|
|
588
|
+
if (this.language.popups.getState().completion !== null) {
|
|
589
|
+
this.language.popups.setState({
|
|
590
|
+
completion: null
|
|
591
|
+
});
|
|
542
592
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
593
|
+
}
|
|
594
|
+
key(event) {
|
|
595
|
+
if (isShortcut(this.language.shortcuts.triggerCompletion, event)) {
|
|
596
|
+
if (this.isOpen) {
|
|
597
|
+
this.detailsOpen = !this.detailsOpen;
|
|
598
|
+
this.publish();
|
|
599
|
+
} else {
|
|
600
|
+
this.invoke();
|
|
601
|
+
}
|
|
602
|
+
return true;
|
|
546
603
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
this.
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
604
|
+
if (!this.isOpen || event.ctrlKey || event.metaKey || event.altKey) {
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
if (this.commits(event)) {
|
|
608
|
+
void this.accept(false, this.active, event.key);
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
switch (event.key) {
|
|
612
|
+
case 'ArrowDown':
|
|
613
|
+
this.move(1);
|
|
614
|
+
return true;
|
|
615
|
+
case 'ArrowUp':
|
|
616
|
+
this.move(-1);
|
|
617
|
+
return true;
|
|
618
|
+
case 'PageDown':
|
|
619
|
+
this.move(PAGE);
|
|
620
|
+
return true;
|
|
621
|
+
case 'PageUp':
|
|
622
|
+
this.move(-PAGE);
|
|
623
|
+
return true;
|
|
624
|
+
case 'Enter':
|
|
625
|
+
case 'Tab':
|
|
626
|
+
if (event.shiftKey) {
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
void this.accept(event.key === 'Tab');
|
|
630
|
+
return true;
|
|
631
|
+
case 'Escape':
|
|
632
|
+
this.close();
|
|
633
|
+
return true;
|
|
634
|
+
default:
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/* A character the active item commits on, typed once the person has moved in the list or asked for it, which is when Enter would not be the way they chose. */
|
|
639
|
+
commits(event) {
|
|
640
|
+
if (!this.touched && !this.explicit || event.key.length !== 1 || event.isComposing) {
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
return this.ranked[this.active]?.item.commitCharacters?.includes(event.key) === true;
|
|
644
|
+
}
|
|
645
|
+
edited(change) {
|
|
646
|
+
this.textRevision++;
|
|
647
|
+
if (change.source !== 'input' || change.changes.length !== 1) {
|
|
648
|
+
this.close();
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
const {
|
|
652
|
+
range,
|
|
653
|
+
text
|
|
654
|
+
} = change.changes[0];
|
|
655
|
+
const triggers = this.triggerCharacters();
|
|
656
|
+
const last = Array.from(text).at(-1) ?? '';
|
|
657
|
+
const typedIdentifier = text !== '' && Array.from(text).every(isIdentifierCharacter);
|
|
658
|
+
if (text.length === 1 && triggers.includes(last)) {
|
|
659
|
+
this.close();
|
|
660
|
+
this.beginRequest({
|
|
661
|
+
triggerKind: 2,
|
|
662
|
+
triggerCharacter: last
|
|
663
|
+
}, 0);
|
|
664
|
+
} else if (isEmptyRange(range) && opensCompletion(textBeforeOf(this.language.editor, range.start), text)) {
|
|
665
|
+
this.close();
|
|
666
|
+
this.beginRequest({
|
|
667
|
+
triggerKind: 1
|
|
668
|
+
}, START_DELAY_MS);
|
|
669
|
+
} else if (this.isOpen) {
|
|
670
|
+
if (text === '' || typedIdentifier) {
|
|
594
671
|
this.refilter();
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
672
|
+
} else {
|
|
673
|
+
this.close();
|
|
674
|
+
}
|
|
675
|
+
} else if (text.length === 1 && typedIdentifier && isEmptyRange(range)) {
|
|
676
|
+
this.beginRequest({
|
|
677
|
+
triggerKind: 1
|
|
678
|
+
}, START_DELAY_MS);
|
|
679
|
+
} else if (text !== '') {
|
|
680
|
+
// A request the last letter started, still waiting or out, would open the list after a character that ends the word.
|
|
681
|
+
this.close();
|
|
602
682
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
this.close();
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
const lineBefore = textBeforeOf(editor, caret);
|
|
611
|
-
const typedKey = `${caret.line}:${caret.character}:${lineBefore}`;
|
|
612
|
-
if (this.rankedFor?.pool === this.pool && this.rankedFor.key === typedKey) {
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
this.rankedFor = { pool: this.pool, key: typedKey };
|
|
616
|
-
// Most items of a list replace the same range, so the text under it is read once and not once per item.
|
|
617
|
-
const typed = new Map();
|
|
618
|
-
const textBetween = (range) => {
|
|
619
|
-
const key = `${range.start.line}:${range.start.character}:${range.end.line}:${range.end.character}`;
|
|
620
|
-
let text = typed.get(key);
|
|
621
|
-
if (text === undefined) {
|
|
622
|
-
text = editor.textInRange(range);
|
|
623
|
-
typed.set(key, text);
|
|
624
|
-
}
|
|
625
|
-
return text;
|
|
626
|
-
};
|
|
627
|
-
const before = this.ranked[this.active]?.item;
|
|
628
|
-
const recent = recentChoicesOf(this.language.languageId);
|
|
629
|
-
this.ranked = rankCompletions(this.pool, (item) => prefixFor(item, caret, lineBefore, textBetween), (item) => recent.recency(item));
|
|
630
|
-
if (this.ranked.length === 0) {
|
|
631
|
-
if (this.incomplete && this.pool.length > 0) {
|
|
632
|
-
this.beginRequest({ triggerKind: 3 }, START_DELAY_MS);
|
|
633
|
-
}
|
|
634
|
-
this.close();
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
const key = this.activeKey ?? (this.touched && before !== undefined ? RecentChoices.keyOf(before) : null);
|
|
638
|
-
this.activeKey = null;
|
|
639
|
-
const kept = key === null ? -1 : this.ranked.findIndex((entry) => RecentChoices.keyOf(entry.item) === key);
|
|
640
|
-
this.active = kept >= 0 ? kept : 0;
|
|
641
|
-
this.touched = this.touched && kept >= 0;
|
|
642
|
-
this.publish();
|
|
643
|
-
this.scheduleResolve();
|
|
644
|
-
if (this.incomplete) {
|
|
645
|
-
this.beginRequest({ triggerKind: 3 }, START_DELAY_MS);
|
|
646
|
-
}
|
|
683
|
+
}
|
|
684
|
+
caretMoved(position) {
|
|
685
|
+
if (this.start === null) {
|
|
686
|
+
return;
|
|
647
687
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
const entry = this.ranked[this.active];
|
|
653
|
-
if (entry === undefined || this.resolved.has(entry.item) || project.service.providerOptions('completionItem/resolve', uri) === undefined) {
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
const request = this.request;
|
|
657
|
-
this.resolveTimer = this.timers.set(() => {
|
|
658
|
-
const controller = new AbortController();
|
|
659
|
-
this.resolving = controller;
|
|
660
|
-
void project.service
|
|
661
|
-
.resolveCompletion(uri, entry.item, { signal: controller.signal })
|
|
662
|
-
.then((item) => {
|
|
663
|
-
this.resolved.set(entry.item, { ...entry.item, ...item });
|
|
664
|
-
if (this.ranked[this.active]?.item === entry.item) {
|
|
665
|
-
this.publish();
|
|
666
|
-
}
|
|
667
|
-
})
|
|
668
|
-
.catch((error) => {
|
|
669
|
-
// A server only resolves an item for the text it was made for, so only an item that is wanted and stale costs a new list.
|
|
670
|
-
if (error instanceof StaleResultError && !controller.signal.aborted && request === this.request && this.start !== null) {
|
|
671
|
-
void this.ask({ triggerKind: 1 }, request, true);
|
|
672
|
-
}
|
|
673
|
-
});
|
|
674
|
-
}, RESOLVE_DELAY_MS);
|
|
675
|
-
}
|
|
676
|
-
publish() {
|
|
677
|
-
const entry = this.ranked[this.active];
|
|
678
|
-
if (entry === undefined || this.start === null) {
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
const item = this.resolved.get(entry.item) ?? entry.item;
|
|
682
|
-
const descriptions = qualifiersOf(this.ranked.map((ranked) => ranked.item));
|
|
683
|
-
const rows = this.ranked.map(({ item: row, prefix }, index) => ({
|
|
684
|
-
label: row.label,
|
|
685
|
-
kind: row.kind,
|
|
686
|
-
detail: row.labelDetails?.detail ?? '',
|
|
687
|
-
description: descriptions[index],
|
|
688
|
-
matches: matchedCharacters(row.label, prefix),
|
|
689
|
-
deprecated: row.deprecated === true || row.tags?.includes(1) === true
|
|
690
|
-
}));
|
|
691
|
-
const highlightLanguage = shikiLanguageOf(this.language.languageId);
|
|
692
|
-
const view = {
|
|
693
|
-
anchor: this.start,
|
|
694
|
-
rows,
|
|
695
|
-
active: this.active,
|
|
696
|
-
detailsOpen: this.detailsOpen,
|
|
697
|
-
docs: completionDocsOf(item, highlightLanguage),
|
|
698
|
-
server: this.server,
|
|
699
|
-
highlightLanguage
|
|
700
|
-
};
|
|
701
|
-
this.language.popups.setState({ completion: view });
|
|
688
|
+
if (position.line !== this.start.line || position.character < this.start.character) {
|
|
689
|
+
this.close();
|
|
690
|
+
} else {
|
|
691
|
+
this.refilter();
|
|
702
692
|
}
|
|
703
|
-
}
|
|
693
|
+
}
|
|
694
|
+
triggerCharacters() {
|
|
695
|
+
const {
|
|
696
|
+
project,
|
|
697
|
+
uri
|
|
698
|
+
} = this.language;
|
|
699
|
+
return project.service.providerOptions(METHOD, uri)?.triggerCharacters ?? [];
|
|
700
|
+
}
|
|
701
|
+
beginRequest(context, delay) {
|
|
702
|
+
const {
|
|
703
|
+
project,
|
|
704
|
+
uri
|
|
705
|
+
} = this.language;
|
|
706
|
+
if (!project.service.supports(METHOD, uri)) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
this.timers.clear(this.requestTimer);
|
|
710
|
+
const request = ++this.request;
|
|
711
|
+
this.requestTimer = this.timers.set(() => void this.ask(context, request), delay);
|
|
712
|
+
}
|
|
713
|
+
async ask(context, request, refresh = false) {
|
|
714
|
+
const {
|
|
715
|
+
editor,
|
|
716
|
+
project,
|
|
717
|
+
uri
|
|
718
|
+
} = this.language;
|
|
719
|
+
const position = editor.getCaret();
|
|
720
|
+
this.controller?.abort();
|
|
721
|
+
const controller = new AbortController();
|
|
722
|
+
this.controller = controller;
|
|
723
|
+
let normalized;
|
|
724
|
+
try {
|
|
725
|
+
const result = await project.service.completion(uri, position, context, {
|
|
726
|
+
signal: controller.signal
|
|
727
|
+
});
|
|
728
|
+
if (request !== this.request || this.controller !== controller || controller.signal.aborted) {
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
normalized = itemsOf(result);
|
|
732
|
+
} catch {
|
|
733
|
+
if (request === this.request && this.controller === controller && !controller.signal.aborted) {
|
|
734
|
+
this.close();
|
|
735
|
+
}
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const {
|
|
739
|
+
items,
|
|
740
|
+
incomplete
|
|
741
|
+
} = normalized;
|
|
742
|
+
this.pool = items;
|
|
743
|
+
this.incomplete = incomplete;
|
|
744
|
+
this.resolved.clear();
|
|
745
|
+
this.server = project.host.serverNames?.(uri)[0] ?? '';
|
|
746
|
+
if (!refresh) {
|
|
747
|
+
const caret = editor.getCaret();
|
|
748
|
+
const lineBefore = textBeforeOf(editor, caret);
|
|
749
|
+
const prefix = context.triggerKind === 2 ? '' : identifierPrefix(lineBefore);
|
|
750
|
+
this.start = {
|
|
751
|
+
line: caret.line,
|
|
752
|
+
character: caret.character - prefix.length
|
|
753
|
+
};
|
|
754
|
+
this.active = 0;
|
|
755
|
+
this.touched = false;
|
|
756
|
+
} else if (this.touched && this.ranked[this.active] !== undefined) {
|
|
757
|
+
// The list is made again, so the row that was chosen is found again by what it says.
|
|
758
|
+
this.activeKey = RecentChoices.keyOf(this.ranked[this.active].item);
|
|
759
|
+
}
|
|
760
|
+
const asked = this.explicit && !refresh;
|
|
761
|
+
this.refilter();
|
|
762
|
+
if (asked && !this.isOpen && !this.incomplete) {
|
|
763
|
+
this.language.project.host.notify?.({
|
|
764
|
+
id: TOAST_ID,
|
|
765
|
+
kind: 'error',
|
|
766
|
+
title: this.language.project.i18n.t('editor:language.completion.none')
|
|
767
|
+
});
|
|
768
|
+
} else if (asked && this.ranked.length === 1 && !this.incomplete) {
|
|
769
|
+
// One answer to a list that was asked for is the one wanted.
|
|
770
|
+
void this.accept(false, 0);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
refilter() {
|
|
774
|
+
const {
|
|
775
|
+
editor
|
|
776
|
+
} = this.language;
|
|
777
|
+
const caret = editor.getCaret();
|
|
778
|
+
if (this.start === null || caret.line !== this.start.line || caret.character < this.start.character) {
|
|
779
|
+
this.close();
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const lineBefore = textBeforeOf(editor, caret);
|
|
783
|
+
const typedKey = `${caret.line}:${caret.character}:${lineBefore}`;
|
|
784
|
+
if (this.rankedFor?.pool === this.pool && this.rankedFor.key === typedKey) {
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
this.rankedFor = {
|
|
788
|
+
pool: this.pool,
|
|
789
|
+
key: typedKey
|
|
790
|
+
};
|
|
791
|
+
// Most items of a list replace the same range, so the text under it is read once and not once per item.
|
|
792
|
+
const typed = new Map();
|
|
793
|
+
const textBetween = range => {
|
|
794
|
+
const key = `${range.start.line}:${range.start.character}:${range.end.line}:${range.end.character}`;
|
|
795
|
+
let text = typed.get(key);
|
|
796
|
+
if (text === undefined) {
|
|
797
|
+
text = editor.textInRange(range);
|
|
798
|
+
typed.set(key, text);
|
|
799
|
+
}
|
|
800
|
+
return text;
|
|
801
|
+
};
|
|
802
|
+
const before = this.ranked[this.active]?.item;
|
|
803
|
+
const recent = recentChoicesOf(this.language.languageId);
|
|
804
|
+
this.ranked = rankCompletions(this.pool, item => prefixFor(item, caret, lineBefore, textBetween), item => recent.recency(item));
|
|
805
|
+
if (this.ranked.length === 0) {
|
|
806
|
+
if (this.incomplete && this.pool.length > 0) {
|
|
807
|
+
this.beginRequest({
|
|
808
|
+
triggerKind: 3
|
|
809
|
+
}, START_DELAY_MS);
|
|
810
|
+
}
|
|
811
|
+
this.close();
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
const key = this.activeKey ?? (this.touched && before !== undefined ? RecentChoices.keyOf(before) : null);
|
|
815
|
+
this.activeKey = null;
|
|
816
|
+
const kept = key === null ? -1 : this.ranked.findIndex(entry => RecentChoices.keyOf(entry.item) === key);
|
|
817
|
+
this.active = kept >= 0 ? kept : 0;
|
|
818
|
+
this.touched = this.touched && kept >= 0;
|
|
819
|
+
this.publish();
|
|
820
|
+
this.scheduleResolve();
|
|
821
|
+
if (this.incomplete) {
|
|
822
|
+
this.beginRequest({
|
|
823
|
+
triggerKind: 3
|
|
824
|
+
}, START_DELAY_MS);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
scheduleResolve() {
|
|
828
|
+
const {
|
|
829
|
+
project,
|
|
830
|
+
uri
|
|
831
|
+
} = this.language;
|
|
832
|
+
this.timers.clear(this.resolveTimer);
|
|
833
|
+
this.resolving?.abort();
|
|
834
|
+
const entry = this.ranked[this.active];
|
|
835
|
+
if (entry === undefined || this.resolved.has(entry.item) || project.service.providerOptions('completionItem/resolve', uri) === undefined) {
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
const request = this.request;
|
|
839
|
+
this.resolveTimer = this.timers.set(() => {
|
|
840
|
+
const controller = new AbortController();
|
|
841
|
+
this.resolving = controller;
|
|
842
|
+
void project.service.resolveCompletion(uri, entry.item, {
|
|
843
|
+
signal: controller.signal
|
|
844
|
+
}).then(item => {
|
|
845
|
+
this.resolved.set(entry.item, {
|
|
846
|
+
...entry.item,
|
|
847
|
+
...item
|
|
848
|
+
});
|
|
849
|
+
if (this.ranked[this.active]?.item === entry.item) {
|
|
850
|
+
this.publish();
|
|
851
|
+
}
|
|
852
|
+
}).catch(error => {
|
|
853
|
+
// A server only resolves an item for the text it was made for, so only an item that is wanted and stale costs a new list.
|
|
854
|
+
if (error instanceof StaleResultError && !controller.signal.aborted && request === this.request && this.start !== null) {
|
|
855
|
+
void this.ask({
|
|
856
|
+
triggerKind: 1
|
|
857
|
+
}, request, true);
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
}, RESOLVE_DELAY_MS);
|
|
861
|
+
}
|
|
862
|
+
publish() {
|
|
863
|
+
const entry = this.ranked[this.active];
|
|
864
|
+
if (entry === undefined || this.start === null) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
const item = this.resolved.get(entry.item) ?? entry.item;
|
|
868
|
+
const descriptions = qualifiersOf(this.ranked.map(ranked => ranked.item));
|
|
869
|
+
const rows = this.ranked.map(({
|
|
870
|
+
item: row,
|
|
871
|
+
prefix
|
|
872
|
+
}, index) => ({
|
|
873
|
+
label: row.label,
|
|
874
|
+
kind: row.kind,
|
|
875
|
+
detail: row.labelDetails?.detail ?? '',
|
|
876
|
+
description: descriptions[index],
|
|
877
|
+
matches: matchedCharacters(row.label, prefix),
|
|
878
|
+
deprecated: row.deprecated === true || row.tags?.includes(1) === true
|
|
879
|
+
}));
|
|
880
|
+
const highlightLanguage = shikiLanguageOf(this.language.languageId);
|
|
881
|
+
const view = {
|
|
882
|
+
anchor: this.start,
|
|
883
|
+
rows,
|
|
884
|
+
active: this.active,
|
|
885
|
+
detailsOpen: this.detailsOpen,
|
|
886
|
+
docs: completionDocsOf(item, highlightLanguage),
|
|
887
|
+
server: this.server,
|
|
888
|
+
highlightLanguage
|
|
889
|
+
};
|
|
890
|
+
this.language.popups.setState({
|
|
891
|
+
completion: view
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
}
|