@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/inlay-hints.js
CHANGED
|
@@ -14,96 +14,112 @@ const DOUBLE_PRESS_MS = 500;
|
|
|
14
14
|
const MARGIN_LINES = 60;
|
|
15
15
|
/* A hint's label is a string or a list of parts, which are read as one text. */
|
|
16
16
|
export function hintLabelOf(hint) {
|
|
17
|
-
|
|
17
|
+
return typeof hint.label === 'string' ? hint.label : hint.label.map(part => part.value).join('');
|
|
18
18
|
}
|
|
19
19
|
/* The hints worth drawing, in the order of the text. */
|
|
20
20
|
export function drawableHints(hints) {
|
|
21
|
-
|
|
22
|
-
.filter((hint) => hintLabelOf(hint) !== '')
|
|
23
|
-
.sort((left, right) => left.position.line - right.position.line || left.position.character - right.position.character);
|
|
21
|
+
return hints.filter(hint => hintLabelOf(hint) !== '').sort((left, right) => left.position.line - right.position.line || left.position.character - right.position.character);
|
|
24
22
|
}
|
|
25
23
|
/* A hint as the editor draws it: a label with parts keeps them apart so each can be pointed at. */
|
|
26
24
|
export function editorHintOf(hint, id) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
return {
|
|
26
|
+
id,
|
|
27
|
+
position: hint.position,
|
|
28
|
+
label: hintLabelOf(hint),
|
|
29
|
+
...(typeof hint.label === 'string' ? {} : {
|
|
30
|
+
parts: hint.label.map(part => part.value)
|
|
31
|
+
}),
|
|
32
|
+
...(hint.paddingLeft ? {
|
|
33
|
+
paddingLeft: true
|
|
34
|
+
} : {}),
|
|
35
|
+
...(hint.paddingRight ? {
|
|
36
|
+
paddingRight: true
|
|
37
|
+
} : {})
|
|
38
|
+
};
|
|
35
39
|
}
|
|
36
40
|
export function editorHintsOf(hints) {
|
|
37
|
-
|
|
41
|
+
return drawableHints(hints).map((hint, index) => editorHintOf(hint, `hint-${index}`));
|
|
38
42
|
}
|
|
39
43
|
/* The lines in view with a margin of lines around them, from the start of the first to the end of the last. */
|
|
40
44
|
export function withMargin(visible, endOfDocument, endOfLine) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
const first = Math.max(0, visible.start.line - MARGIN_LINES);
|
|
46
|
+
const last = Math.min(endOfDocument.line, visible.end.line + MARGIN_LINES);
|
|
47
|
+
return {
|
|
48
|
+
start: {
|
|
49
|
+
line: first,
|
|
50
|
+
character: 0
|
|
51
|
+
},
|
|
52
|
+
end: last === endOfDocument.line ? endOfDocument : endOfLine(last)
|
|
53
|
+
};
|
|
44
54
|
}
|
|
45
55
|
/* Whether what was asked for already holds every line in view. */
|
|
46
56
|
export function covers(asked, visible) {
|
|
47
|
-
|
|
57
|
+
return asked.start.line <= visible.start.line && asked.end.line >= visible.end.line;
|
|
48
58
|
}
|
|
49
59
|
/* The words of a tooltip: plain text is escaped so it reads as typed, Markdown goes through the hover card's own rendering. */
|
|
50
60
|
export function tooltipTextOf(tooltip) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
if (tooltip === undefined) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const text = hoverTextOf({
|
|
65
|
+
contents: typeof tooltip === 'string' ? {
|
|
66
|
+
kind: 'plaintext',
|
|
67
|
+
value: tooltip
|
|
68
|
+
} : tooltip
|
|
69
|
+
});
|
|
70
|
+
return isEmptyHover(text) ? null : text;
|
|
56
71
|
}
|
|
57
72
|
/* One part of a hint's label, or null for a hint whose label is a string. */
|
|
58
73
|
export function partOf(hint, index) {
|
|
59
|
-
|
|
74
|
+
return index === null || typeof hint.label === 'string' ? null : hint.label[index] ?? null;
|
|
60
75
|
}
|
|
61
76
|
/* What a hover shows: the part's tooltip, else the hint's, else (with no part to point at) that of the first part that has one. */
|
|
62
77
|
export function tooltipOf(hint, index) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
const part = partOf(hint, index);
|
|
79
|
+
const own = part?.tooltip ?? hint.tooltip;
|
|
80
|
+
if (own !== undefined || index !== null || typeof hint.label === 'string') {
|
|
81
|
+
return tooltipTextOf(own);
|
|
82
|
+
}
|
|
83
|
+
return tooltipTextOf(hint.label.find(candidate => candidate.tooltip !== undefined)?.tooltip);
|
|
69
84
|
}
|
|
70
85
|
/* Whether a server could still say more about the hint: nothing in it gives a tooltip, a place or a command yet. */
|
|
71
86
|
export function lacksInfo(hint, index) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
if (hint.tooltip !== undefined) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const parts = typeof hint.label === 'string' ? [] : index === null ? hint.label : [hint.label[index]];
|
|
91
|
+
return !parts.some(part => part !== undefined && (part.tooltip !== undefined || part.location !== undefined || part.command !== undefined));
|
|
77
92
|
}
|
|
78
93
|
/* Whether the edits fit the text as it is now: positions inside it, each range in order, none overlapping the next. */
|
|
79
94
|
export function editsFit(editor, edits) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const ordered = [...edits].sort((left, right) => (before(left.range.start, right.range.start) ? -1 : 1));
|
|
89
|
-
return ordered.every((edit, index) => {
|
|
90
|
-
const next = ordered[index + 1];
|
|
91
|
-
return (inside(edit.range.start) &&
|
|
92
|
-
inside(edit.range.end) &&
|
|
93
|
-
before(edit.range.start, edit.range.end) &&
|
|
94
|
-
(next === undefined || before(edit.range.end, next.range.start)));
|
|
95
|
-
});
|
|
95
|
+
const end = editor.positionAt(Number.MAX_SAFE_INTEGER);
|
|
96
|
+
const inside = position => Number.isInteger(position.line) && Number.isInteger(position.character) && position.line >= 0 && position.character >= 0 && position.line <= end.line && position.character <= lineTextOf(editor, position.line).length;
|
|
97
|
+
const before = (left, right) => left.line < right.line || left.line === right.line && left.character <= right.character;
|
|
98
|
+
const ordered = [...edits].sort((left, right) => before(left.range.start, right.range.start) ? -1 : 1);
|
|
99
|
+
return ordered.every((edit, index) => {
|
|
100
|
+
const next = ordered[index + 1];
|
|
101
|
+
return inside(edit.range.start) && inside(edit.range.end) && before(edit.range.start, edit.range.end) && (next === undefined || before(edit.range.end, next.range.start));
|
|
102
|
+
});
|
|
96
103
|
}
|
|
97
104
|
/* What a press does, from what is known of the hint: a double click inserts it and nothing else, Mod+click follows a part's place, a plain click runs its command. */
|
|
98
105
|
export function actionOf(hint, click) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
if (click.clicks === 2) {
|
|
107
|
+
return hint.textEdits !== undefined && hint.textEdits.length > 0 ? {
|
|
108
|
+
kind: 'insert',
|
|
109
|
+
edits: hint.textEdits
|
|
110
|
+
} : null;
|
|
111
|
+
}
|
|
112
|
+
const part = partOf(hint, click.part);
|
|
113
|
+
if (click.mod) {
|
|
114
|
+
return part?.location === undefined ? null : {
|
|
115
|
+
kind: 'go',
|
|
116
|
+
location: part.location
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return part?.command === undefined ? null : {
|
|
120
|
+
kind: 'run',
|
|
121
|
+
command: part.command
|
|
122
|
+
};
|
|
107
123
|
}
|
|
108
124
|
/*
|
|
109
125
|
* Types and parameter names the servers infer, asked for the lines in view and a margin around them. The
|
|
@@ -120,289 +136,318 @@ export function actionOf(hint, click) {
|
|
|
120
136
|
* sits there.
|
|
121
137
|
*/
|
|
122
138
|
export class InlayHintsFeature {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (event.key === modKey) {
|
|
187
|
-
this.setModHeld(event.type === 'keydown');
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
const lost = () => this.setModHeld(false);
|
|
191
|
-
window.addEventListener('keydown', keys);
|
|
192
|
-
window.addEventListener('keyup', keys);
|
|
193
|
-
window.addEventListener('blur', lost);
|
|
194
|
-
offs.push(() => {
|
|
195
|
-
window.removeEventListener('keydown', keys);
|
|
196
|
-
window.removeEventListener('keyup', keys);
|
|
197
|
-
window.removeEventListener('blur', lost);
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
language.onDispose(() => {
|
|
201
|
-
for (const off of offs) {
|
|
202
|
-
off();
|
|
203
|
-
}
|
|
204
|
-
this.cancelHover();
|
|
205
|
-
this.timers.clear(this.commandTimer);
|
|
206
|
-
this.refresher.dispose();
|
|
207
|
-
this.entries.clear();
|
|
208
|
-
editor.setInlayHints([]);
|
|
209
|
-
});
|
|
139
|
+
language;
|
|
140
|
+
timers;
|
|
141
|
+
refresher;
|
|
142
|
+
/* The lines the hints on screen were asked for. */
|
|
143
|
+
asked = null;
|
|
144
|
+
entries = new Map();
|
|
145
|
+
shown = [];
|
|
146
|
+
generation = 0;
|
|
147
|
+
/* Counts the edits of the text, which hints and answers made for an earlier one are tested against. */
|
|
148
|
+
epoch = 0;
|
|
149
|
+
target = null;
|
|
150
|
+
modHeld = false;
|
|
151
|
+
showTimer;
|
|
152
|
+
/* The command of a click that may still become a double click. */
|
|
153
|
+
commandTimer;
|
|
154
|
+
/* Counts presses, so one that waited for a resolve gives way to a later one. */
|
|
155
|
+
pressCount = 0;
|
|
156
|
+
/* Counts what the pointer does, so a tooltip that was waited for is dropped once the pointer has moved on. */
|
|
157
|
+
request = 0;
|
|
158
|
+
constructor(language, timers = realTimers) {
|
|
159
|
+
this.language = language;
|
|
160
|
+
this.timers = timers;
|
|
161
|
+
const {
|
|
162
|
+
editor,
|
|
163
|
+
project,
|
|
164
|
+
uri
|
|
165
|
+
} = language;
|
|
166
|
+
this.refresher = new Refresher(async signal => {
|
|
167
|
+
const {
|
|
168
|
+
service
|
|
169
|
+
} = project;
|
|
170
|
+
if (!service.supports(METHOD, uri)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const range = withMargin(editor.getVisibleRange(), editor.positionAt(Number.MAX_SAFE_INTEGER), line => ({
|
|
174
|
+
line,
|
|
175
|
+
character: lineTextOf(editor, line).length
|
|
176
|
+
}));
|
|
177
|
+
this.asked = range;
|
|
178
|
+
const epoch = this.epoch;
|
|
179
|
+
const hints = await service.inlayHints(uri, range, {
|
|
180
|
+
signal
|
|
181
|
+
});
|
|
182
|
+
if (!signal.aborted && epoch === this.epoch) {
|
|
183
|
+
this.install(hints === null ? [] : drawableHints(hints), epoch);
|
|
184
|
+
}
|
|
185
|
+
}, PAUSE_MS, timers);
|
|
186
|
+
const offs = [editor.onTextChange(() => {
|
|
187
|
+
this.epoch++;
|
|
188
|
+
this.cancelHover();
|
|
189
|
+
this.refresher.later();
|
|
190
|
+
}), editor.onViewChange(() => {
|
|
191
|
+
if (this.asked !== null && !covers(this.asked, editor.getVisibleRange())) {
|
|
192
|
+
this.refresher.later();
|
|
193
|
+
}
|
|
194
|
+
}), language.onProvidersChanged(() => {
|
|
195
|
+
this.refresher.now();
|
|
196
|
+
})];
|
|
197
|
+
if (editor.onInlayHover !== undefined) {
|
|
198
|
+
offs.push(editor.onInlayHover(target => this.hovered(target === null ? null : {
|
|
199
|
+
id: target.id,
|
|
200
|
+
part: target.part
|
|
201
|
+
})));
|
|
210
202
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (held !== this.modHeld) {
|
|
214
|
-
this.modHeld = held;
|
|
215
|
-
this.updateLink();
|
|
216
|
-
}
|
|
203
|
+
if (editor.onInlayClick !== undefined) {
|
|
204
|
+
offs.push(editor.onInlayClick(click => this.pressed(click)));
|
|
217
205
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (
|
|
222
|
-
|
|
206
|
+
if (typeof window !== 'undefined') {
|
|
207
|
+
const modKey = language.apple ? 'Meta' : 'Control';
|
|
208
|
+
const keys = event => {
|
|
209
|
+
if (event.key === modKey) {
|
|
210
|
+
this.setModHeld(event.type === 'keydown');
|
|
223
211
|
}
|
|
224
|
-
|
|
212
|
+
};
|
|
213
|
+
const lost = () => this.setModHeld(false);
|
|
214
|
+
window.addEventListener('keydown', keys);
|
|
215
|
+
window.addEventListener('keyup', keys);
|
|
216
|
+
window.addEventListener('blur', lost);
|
|
217
|
+
offs.push(() => {
|
|
218
|
+
window.removeEventListener('keydown', keys);
|
|
219
|
+
window.removeEventListener('keyup', keys);
|
|
220
|
+
window.removeEventListener('blur', lost);
|
|
221
|
+
});
|
|
225
222
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
223
|
+
language.onDispose(() => {
|
|
224
|
+
for (const off of offs) {
|
|
225
|
+
off();
|
|
226
|
+
}
|
|
227
|
+
this.cancelHover();
|
|
228
|
+
this.timers.clear(this.commandTimer);
|
|
229
|
+
this.refresher.dispose();
|
|
230
|
+
this.entries.clear();
|
|
231
|
+
editor.setInlayHints([]);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
/* The key that follows a place is down or up; it comes from the window, as the editor only says where the pointer is. */
|
|
235
|
+
setModHeld(held) {
|
|
236
|
+
if (held !== this.modHeld) {
|
|
237
|
+
this.modHeld = held;
|
|
238
|
+
this.updateLink();
|
|
237
239
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (this.cardOpen()) {
|
|
245
|
-
this.language.hover.release();
|
|
246
|
-
}
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
this.showTimer = this.timers.set(() => void this.showTooltip(target, request), SHOW_DELAY_MS);
|
|
250
|
-
// A press has to know what the hint offers when it comes, so the answer is asked for as the pointer arrives.
|
|
251
|
-
const entry = this.entries.get(target.id);
|
|
252
|
-
if (entry !== undefined && this.resolvable(entry)) {
|
|
253
|
-
void this.resolve(entry).then(() => this.updateLink());
|
|
254
|
-
}
|
|
240
|
+
}
|
|
241
|
+
/* The tooltip of a hint that sits at a position, as a card; false when there is none. This is how the keyboard reaches it. */
|
|
242
|
+
async showTooltipAt(position) {
|
|
243
|
+
const found = [...this.entries].find(([, entry]) => entry.epoch === this.epoch && sameSpot(entry.hint.position, position));
|
|
244
|
+
if (found === undefined) {
|
|
245
|
+
return false;
|
|
255
246
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
247
|
+
return this.showTooltip({
|
|
248
|
+
id: found[0],
|
|
249
|
+
part: null
|
|
250
|
+
}, this.request, false);
|
|
251
|
+
}
|
|
252
|
+
install(hints, epoch) {
|
|
253
|
+
this.generation++;
|
|
254
|
+
this.entries.clear();
|
|
255
|
+
this.shown = hints.map((hint, index) => {
|
|
256
|
+
const id = `hint-${this.generation}-${index}`;
|
|
257
|
+
this.entries.set(id, {
|
|
258
|
+
id,
|
|
259
|
+
hint,
|
|
260
|
+
epoch,
|
|
261
|
+
resolution: null,
|
|
262
|
+
answered: false
|
|
263
|
+
});
|
|
264
|
+
return editorHintOf(hint, id);
|
|
265
|
+
});
|
|
266
|
+
this.language.editor.setInlayHints(this.shown);
|
|
267
|
+
this.target = null;
|
|
268
|
+
this.updateLink();
|
|
269
|
+
}
|
|
270
|
+
hovered(target) {
|
|
271
|
+
this.target = target;
|
|
272
|
+
this.timers.clear(this.showTimer);
|
|
273
|
+
const request = ++this.request;
|
|
274
|
+
this.updateLink();
|
|
275
|
+
if (target === null) {
|
|
276
|
+
if (this.cardOpen()) {
|
|
277
|
+
this.language.hover.release();
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
264
280
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
281
|
+
this.showTimer = this.timers.set(() => void this.showTooltip(target, request), SHOW_DELAY_MS);
|
|
282
|
+
// A press has to know what the hint offers when it comes, so the answer is asked for as the pointer arrives.
|
|
283
|
+
const entry = this.entries.get(target.id);
|
|
284
|
+
if (entry !== undefined && this.resolvable(entry)) {
|
|
285
|
+
void this.resolve(entry).then(() => this.updateLink());
|
|
268
286
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
this.updateLink();
|
|
278
|
-
}
|
|
279
|
-
if ((pointer && request !== this.request) || entry.epoch !== this.epoch) {
|
|
280
|
-
return false;
|
|
281
|
-
}
|
|
282
|
-
const text = tooltipOf(entry.hint, target.part);
|
|
283
|
-
if (text === null) {
|
|
284
|
-
if (this.cardOpen()) {
|
|
285
|
-
this.language.hover.hide();
|
|
286
|
-
}
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
this.language.hover.showText(entry.hint.position, text);
|
|
290
|
-
return true;
|
|
287
|
+
}
|
|
288
|
+
cancelHover() {
|
|
289
|
+
this.timers.clear(this.showTimer);
|
|
290
|
+
this.request++;
|
|
291
|
+
this.target = null;
|
|
292
|
+
this.updateLink();
|
|
293
|
+
if (this.cardOpen()) {
|
|
294
|
+
this.language.hover.hide();
|
|
291
295
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
.resolveInlayHint(this.language.uri, entry.hint)
|
|
303
|
-
.then((resolved) => {
|
|
304
|
-
if (entry.epoch === this.epoch) {
|
|
305
|
-
entry.hint = { ...entry.hint, ...resolved, position: entry.hint.position };
|
|
306
|
-
}
|
|
307
|
-
return entry.hint;
|
|
308
|
-
})
|
|
309
|
-
.catch(() => entry.hint)
|
|
310
|
-
.finally(() => {
|
|
311
|
-
entry.answered = true;
|
|
312
|
-
});
|
|
313
|
-
return entry.resolution;
|
|
296
|
+
}
|
|
297
|
+
/* Whether the card up is a tooltip, which is the only kind that has no footer. */
|
|
298
|
+
cardOpen() {
|
|
299
|
+
return this.language.popups.getState().hover?.info?.plain === true;
|
|
300
|
+
}
|
|
301
|
+
/* The card for a hint or part, once the server has said what it can. Says whether it opened. */
|
|
302
|
+
async showTooltip(target, request, pointer = true) {
|
|
303
|
+
const entry = this.entries.get(target.id);
|
|
304
|
+
if (entry === undefined) {
|
|
305
|
+
return false;
|
|
314
306
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
307
|
+
if (lacksInfo(entry.hint, target.part)) {
|
|
308
|
+
await this.resolve(entry);
|
|
309
|
+
this.updateLink();
|
|
318
310
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const { target } = this;
|
|
322
|
-
const entry = target === null ? undefined : this.entries.get(target.id);
|
|
323
|
-
const part = entry === undefined || target === null || entry.epoch !== this.epoch ? null : partOf(entry.hint, target.part);
|
|
324
|
-
const link = part !== null && (part.command !== undefined || (this.modHeld && part.location !== undefined));
|
|
325
|
-
this.language.editor.setInlayLink?.(link ? target : null);
|
|
311
|
+
if (pointer && request !== this.request || entry.epoch !== this.epoch) {
|
|
312
|
+
return false;
|
|
326
313
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
this.timers.clear(this.showTimer);
|
|
334
|
-
const request = ++this.request;
|
|
335
|
-
const worth = tooltipOf(entry.hint, click.part) !== null || this.resolvable(entry);
|
|
336
|
-
if (worth) {
|
|
337
|
-
void this.showTooltip({ id: click.id, part: click.part }, request);
|
|
338
|
-
}
|
|
339
|
-
return worth;
|
|
340
|
-
}
|
|
341
|
-
if (click.alt || click.shift) {
|
|
342
|
-
return false;
|
|
343
|
-
}
|
|
344
|
-
this.timers.clear(this.commandTimer);
|
|
345
|
-
const press = ++this.pressCount;
|
|
346
|
-
if (this.resolvable(entry) && (click.clicks === 2 || actionOf(entry.hint, click) !== null)) {
|
|
347
|
-
// The server may still add edits, which would make this the first press of a double click.
|
|
348
|
-
void this.resolve(entry).then(() => {
|
|
349
|
-
if (press === this.pressCount) {
|
|
350
|
-
this.press(entry, click);
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
return true;
|
|
354
|
-
}
|
|
355
|
-
return this.press(entry, click);
|
|
314
|
+
const text = tooltipOf(entry.hint, target.part);
|
|
315
|
+
if (text === null) {
|
|
316
|
+
if (this.cardOpen()) {
|
|
317
|
+
this.language.hover.hide();
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
356
320
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
321
|
+
this.language.hover.showText(entry.hint.position, text);
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
/* Asks the server for more of a hint, once; what it says is kept only while the text is the one the hint was made for. */
|
|
325
|
+
resolve(entry) {
|
|
326
|
+
if (entry.resolution !== null) {
|
|
327
|
+
return entry.resolution;
|
|
328
|
+
}
|
|
329
|
+
const {
|
|
330
|
+
service
|
|
331
|
+
} = this.language.project;
|
|
332
|
+
if (entry.epoch !== this.epoch || !service.supports(RESOLVE_METHOD, this.language.uri)) {
|
|
333
|
+
return Promise.resolve(entry.hint);
|
|
334
|
+
}
|
|
335
|
+
entry.resolution = service.resolveInlayHint(this.language.uri, entry.hint).then(resolved => {
|
|
336
|
+
if (entry.epoch === this.epoch) {
|
|
337
|
+
entry.hint = {
|
|
338
|
+
...entry.hint,
|
|
339
|
+
...resolved,
|
|
340
|
+
position: entry.hint.position
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
return entry.hint;
|
|
344
|
+
}).catch(() => entry.hint).finally(() => {
|
|
345
|
+
entry.answered = true;
|
|
346
|
+
});
|
|
347
|
+
return entry.resolution;
|
|
348
|
+
}
|
|
349
|
+
/* Whether asking the server could still change what the hint offers. */
|
|
350
|
+
resolvable(entry) {
|
|
351
|
+
return !entry.answered && entry.epoch === this.epoch && this.language.project.service.supports(RESOLVE_METHOD, this.language.uri);
|
|
352
|
+
}
|
|
353
|
+
/* Underlines the part under the pointer when a press would do something with it: a command always, a place while the key is down. */
|
|
354
|
+
updateLink() {
|
|
355
|
+
const {
|
|
356
|
+
target
|
|
357
|
+
} = this;
|
|
358
|
+
const entry = target === null ? undefined : this.entries.get(target.id);
|
|
359
|
+
const part = entry === undefined || target === null || entry.epoch !== this.epoch ? null : partOf(entry.hint, target.part);
|
|
360
|
+
const link = part !== null && (part.command !== undefined || this.modHeld && part.location !== undefined);
|
|
361
|
+
this.language.editor.setInlayLink?.(link ? target : null);
|
|
362
|
+
}
|
|
363
|
+
pressed(click) {
|
|
364
|
+
const entry = this.entries.get(click.id);
|
|
365
|
+
if (entry === undefined || entry.epoch !== this.epoch) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
if (click.touch) {
|
|
369
|
+
this.timers.clear(this.showTimer);
|
|
370
|
+
const request = ++this.request;
|
|
371
|
+
const worth = tooltipOf(entry.hint, click.part) !== null || this.resolvable(entry);
|
|
372
|
+
if (worth) {
|
|
373
|
+
void this.showTooltip({
|
|
374
|
+
id: click.id,
|
|
375
|
+
part: click.part
|
|
376
|
+
}, request);
|
|
377
|
+
}
|
|
378
|
+
return worth;
|
|
379
|
+
}
|
|
380
|
+
if (click.alt || click.shift) {
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
this.timers.clear(this.commandTimer);
|
|
384
|
+
const press = ++this.pressCount;
|
|
385
|
+
if (this.resolvable(entry) && (click.clicks === 2 || actionOf(entry.hint, click) !== null)) {
|
|
386
|
+
// The server may still add edits, which would make this the first press of a double click.
|
|
387
|
+
void this.resolve(entry).then(() => {
|
|
388
|
+
if (press === this.pressCount) {
|
|
389
|
+
this.press(entry, click);
|
|
374
390
|
}
|
|
375
|
-
|
|
376
|
-
|
|
391
|
+
});
|
|
392
|
+
return true;
|
|
377
393
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const applied = editor.applyEdits(action.edits.map((edit) => ({ range: edit.range, text: edit.newText })));
|
|
391
|
-
if (applied) {
|
|
392
|
-
this.shown = this.shown.filter((hint) => hint.id !== entry.id);
|
|
393
|
-
editor.setInlayHints(this.shown);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
394
|
+
return this.press(entry, click);
|
|
395
|
+
}
|
|
396
|
+
press(entry, click) {
|
|
397
|
+
this.timers.clear(this.commandTimer);
|
|
398
|
+
const action = actionOf(entry.hint, click);
|
|
399
|
+
const insertable = entry.hint.textEdits !== undefined && entry.hint.textEdits.length > 0;
|
|
400
|
+
if (action?.kind === 'run' && insertable) {
|
|
401
|
+
// A double click inserts the edits, so the command of its first press waits to see whether a second one comes.
|
|
402
|
+
const epoch = this.epoch;
|
|
403
|
+
this.commandTimer = this.timers.set(() => {
|
|
404
|
+
if (epoch === this.epoch) {
|
|
405
|
+
void this.perform(entry, action);
|
|
396
406
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
407
|
+
}, DOUBLE_PRESS_MS);
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
if (action !== null) {
|
|
411
|
+
void this.perform(entry, action);
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
// The first press of a double click must not move the caret either, or the second would land beside the hint.
|
|
415
|
+
return click.clicks === 1 && !click.mod && insertable;
|
|
416
|
+
}
|
|
417
|
+
async perform(entry, action) {
|
|
418
|
+
const {
|
|
419
|
+
editor,
|
|
420
|
+
project,
|
|
421
|
+
uri
|
|
422
|
+
} = this.language;
|
|
423
|
+
try {
|
|
424
|
+
if (action.kind === 'go') {
|
|
425
|
+
this.cancelHover();
|
|
426
|
+
this.language.goTo(action.location);
|
|
427
|
+
} else if (action.kind === 'run') {
|
|
428
|
+
await project.service.executeCommand(uri, action.command);
|
|
429
|
+
} else if (entry.epoch === this.epoch && editsFit(editor, action.edits)) {
|
|
430
|
+
this.cancelHover();
|
|
431
|
+
const applied = editor.applyEdits(action.edits.map(edit => ({
|
|
432
|
+
range: edit.range,
|
|
433
|
+
text: edit.newText
|
|
434
|
+
})));
|
|
435
|
+
if (applied) {
|
|
436
|
+
this.shown = this.shown.filter(hint => hint.id !== entry.id);
|
|
437
|
+
editor.setInlayHints(this.shown);
|
|
403
438
|
}
|
|
439
|
+
}
|
|
440
|
+
} catch (error) {
|
|
441
|
+
this.language.project.host.notify?.({
|
|
442
|
+
id: TOAST_ID,
|
|
443
|
+
kind: 'error',
|
|
444
|
+
title: project.i18n.t('editor:language.inlay.failed', {
|
|
445
|
+
message: messageOf(error)
|
|
446
|
+
})
|
|
447
|
+
});
|
|
404
448
|
}
|
|
449
|
+
}
|
|
405
450
|
}
|
|
406
451
|
function sameSpot(left, right) {
|
|
407
|
-
|
|
408
|
-
}
|
|
452
|
+
return left.line === right.line && left.character === right.character;
|
|
453
|
+
}
|