@adecore/editor-react 0.31.12 → 0.32.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-model.js
CHANGED
|
@@ -6,23 +6,23 @@ import { compileSnippet, parseSnippet, tabOrder } from './snippet.js';
|
|
|
6
6
|
import { indentSnippet } from './snippet-indent.js';
|
|
7
7
|
const IDENTIFIER_CHARACTER = /[\p{L}\p{N}\p{M}_$]/u;
|
|
8
8
|
export function isIdentifierCharacter(character) {
|
|
9
|
-
|
|
9
|
+
return IDENTIFIER_CHARACTER.test(character);
|
|
10
10
|
}
|
|
11
11
|
let lastBefore = '';
|
|
12
12
|
let lastPrefix = '';
|
|
13
13
|
/* The word being typed: the identifier characters at the end of the text before the caret. A list asks it of every item with the same line, so the last answer is kept. */
|
|
14
14
|
export function identifierPrefix(before) {
|
|
15
|
-
|
|
16
|
-
return lastPrefix;
|
|
17
|
-
}
|
|
18
|
-
const characters = Array.from(before);
|
|
19
|
-
let start = characters.length;
|
|
20
|
-
while (start > 0 && IDENTIFIER_CHARACTER.test(characters[start - 1])) {
|
|
21
|
-
start--;
|
|
22
|
-
}
|
|
23
|
-
lastBefore = before;
|
|
24
|
-
lastPrefix = characters.slice(start).join('');
|
|
15
|
+
if (before === lastBefore) {
|
|
25
16
|
return lastPrefix;
|
|
17
|
+
}
|
|
18
|
+
const characters = Array.from(before);
|
|
19
|
+
let start = characters.length;
|
|
20
|
+
while (start > 0 && IDENTIFIER_CHARACTER.test(characters[start - 1])) {
|
|
21
|
+
start--;
|
|
22
|
+
}
|
|
23
|
+
lastBefore = before;
|
|
24
|
+
lastPrefix = characters.slice(start).join('');
|
|
25
|
+
return lastPrefix;
|
|
26
26
|
}
|
|
27
27
|
const OPENS_VALUE = /(?:[-=!<>&|?+*/%^:,]|(?:^|[^\p{L}\p{N}_$])(?:return|new|case|in|of|instanceof|typeof|keyof|await|yield|throw|extends|implements|as|is|not|and|or|echo|print|use|import|from|clone))$/u;
|
|
28
28
|
const NOT_A_VALUE = /(?:\+\+|--|\/\/|\/\*\*?|^\s*[-+*])$/;
|
|
@@ -32,36 +32,35 @@ const NOT_A_VALUE = /(?:\+\+|--|\/\/|\/\*\*?|^\s*[-+*])$/;
|
|
|
32
32
|
* opener, a list marker and `++` or `--` open nothing.
|
|
33
33
|
*/
|
|
34
34
|
export function opensCompletion(before, text) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
if (text === '(' || text === '()' || text === '[' || text === '[]') {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return text === ' ' && OPENS_VALUE.test(before) && !NOT_A_VALUE.test(before);
|
|
39
39
|
}
|
|
40
40
|
function run(start, length) {
|
|
41
|
-
|
|
41
|
+
return Array.from({
|
|
42
|
+
length
|
|
43
|
+
}, (_, offset) => start + offset);
|
|
42
44
|
}
|
|
43
45
|
const SHAPE_LIMIT = 4000;
|
|
44
46
|
const shapes = new Map();
|
|
45
47
|
/* What matching reads of a label, which is the same at every keystroke of a word and costs a regular expression pass to find. */
|
|
46
48
|
function shapeOf(value) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
shapes.set(value, shape);
|
|
64
|
-
return shape;
|
|
49
|
+
const known = shapes.get(value);
|
|
50
|
+
if (known !== undefined) {
|
|
51
|
+
return known;
|
|
52
|
+
}
|
|
53
|
+
const boundaries = [...value.matchAll(/(?:^|[_$\s-])([\p{L}\p{N}])|(\p{Lu})/gu)];
|
|
54
|
+
const shape = {
|
|
55
|
+
lower: value.toLocaleLowerCase(),
|
|
56
|
+
offsets: boundaries.map(boundary => boundary.index + (boundary[1] ? boundary[0].length - boundary[1].length : 0)),
|
|
57
|
+
initials: boundaries.map(match => match[1] ?? match[2]).join('').toLocaleLowerCase()
|
|
58
|
+
};
|
|
59
|
+
if (shapes.size >= SHAPE_LIMIT) {
|
|
60
|
+
shapes.clear();
|
|
61
|
+
}
|
|
62
|
+
shapes.set(value, shape);
|
|
63
|
+
return shape;
|
|
65
64
|
}
|
|
66
65
|
/*
|
|
67
66
|
* How well a prefix fits a label, lower being better, and which characters it matched, or undefined when
|
|
@@ -70,75 +69,98 @@ function shapeOf(value) {
|
|
|
70
69
|
* in order for a prefix of two or more.
|
|
71
70
|
*/
|
|
72
71
|
export function matchDetail(value, prefix) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
72
|
+
if (prefix === '') {
|
|
73
|
+
return {
|
|
74
|
+
score: 0,
|
|
75
|
+
positions: []
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (value.startsWith(prefix)) {
|
|
79
|
+
return {
|
|
80
|
+
score: 0,
|
|
81
|
+
positions: run(0, prefix.length)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const {
|
|
85
|
+
lower,
|
|
86
|
+
offsets,
|
|
87
|
+
initials
|
|
88
|
+
} = shapeOf(value);
|
|
89
|
+
const query = prefix.toLocaleLowerCase();
|
|
90
|
+
if (lower.startsWith(query)) {
|
|
91
|
+
return {
|
|
92
|
+
score: 1,
|
|
93
|
+
positions: run(0, query.length)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
for (const offset of offsets) {
|
|
97
|
+
if (lower.slice(offset).startsWith(query)) {
|
|
98
|
+
return {
|
|
99
|
+
score: 2 + offset / 1000,
|
|
100
|
+
positions: run(offset, query.length)
|
|
101
|
+
};
|
|
94
102
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
}
|
|
104
|
+
if (initials.startsWith(query)) {
|
|
105
|
+
return {
|
|
106
|
+
score: 3,
|
|
107
|
+
positions: offsets.slice(0, query.length)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (query.length < 2 || lower[0] !== query[0]) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
let cursor = 0;
|
|
114
|
+
const positions = [];
|
|
115
|
+
for (const character of query) {
|
|
116
|
+
const next = lower.indexOf(character, cursor);
|
|
117
|
+
if (next < 0) {
|
|
118
|
+
return undefined;
|
|
104
119
|
}
|
|
105
|
-
|
|
120
|
+
positions.push(next);
|
|
121
|
+
cursor = next + character.length;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
score: 4 + cursor / 1000,
|
|
125
|
+
positions
|
|
126
|
+
};
|
|
106
127
|
}
|
|
107
128
|
export function matchScore(value, prefix) {
|
|
108
|
-
|
|
129
|
+
return matchDetail(value, prefix)?.score;
|
|
109
130
|
}
|
|
110
131
|
/* The range an item replaces when it is accepted; for an insert-or-replace edit, the one `replace` asks for. */
|
|
111
132
|
export function editRangeOf(item, replace) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
133
|
+
const edit = item.textEdit;
|
|
134
|
+
if (edit === undefined) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
if ('range' in edit) {
|
|
138
|
+
return edit.range;
|
|
139
|
+
}
|
|
140
|
+
return replace ? edit.replace : edit.insert;
|
|
120
141
|
}
|
|
121
142
|
/* Items from a result, with the list's defaults filled in so each carries its own edit range and format. */
|
|
122
143
|
export function itemsOf(result) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
144
|
+
const list = result === null || Array.isArray(result) ? undefined : result;
|
|
145
|
+
const items = result === null ? [] : Array.isArray(result) ? result : result.items;
|
|
146
|
+
return {
|
|
147
|
+
incomplete: list?.isIncomplete ?? false,
|
|
148
|
+
items: items.flatMap(item => {
|
|
149
|
+
if (typeof item?.label !== 'string') {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
return [applyCompletionDefaults(item, list?.itemDefaults, {
|
|
154
|
+
onInvalidRange: () => undefined
|
|
155
|
+
})];
|
|
156
|
+
} catch (error) {
|
|
157
|
+
if (error instanceof TypeError || error instanceof RangeError) {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
};
|
|
142
164
|
}
|
|
143
165
|
/*
|
|
144
166
|
* What a person has typed of an item: from the start of the range it will replace up to the caret when
|
|
@@ -146,70 +168,80 @@ export function itemsOf(result) {
|
|
|
146
168
|
* `thisArg.` from filtering on the dot.
|
|
147
169
|
*/
|
|
148
170
|
export function prefixFor(item, caret, lineBefore, textBetween) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
171
|
+
const range = editRangeOf(item, false);
|
|
172
|
+
if (range !== null && range.start.line === caret.line && comparePositions(range.start, caret) <= 0 && comparePositions(caret, range.end) <= 0) {
|
|
173
|
+
return textBetween({
|
|
174
|
+
start: range.start,
|
|
175
|
+
end: caret
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return identifierPrefix(lineBefore);
|
|
154
179
|
}
|
|
155
180
|
/*
|
|
156
181
|
* The items that fit what is typed, best first: by how they match, then one the person chose lately, a
|
|
157
182
|
* preselected one, and the server's own order. `recency` is higher for a choice made more recently, and 0 for none.
|
|
158
183
|
*/
|
|
159
184
|
export function rankCompletions(items, prefixOf, recency = () => 0, limit = 150) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
185
|
+
const {
|
|
186
|
+
compare
|
|
187
|
+
} = labelCollator();
|
|
188
|
+
return items.map((item, index) => {
|
|
189
|
+
const prefix = prefixOf(item);
|
|
190
|
+
return {
|
|
191
|
+
item,
|
|
192
|
+
index,
|
|
193
|
+
prefix,
|
|
194
|
+
score: matchScore(item.filterText ?? item.label, prefix),
|
|
195
|
+
recent: recency(item)
|
|
196
|
+
};
|
|
197
|
+
}).filter(entry => entry.score !== undefined).sort((left, right) => left.score - right.score || right.recent - left.recent || Number(right.item.preselect === true) - Number(left.item.preselect === true) || compare(left.item.sortText ?? left.item.label, right.item.sortText ?? right.item.label) || left.index - right.index).slice(0, limit).map(({
|
|
198
|
+
item,
|
|
199
|
+
score,
|
|
200
|
+
prefix
|
|
201
|
+
}) => ({
|
|
202
|
+
item,
|
|
203
|
+
score,
|
|
204
|
+
prefix
|
|
205
|
+
}));
|
|
174
206
|
}
|
|
175
207
|
/* The characters of a label to mark for what was typed; none when the label is not what the server filtered on. */
|
|
176
208
|
export function matchedCharacters(label, prefix) {
|
|
177
|
-
|
|
209
|
+
return matchDetail(label, prefix)?.positions ?? [];
|
|
178
210
|
}
|
|
179
211
|
/* The first line of what a server says about where an item comes from, with a PHP `use` made into the namespace it names. */
|
|
180
212
|
function sourceOf(item) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
213
|
+
const detail = item.detail?.split('\n')[0]?.trim() ?? '';
|
|
214
|
+
const imported = /^use\s+(.+)$/.exec(detail);
|
|
215
|
+
return imported === null ? detail : imported[1].slice(0, Math.max(0, imported[1].lastIndexOf('\\')));
|
|
184
216
|
}
|
|
185
217
|
/* What stands right-aligned after a row: the module or namespace the item comes from, else what the server calls its type. */
|
|
186
218
|
export function qualifierOf(item) {
|
|
187
|
-
|
|
188
|
-
|
|
219
|
+
const description = item.labelDetails?.description?.trim() ?? '';
|
|
220
|
+
return description !== '' ? description : sourceOf(item);
|
|
189
221
|
}
|
|
190
222
|
/*
|
|
191
223
|
* The qualifier of each row of a list. Items that share a label are told apart however it takes: by the
|
|
192
224
|
* qualifier, or by what the server says besides, and by a number when a server says nothing that differs.
|
|
193
225
|
*/
|
|
194
226
|
export function qualifiersOf(items) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
const alternate = indexes.map((index) => sourceOf(items[index]) || result[index]);
|
|
203
|
-
const distinct = new Set(alternate).size === indexes.length && !alternate.includes('');
|
|
204
|
-
indexes.forEach((index, position) => {
|
|
205
|
-
result[index] = distinct ? alternate[position] : `${result[index] ?? ''} #${position + 1}`.trim();
|
|
206
|
-
});
|
|
227
|
+
const result = items.map(qualifierOf);
|
|
228
|
+
const groups = new Map();
|
|
229
|
+
items.forEach((item, index) => groups.set(item.label, [...(groups.get(item.label) ?? []), index]));
|
|
230
|
+
for (const indexes of groups.values()) {
|
|
231
|
+
if (indexes.length < 2 || new Set(indexes.map(index => result[index])).size === indexes.length) {
|
|
232
|
+
continue;
|
|
207
233
|
}
|
|
208
|
-
|
|
234
|
+
const alternate = indexes.map(index => sourceOf(items[index]) || result[index]);
|
|
235
|
+
const distinct = new Set(alternate).size === indexes.length && !alternate.includes('');
|
|
236
|
+
indexes.forEach((index, position) => {
|
|
237
|
+
result[index] = distinct ? alternate[position] : `${result[index] ?? ''} #${position + 1}`.trim();
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
209
241
|
}
|
|
210
242
|
/* A snippet as the plain text it would insert: tab stops and variables vanish, placeholders and choices become their text, and the escapes of the format are undone. */
|
|
211
243
|
export function snippetToText(snippet) {
|
|
212
|
-
|
|
244
|
+
return parseSnippet(snippet).text;
|
|
213
245
|
}
|
|
214
246
|
export const METHOD_KIND = 2;
|
|
215
247
|
export const FUNCTION_KIND = 3;
|
|
@@ -217,80 +249,132 @@ export const CONSTRUCTOR_KIND = 4;
|
|
|
217
249
|
export const CLASS_KIND = 7;
|
|
218
250
|
export const SNIPPET_FORMAT = 2;
|
|
219
251
|
export function isCallableKind(kind) {
|
|
220
|
-
|
|
252
|
+
return kind === METHOD_KIND || kind === FUNCTION_KIND || kind === CONSTRUCTOR_KIND;
|
|
221
253
|
}
|
|
222
254
|
/* A call snippet with the call cut off, for a name that already has its parentheses: `log(${1:value})$0` becomes `log`. */
|
|
223
255
|
function withoutCall(snippet) {
|
|
224
|
-
|
|
225
|
-
|
|
256
|
+
const open = snippet.search(/(?<!\\)\(/);
|
|
257
|
+
return open > 0 ? snippet.slice(0, open) : snippet;
|
|
226
258
|
}
|
|
227
259
|
/*
|
|
228
260
|
* What goes into the text for an item and the range it takes the place of, given the caret. `lineAfter` is
|
|
229
261
|
* the rest of the line after the caret: an item that would add a call does not when a parenthesis follows.
|
|
230
262
|
*/
|
|
231
263
|
export function insertionOf(item, caret, lineBefore, replace, lineAfter = '', options = {}) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
264
|
+
const raw = item.textEdit?.newText ?? item.insertText ?? item.label;
|
|
265
|
+
const edit = editRangeOf(item, replace);
|
|
266
|
+
const range = edit === null ? {
|
|
267
|
+
start: {
|
|
268
|
+
line: caret.line,
|
|
269
|
+
character: caret.character - identifierPrefix(lineBefore).length
|
|
270
|
+
},
|
|
271
|
+
end: caret
|
|
272
|
+
} :
|
|
273
|
+
// The word grew since the answer came, so what was typed since belongs to what is replaced.
|
|
274
|
+
{
|
|
275
|
+
start: edit.start,
|
|
276
|
+
end: comparePositions(edit.end, caret) < 0 && edit.end.line === caret.line ? caret : edit.end
|
|
277
|
+
};
|
|
278
|
+
if (item.insertTextFormat !== SNIPPET_FORMAT) {
|
|
279
|
+
const adjusted = item.insertTextMode === 2 ? indentSnippet({
|
|
280
|
+
text: raw,
|
|
281
|
+
stops: []
|
|
282
|
+
}, options.indentationBefore ?? lineBefore.slice(0, range.start.character)) : {
|
|
283
|
+
text: raw,
|
|
284
|
+
stops: []
|
|
285
|
+
};
|
|
249
286
|
return {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
: {
|
|
257
|
-
variableTransforms: rendered.transforms.map((range) => ({ ...range.transform, value: rendered.text.slice(range.start, range.end) }))
|
|
258
|
-
}),
|
|
259
|
-
...(parsed.limited ? { limited: true } : {})
|
|
287
|
+
range,
|
|
288
|
+
text: adjusted.text,
|
|
289
|
+
stops: [],
|
|
290
|
+
...(adjusted.limited ? {
|
|
291
|
+
limited: true
|
|
292
|
+
} : {})
|
|
260
293
|
};
|
|
294
|
+
}
|
|
295
|
+
const followed = range.end.line === caret.line && lineAfter.slice(Math.max(0, range.end.character - caret.character)).startsWith('(');
|
|
296
|
+
const source = followed && isCallableKind(item.kind) ? withoutCall(raw) : raw;
|
|
297
|
+
const template = options.template?.source === source ? options.template : compileSnippet(source);
|
|
298
|
+
const rendered = template.render(options.resolve, options.variableTransforms);
|
|
299
|
+
const parsed = item.insertTextMode === 2 ? indentSnippet(rendered, options.indentationBefore ?? lineBefore.slice(0, range.start.character)) : rendered;
|
|
300
|
+
return {
|
|
301
|
+
range,
|
|
302
|
+
text: parsed.text,
|
|
303
|
+
stops: tabOrder(parsed),
|
|
304
|
+
fields: parsed.fields,
|
|
305
|
+
...(options.variableTransforms !== undefined || rendered.transforms === undefined ? {} : {
|
|
306
|
+
variableTransforms: rendered.transforms.map(range => ({
|
|
307
|
+
...range.transform,
|
|
308
|
+
value: rendered.text.slice(range.start, range.end)
|
|
309
|
+
}))
|
|
310
|
+
}),
|
|
311
|
+
...(parsed.limited ? {
|
|
312
|
+
limited: true
|
|
313
|
+
} : {})
|
|
314
|
+
};
|
|
261
315
|
}
|
|
262
316
|
/*
|
|
263
317
|
* The same insertion at every other caret that has the word the primary caret has before it, so a name typed at
|
|
264
318
|
* several carets is completed at all of them. A caret that is inside a selection, or after other text, is left alone.
|
|
265
319
|
*/
|
|
266
320
|
export function mirroredInsertions(carets, primary, caret, text, insertion, includeSelections = false) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
321
|
+
const before = insertion.range.start.line === caret.line ? caret.character - insertion.range.start.character : -1;
|
|
322
|
+
const after = insertion.range.end.line === caret.line ? insertion.range.end.character - caret.character : -1;
|
|
323
|
+
if (before < 0 || after < 0) {
|
|
324
|
+
return [];
|
|
325
|
+
}
|
|
326
|
+
const typed = text({
|
|
327
|
+
start: insertion.range.start,
|
|
328
|
+
end: caret
|
|
329
|
+
});
|
|
330
|
+
const replaced = text({
|
|
331
|
+
start: caret,
|
|
332
|
+
end: insertion.range.end
|
|
333
|
+
});
|
|
334
|
+
return carets.flatMap(other => {
|
|
335
|
+
const at = includeSelections ? other.end : other.start;
|
|
336
|
+
if (comparePositions(other.start, primary.start) === 0 && comparePositions(other.end, primary.end) === 0) {
|
|
337
|
+
return [];
|
|
271
338
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
339
|
+
if (!includeSelections && comparePositions(other.start, other.end) !== 0 || at.character < before) {
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
const range = {
|
|
343
|
+
start: {
|
|
344
|
+
line: at.line,
|
|
345
|
+
character: at.character - before
|
|
346
|
+
},
|
|
347
|
+
end: {
|
|
348
|
+
line: at.line,
|
|
349
|
+
character: at.character + after
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
const preceding = range.start.character > 0 ? text({
|
|
353
|
+
start: {
|
|
354
|
+
line: at.line,
|
|
355
|
+
character: range.start.character - 1
|
|
356
|
+
},
|
|
357
|
+
end: range.start
|
|
358
|
+
}) : '';
|
|
359
|
+
const startsWord = preceding === '' || !isIdentifierCharacter(preceding);
|
|
360
|
+
const matches = startsWord && text({
|
|
361
|
+
start: range.start,
|
|
362
|
+
end: at
|
|
363
|
+
}) === typed && text({
|
|
364
|
+
start: at,
|
|
365
|
+
end: range.end
|
|
366
|
+
}) === replaced;
|
|
367
|
+
return matches ? [{
|
|
368
|
+
range,
|
|
369
|
+
text: insertion.text
|
|
370
|
+
}] : [];
|
|
371
|
+
});
|
|
288
372
|
}
|
|
289
373
|
export function documentationText(documentation) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
374
|
+
if (documentation === undefined) {
|
|
375
|
+
return '';
|
|
376
|
+
}
|
|
377
|
+
return typeof documentation === 'string' ? documentation : documentation.kind === 'plaintext' ? escapeMarkdown(documentation.value) : documentation.value;
|
|
294
378
|
}
|
|
295
379
|
/* A detail line that only says where an item comes from: a PHP `use` or an auto import. */
|
|
296
380
|
export const SOURCE_LINE = /^(?:use\s|Auto import from\b)/;
|
|
@@ -300,75 +384,81 @@ export const SOURCE_LINE = /^(?:use\s|Auto import from\b)/;
|
|
|
300
384
|
* kept apart, since it is no code.
|
|
301
385
|
*/
|
|
302
386
|
export function completionDocsOf(item, highlightLanguage) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
387
|
+
const lines = (item.detail ?? '').trim().split('\n');
|
|
388
|
+
const hasSource = SOURCE_LINE.test(lines[0] ?? '');
|
|
389
|
+
const source = hasSource ? lines[0].trim() : '';
|
|
390
|
+
const code = (hasSource ? lines.slice(1) : lines).join('\n').trim();
|
|
391
|
+
const parsed = splitSignatures(documentationText(item.documentation));
|
|
392
|
+
const text = {
|
|
393
|
+
signatures: code === '' ? parsed.signatures : [{
|
|
394
|
+
language: highlightLanguage,
|
|
395
|
+
code
|
|
396
|
+
}, ...parsed.signatures],
|
|
397
|
+
markdown: parsed.markdown
|
|
398
|
+
};
|
|
399
|
+
return source === '' && isEmptyHover(text) ? null : {
|
|
400
|
+
source,
|
|
401
|
+
text
|
|
402
|
+
};
|
|
313
403
|
}
|
|
314
404
|
const KIND_LETTERS = {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
405
|
+
1: 't',
|
|
406
|
+
2: 'm',
|
|
407
|
+
3: 'f',
|
|
408
|
+
4: 'c',
|
|
409
|
+
5: 'p',
|
|
410
|
+
6: 'v',
|
|
411
|
+
7: 'c',
|
|
412
|
+
8: 'i',
|
|
413
|
+
9: 'm',
|
|
414
|
+
10: 'p',
|
|
415
|
+
11: 'u',
|
|
416
|
+
12: 'v',
|
|
417
|
+
13: 'e',
|
|
418
|
+
14: 'k',
|
|
419
|
+
15: 's',
|
|
420
|
+
16: 'c',
|
|
421
|
+
17: 'f',
|
|
422
|
+
18: 'r',
|
|
423
|
+
19: 'f',
|
|
424
|
+
20: 'e',
|
|
425
|
+
21: 'k',
|
|
426
|
+
22: 's',
|
|
427
|
+
23: 'e',
|
|
428
|
+
24: 'o',
|
|
429
|
+
25: 't'
|
|
340
430
|
};
|
|
341
431
|
const KIND_TONES = {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
432
|
+
2: 'callable',
|
|
433
|
+
3: 'callable',
|
|
434
|
+
4: 'callable',
|
|
435
|
+
5: 'value',
|
|
436
|
+
6: 'value',
|
|
437
|
+
10: 'value',
|
|
438
|
+
12: 'value',
|
|
439
|
+
20: 'value',
|
|
440
|
+
21: 'value',
|
|
441
|
+
7: 'type',
|
|
442
|
+
8: 'type',
|
|
443
|
+
9: 'type',
|
|
444
|
+
13: 'type',
|
|
445
|
+
22: 'type',
|
|
446
|
+
25: 'type'
|
|
357
447
|
};
|
|
358
448
|
export function kindLetterOf(kind) {
|
|
359
|
-
|
|
449
|
+
return kind === undefined ? '·' : KIND_LETTERS[kind] ?? '·';
|
|
360
450
|
}
|
|
361
451
|
export function kindToneOf(kind) {
|
|
362
|
-
|
|
452
|
+
return kind === undefined ? 'other' : KIND_TONES[kind] ?? 'other';
|
|
363
453
|
}
|
|
364
454
|
/*
|
|
365
455
|
* The rows of a list worth drawing: the ones in view, a few on each side, and the active one with the
|
|
366
456
|
* same margin, so the rows between never leave a gap. The rest is only space.
|
|
367
457
|
*/
|
|
368
458
|
export function rowWindow(scrollTop, active, total, rowHeight, visible, margin, padding) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
}
|
|
459
|
+
const shown = Math.floor(Math.max(0, scrollTop - padding) / rowHeight);
|
|
460
|
+
return {
|
|
461
|
+
first: Math.max(0, Math.min(shown, active) - margin),
|
|
462
|
+
last: Math.min(total, Math.max(shown + visible + 1, active + 1) + margin)
|
|
463
|
+
};
|
|
464
|
+
}
|