@adecore/editor-react 0.39.0 → 0.40.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CallHierarchyPanel.js +30 -55
- package/dist/CodeAuthorsCard.js +0 -1
- package/dist/CompletionPopup.js +150 -181
- package/dist/DiffView.d.ts +1 -1
- package/dist/DiffView.js +2 -29
- package/dist/PickPopup.js +54 -80
- package/dist/SplitDiffView.js +27 -57
- package/dist/SymbolPicker.js +2 -8
- package/dist/code-actions-model.js +2 -9
- package/dist/code-actions.js +10 -11
- package/dist/code-vision.js +2 -1
- package/dist/completion-model.js +2 -2
- package/dist/completion.js +13 -34
- package/dist/definition-link.d.ts +1 -0
- package/dist/definition-link.js +22 -18
- package/dist/diagnostics-model.d.ts +1 -0
- package/dist/diagnostics-model.js +3 -1
- package/dist/diagnostics.js +1 -2
- package/dist/diff-syntax.d.ts +1 -0
- package/dist/diff-syntax.js +31 -0
- package/dist/doc-comment-model.d.ts +1 -0
- package/dist/doc-comment-model.js +1 -1
- package/dist/folding.js +2 -3
- package/dist/format-on-type.js +6 -10
- package/dist/hover-content.d.ts +0 -2
- package/dist/hover-content.js +1 -9
- package/dist/inlay-hints.d.ts +0 -1
- package/dist/inlay-hints.js +11 -41
- package/dist/navigation.js +4 -14
- package/dist/peek.js +2 -12
- package/dist/popup-focus.d.ts +3 -1
- package/dist/popup-focus.js +33 -0
- package/dist/recent-choices.d.ts +0 -1
- package/dist/recent-choices.js +0 -4
- package/dist/rendered-docs.d.ts +1 -0
- package/dist/rendered-docs.js +15 -28
- package/dist/snippet-field-transforms.d.ts +5 -0
- package/dist/snippet-field-transforms.js +27 -16
- package/dist/snippet-session.js +8 -20
- package/dist/symbol-links.js +3 -10
- package/dist/symbol-picker-model.d.ts +1 -0
- package/dist/symbol-picker-model.js +7 -0
- package/dist/unique.d.ts +10 -0
- package/dist/unique.js +15 -0
- package/package.json +9 -9
- package/src/AnchoredPopup.tsx +2 -0
- package/src/BaselineStatus.tsx +1 -0
- package/src/CallHierarchyPanel.tsx +16 -6
- package/src/ChangeReview.tsx +7 -0
- package/src/CodeAuthorsCard.tsx +2 -5
- package/src/CompletionPopup.tsx +21 -15
- package/src/DiffView.tsx +53 -30
- package/src/EditorContextMenu.tsx +7 -0
- package/src/EditorView.tsx +28 -0
- package/src/FindReplace.tsx +8 -0
- package/src/HoverCard.tsx +8 -0
- package/src/HoverSections.tsx +11 -0
- package/src/LanguagePopups.tsx +1 -0
- package/src/PanelResizeHandle.tsx +8 -0
- package/src/PathText.tsx +1 -0
- package/src/PeekPanel.tsx +13 -0
- package/src/PickPopup.tsx +7 -6
- package/src/RenameCard.tsx +13 -0
- package/src/RenderedDocs.tsx +11 -0
- package/src/SplitDiffView.tsx +91 -56
- package/src/SymbolPicker.tsx +10 -18
- package/src/call-hierarchy-model.ts +10 -0
- package/src/call-hierarchy.ts +82 -0
- package/src/code-actions-model.ts +21 -9
- package/src/code-actions.ts +83 -7
- package/src/code-authors.ts +20 -0
- package/src/code-vision-model.ts +37 -0
- package/src/code-vision.ts +47 -3
- package/src/completion-call.ts +19 -0
- package/src/completion-model.ts +58 -2
- package/src/completion.ts +156 -30
- package/src/conflict-model.ts +4 -0
- package/src/context-menu-rows.ts +1 -0
- package/src/context-menu.ts +7 -0
- package/src/definition-link.ts +42 -18
- package/src/diagnostics-model.ts +19 -1
- package/src/diagnostics.ts +31 -2
- package/src/diff-expansion.ts +9 -0
- package/src/diff-overlays.tsx +8 -0
- package/src/diff-syntax.ts +54 -0
- package/src/diff-wordmarks.ts +8 -0
- package/src/doc-comment-model.ts +51 -1
- package/src/editor-language.ts +11 -0
- package/src/find-query.ts +7 -0
- package/src/folding.ts +10 -3
- package/src/format-on-type.ts +23 -10
- package/src/highlight-layers.ts +3 -0
- package/src/highlights.ts +18 -0
- package/src/history.ts +11 -0
- package/src/holder.ts +3 -0
- package/src/hover-content.ts +34 -17
- package/src/hover.ts +57 -0
- package/src/index.ts +4 -0
- package/src/inlay-hints.ts +86 -47
- package/src/key-priority.ts +2 -0
- package/src/line-edits.ts +8 -0
- package/src/models.ts +6 -0
- package/src/navigation-history.ts +5 -0
- package/src/navigation.ts +28 -13
- package/src/panel-size.ts +7 -0
- package/src/patch-decorations.ts +39 -0
- package/src/patch-layout.ts +47 -0
- package/src/peek-model.ts +21 -0
- package/src/peek.ts +49 -13
- package/src/pick.ts +35 -0
- package/src/popup-focus.ts +11 -1
- package/src/popup-placement.ts +5 -0
- package/src/popups.ts +2 -6
- package/src/project-language.ts +33 -0
- package/src/project-problems.ts +11 -0
- package/src/proposal-model.ts +40 -0
- package/src/recent-choices.ts +3 -5
- package/src/refresher.ts +3 -0
- package/src/rename-model.ts +28 -0
- package/src/rename.ts +56 -0
- package/src/rendered-docs.ts +89 -19
- package/src/review-group.ts +4 -0
- package/src/row-host.ts +12 -0
- package/src/selection-ranges.ts +6 -0
- package/src/semantic-delta.ts +7 -0
- package/src/semantic-model.ts +11 -0
- package/src/semantic-tokens.ts +37 -0
- package/src/shortcut-keys.ts +3 -0
- package/src/signature-model.ts +7 -0
- package/src/signature.ts +27 -0
- package/src/snippet-field-transforms.ts +48 -13
- package/src/snippet-indent.ts +10 -0
- package/src/snippet-range-order.ts +5 -0
- package/src/snippet-session.ts +173 -31
- package/src/snippet-transform-client.ts +14 -0
- package/src/snippet-transform-format.ts +98 -0
- package/src/snippet-transform-protocol.ts +5 -0
- package/src/snippet-transform-queue.ts +53 -0
- package/src/snippet-transform-worker.ts +14 -0
- package/src/snippet-variables.ts +9 -0
- package/src/snippet.ts +107 -0
- package/src/split-alignment.ts +10 -0
- package/src/split-scroll.ts +8 -0
- package/src/symbol-links.ts +9 -12
- package/src/symbol-picker-model.ts +25 -0
- package/src/symbol-picker.ts +9 -0
- package/src/symbols.ts +21 -0
- package/src/testing.ts +7 -0
- package/src/timers.ts +5 -0
- package/src/unique.ts +23 -0
- package/src/use-problem-counts.ts +4 -0
- package/src/view-state.ts +9 -0
- package/src/word-diff.ts +5 -0
- package/src/workspace-edit.ts +84 -0
package/src/inlay-hints.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Editor, EditorInlayClick, EditorInlayHint, EditorPosition, EditorRange } from '@adecore/editor';
|
|
2
2
|
import type { Command, InlayHint, InlayHintLabelPart, Location, MarkupContent, TextEdit } from '@adecore/lsp';
|
|
3
|
+
import { followModKey } from './definition-link.ts';
|
|
4
|
+
import { comparePositions } from './diagnostics-model.ts';
|
|
3
5
|
import type { EditorLanguage } from './editor-language.ts';
|
|
4
6
|
import { hoverTextOf, isEmptyHover, type HoverText } from './hover-content.ts';
|
|
5
7
|
import { SHOW_DELAY_MS } from './hover.ts';
|
|
@@ -12,21 +14,19 @@ const METHOD = 'textDocument/inlayHint';
|
|
|
12
14
|
const RESOLVE_METHOD = 'inlayHint/resolve';
|
|
13
15
|
const TOAST_ID = 'language-inlay-hint';
|
|
14
16
|
const PAUSE_MS = 300;
|
|
17
|
+
|
|
15
18
|
/* How long a click on a hint that a double click would also act on waits for its second press; the editor's own interval for a double press. */
|
|
16
19
|
const DOUBLE_PRESS_MS = 500;
|
|
20
|
+
|
|
17
21
|
/* Lines asked for above and below what is in view, so a little scrolling stays inside what is already drawn. */
|
|
18
22
|
const MARGIN_LINES = 60;
|
|
19
23
|
|
|
20
|
-
/* A hint's label is a string or a list of parts, which are read as one text. */
|
|
21
24
|
export function hintLabelOf(hint: InlayHint): string {
|
|
22
25
|
return typeof hint.label === 'string' ? hint.label : hint.label.map((part) => part.value).join('');
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
/* The hints worth drawing, in the order of the text. */
|
|
26
28
|
export function drawableHints(hints: readonly InlayHint[]): InlayHint[] {
|
|
27
|
-
return hints
|
|
28
|
-
.filter((hint) => hintLabelOf(hint) !== '')
|
|
29
|
-
.sort((left, right) => left.position.line - right.position.line || left.position.character - right.position.character);
|
|
29
|
+
return hints.filter((hint) => hintLabelOf(hint) !== '').sort((left, right) => comparePositions(left.position, right.position));
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/* A hint as the editor draws it: a label with parts keeps them apart so each can be pointed at. */
|
|
@@ -41,18 +41,13 @@ export function editorHintOf(hint: InlayHint, id: string): EditorInlayHint {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export function editorHintsOf(hints: readonly InlayHint[]): EditorInlayHint[] {
|
|
45
|
-
return drawableHints(hints).map((hint, index) => editorHintOf(hint, `hint-${index}`));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/* The lines in view with a margin of lines around them, from the start of the first to the end of the last. */
|
|
49
44
|
export function withMargin(visible: EditorRange, endOfDocument: EditorPosition, endOfLine: (line: number) => EditorPosition): EditorRange {
|
|
50
45
|
const first = Math.max(0, visible.start.line - MARGIN_LINES);
|
|
51
46
|
const last = Math.min(endOfDocument.line, visible.end.line + MARGIN_LINES);
|
|
47
|
+
|
|
52
48
|
return { start: { line: first, character: 0 }, end: last === endOfDocument.line ? endOfDocument : endOfLine(last) };
|
|
53
49
|
}
|
|
54
50
|
|
|
55
|
-
/* Whether what was asked for already holds every line in view. */
|
|
56
51
|
export function covers(asked: EditorRange, visible: EditorRange): boolean {
|
|
57
52
|
return asked.start.line <= visible.start.line && asked.end.line >= visible.end.line;
|
|
58
53
|
}
|
|
@@ -62,11 +57,12 @@ export function tooltipTextOf(tooltip: string | MarkupContent | undefined): Hove
|
|
|
62
57
|
if (tooltip === undefined) {
|
|
63
58
|
return null;
|
|
64
59
|
}
|
|
60
|
+
|
|
65
61
|
const text = hoverTextOf({ contents: typeof tooltip === 'string' ? { kind: 'plaintext', value: tooltip } : tooltip });
|
|
62
|
+
|
|
66
63
|
return isEmptyHover(text) ? null : text;
|
|
67
64
|
}
|
|
68
65
|
|
|
69
|
-
/* One part of a hint's label, or null for a hint whose label is a string. */
|
|
70
66
|
export function partOf(hint: InlayHint, index: number | null): InlayHintLabelPart | null {
|
|
71
67
|
return index === null || typeof hint.label === 'string' ? null : (hint.label[index] ?? null);
|
|
72
68
|
}
|
|
@@ -75,9 +71,11 @@ export function partOf(hint: InlayHint, index: number | null): InlayHintLabelPar
|
|
|
75
71
|
export function tooltipOf(hint: InlayHint, index: number | null): HoverText | null {
|
|
76
72
|
const part = partOf(hint, index);
|
|
77
73
|
const own = part?.tooltip ?? hint.tooltip;
|
|
74
|
+
|
|
78
75
|
if (own !== undefined || index !== null || typeof hint.label === 'string') {
|
|
79
76
|
return tooltipTextOf(own);
|
|
80
77
|
}
|
|
78
|
+
|
|
81
79
|
return tooltipTextOf(hint.label.find((candidate) => candidate.tooltip !== undefined)?.tooltip);
|
|
82
80
|
}
|
|
83
81
|
|
|
@@ -86,13 +84,16 @@ export function lacksInfo(hint: InlayHint, index: number | null): boolean {
|
|
|
86
84
|
if (hint.tooltip !== undefined) {
|
|
87
85
|
return false;
|
|
88
86
|
}
|
|
87
|
+
|
|
89
88
|
const parts = typeof hint.label === 'string' ? [] : index === null ? hint.label : [hint.label[index]];
|
|
89
|
+
|
|
90
90
|
return !parts.some((part) => part !== undefined && (part.tooltip !== undefined || part.location !== undefined || part.command !== undefined));
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/* Whether the edits fit the text as it is now: positions inside it, each range in order, none overlapping the next. */
|
|
94
94
|
export function editsFit(editor: Editor, edits: readonly TextEdit[]): boolean {
|
|
95
95
|
const end = editor.positionAt(Number.MAX_SAFE_INTEGER);
|
|
96
|
+
|
|
96
97
|
const inside = (position: EditorPosition): boolean =>
|
|
97
98
|
Number.isInteger(position.line) &&
|
|
98
99
|
Number.isInteger(position.character) &&
|
|
@@ -100,11 +101,14 @@ export function editsFit(editor: Editor, edits: readonly TextEdit[]): boolean {
|
|
|
100
101
|
position.character >= 0 &&
|
|
101
102
|
position.line <= end.line &&
|
|
102
103
|
position.character <= lineTextOf(editor, position.line).length;
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
|
|
105
|
+
const before = (left: EditorPosition, right: EditorPosition): boolean => comparePositions(left, right) <= 0;
|
|
106
|
+
|
|
105
107
|
const ordered = [...edits].sort((left, right) => (before(left.range.start, right.range.start) ? -1 : 1));
|
|
108
|
+
|
|
106
109
|
return ordered.every((edit, index) => {
|
|
107
110
|
const next = ordered[index + 1];
|
|
111
|
+
|
|
108
112
|
return (
|
|
109
113
|
inside(edit.range.start) &&
|
|
110
114
|
inside(edit.range.end) &&
|
|
@@ -124,14 +128,16 @@ export function actionOf(hint: InlayHint, click: Pick<EditorInlayClick, 'part' |
|
|
|
124
128
|
if (click.clicks === 2) {
|
|
125
129
|
return hint.textEdits !== undefined && hint.textEdits.length > 0 ? { kind: 'insert', edits: hint.textEdits } : null;
|
|
126
130
|
}
|
|
131
|
+
|
|
127
132
|
const part = partOf(hint, click.part);
|
|
133
|
+
|
|
128
134
|
if (click.mod) {
|
|
129
135
|
return part?.location === undefined ? null : { kind: 'go', location: part.location };
|
|
130
136
|
}
|
|
137
|
+
|
|
131
138
|
return part?.command === undefined ? null : { kind: 'run', command: part.command };
|
|
132
139
|
}
|
|
133
140
|
|
|
134
|
-
/* A hint on screen and what has become known of it. */
|
|
135
141
|
interface Entry {
|
|
136
142
|
readonly id: string;
|
|
137
143
|
hint: InlayHint;
|
|
@@ -144,36 +150,38 @@ interface Entry {
|
|
|
144
150
|
|
|
145
151
|
/*
|
|
146
152
|
* Types and parameter names the servers infer, asked for the lines in view and a margin around them. The
|
|
147
|
-
* last ones stay until the answer comes, so nothing blinks while typing
|
|
148
|
-
* view leaves the lines that were asked for.
|
|
153
|
+
* last ones stay until the answer comes, so nothing blinks while typing.
|
|
149
154
|
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* offers nothing (yet) moves the caret as beside the text. A finger has no hover, so a tap shows the
|
|
155
|
-
* tooltip and does nothing more. All of it is for the pointer: the hints are not part
|
|
156
|
-
* of the text and stay out of the keyboard's way, and Quick Info on the caret shows the tooltip of a hint that
|
|
157
|
-
* sits there.
|
|
155
|
+
* Mod+click on a part follows its place, a click runs its command and a double click inserts its text edits
|
|
156
|
+
* as one undo step, never together with the command. A press on a hint that offers nothing (yet) moves the
|
|
157
|
+
* caret; a tap only shows the tooltip. The hints stay out of the keyboard's way: Quick Info on the caret
|
|
158
|
+
* shows the tooltip of a hint that sits there.
|
|
158
159
|
*/
|
|
159
160
|
export class InlayHintsFeature {
|
|
160
161
|
private readonly language: EditorLanguage;
|
|
161
162
|
private readonly timers: Timers;
|
|
162
163
|
private readonly refresher: Refresher;
|
|
164
|
+
|
|
163
165
|
/* The lines the hints on screen were asked for. */
|
|
164
166
|
private asked: EditorRange | null = null;
|
|
167
|
+
|
|
165
168
|
private readonly entries = new Map<string, Entry>();
|
|
166
169
|
private shown: EditorInlayHint[] = [];
|
|
167
170
|
private generation = 0;
|
|
171
|
+
|
|
168
172
|
/* Counts the edits of the text, which hints and answers made for an earlier one are tested against. */
|
|
169
173
|
private epoch = 0;
|
|
174
|
+
|
|
170
175
|
private target: { id: string; part: number | null } | null = null;
|
|
171
176
|
private modHeld = false;
|
|
172
177
|
private showTimer: unknown;
|
|
178
|
+
|
|
173
179
|
/* The command of a click that may still become a double click. */
|
|
174
180
|
private commandTimer: unknown;
|
|
181
|
+
|
|
175
182
|
/* Counts presses, so one that waited for a resolve gives way to a later one. */
|
|
176
183
|
private pressCount = 0;
|
|
184
|
+
|
|
177
185
|
/* Counts what the pointer does, so a tooltip that was waited for is dropped once the pointer has moved on. */
|
|
178
186
|
private request = 0;
|
|
179
187
|
|
|
@@ -181,19 +189,24 @@ export class InlayHintsFeature {
|
|
|
181
189
|
this.language = language;
|
|
182
190
|
this.timers = timers;
|
|
183
191
|
const { editor, project, uri } = language;
|
|
192
|
+
|
|
184
193
|
this.refresher = new Refresher(
|
|
185
194
|
async (signal) => {
|
|
186
195
|
const { service } = project;
|
|
196
|
+
|
|
187
197
|
if (!service.supports(METHOD, uri)) {
|
|
188
198
|
return;
|
|
189
199
|
}
|
|
200
|
+
|
|
190
201
|
const range = withMargin(editor.getVisibleRange(), editor.positionAt(Number.MAX_SAFE_INTEGER), (line) => ({
|
|
191
202
|
line,
|
|
192
203
|
character: lineTextOf(editor, line).length
|
|
193
204
|
}));
|
|
205
|
+
|
|
194
206
|
this.asked = range;
|
|
195
207
|
const epoch = this.epoch;
|
|
196
208
|
const hints = await service.inlayHints(uri, range, { signal });
|
|
209
|
+
|
|
197
210
|
if (!signal.aborted && epoch === this.epoch) {
|
|
198
211
|
this.install(hints === null ? [] : drawableHints(hints), epoch);
|
|
199
212
|
}
|
|
@@ -201,6 +214,7 @@ export class InlayHintsFeature {
|
|
|
201
214
|
PAUSE_MS,
|
|
202
215
|
timers
|
|
203
216
|
);
|
|
217
|
+
|
|
204
218
|
const offs = [
|
|
205
219
|
editor.onTextChange(() => {
|
|
206
220
|
this.epoch++;
|
|
@@ -216,33 +230,22 @@ export class InlayHintsFeature {
|
|
|
216
230
|
this.refresher.now();
|
|
217
231
|
})
|
|
218
232
|
];
|
|
233
|
+
|
|
219
234
|
if (editor.onInlayHover !== undefined) {
|
|
220
235
|
offs.push(editor.onInlayHover((target) => this.hovered(target === null ? null : { id: target.id, part: target.part })));
|
|
221
236
|
}
|
|
237
|
+
|
|
222
238
|
if (editor.onInlayClick !== undefined) {
|
|
223
239
|
offs.push(editor.onInlayClick((click) => this.pressed(click)));
|
|
224
240
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (event.key === modKey) {
|
|
229
|
-
this.setModHeld(event.type === 'keydown');
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
const lost = (): void => this.setModHeld(false);
|
|
233
|
-
window.addEventListener('keydown', keys);
|
|
234
|
-
window.addEventListener('keyup', keys);
|
|
235
|
-
window.addEventListener('blur', lost);
|
|
236
|
-
offs.push(() => {
|
|
237
|
-
window.removeEventListener('keydown', keys);
|
|
238
|
-
window.removeEventListener('keyup', keys);
|
|
239
|
-
window.removeEventListener('blur', lost);
|
|
240
|
-
});
|
|
241
|
-
}
|
|
241
|
+
|
|
242
|
+
offs.push(followModKey(language.apple, (held) => this.setModHeld(held)));
|
|
243
|
+
|
|
242
244
|
language.onDispose(() => {
|
|
243
245
|
for (const off of offs) {
|
|
244
246
|
off();
|
|
245
247
|
}
|
|
248
|
+
|
|
246
249
|
this.cancelHover();
|
|
247
250
|
this.timers.clear(this.commandTimer);
|
|
248
251
|
this.refresher.dispose();
|
|
@@ -261,21 +264,26 @@ export class InlayHintsFeature {
|
|
|
261
264
|
|
|
262
265
|
/* 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. */
|
|
263
266
|
async showTooltipAt(position: EditorPosition): Promise<boolean> {
|
|
264
|
-
const found = [...this.entries].find(([, entry]) => entry.epoch === this.epoch &&
|
|
267
|
+
const found = [...this.entries].find(([, entry]) => entry.epoch === this.epoch && comparePositions(entry.hint.position, position) === 0);
|
|
268
|
+
|
|
265
269
|
if (found === undefined) {
|
|
266
270
|
return false;
|
|
267
271
|
}
|
|
272
|
+
|
|
268
273
|
return this.showTooltip({ id: found[0], part: null }, this.request, false);
|
|
269
274
|
}
|
|
270
275
|
|
|
271
276
|
private install(hints: readonly InlayHint[], epoch: number): void {
|
|
272
277
|
this.generation++;
|
|
273
278
|
this.entries.clear();
|
|
279
|
+
|
|
274
280
|
this.shown = hints.map((hint, index) => {
|
|
275
281
|
const id = `hint-${this.generation}-${index}`;
|
|
276
282
|
this.entries.set(id, { id, hint, epoch, resolution: null, answered: false });
|
|
283
|
+
|
|
277
284
|
return editorHintOf(hint, id);
|
|
278
285
|
});
|
|
286
|
+
|
|
279
287
|
this.language.editor.setInlayHints(this.shown);
|
|
280
288
|
this.target = null;
|
|
281
289
|
this.updateLink();
|
|
@@ -286,15 +294,19 @@ export class InlayHintsFeature {
|
|
|
286
294
|
this.timers.clear(this.showTimer);
|
|
287
295
|
const request = ++this.request;
|
|
288
296
|
this.updateLink();
|
|
297
|
+
|
|
289
298
|
if (target === null) {
|
|
290
299
|
if (this.cardOpen()) {
|
|
291
300
|
this.language.hover.release();
|
|
292
301
|
}
|
|
302
|
+
|
|
293
303
|
return;
|
|
294
304
|
}
|
|
305
|
+
|
|
295
306
|
this.showTimer = this.timers.set(() => void this.showTooltip(target, request), SHOW_DELAY_MS);
|
|
296
307
|
// A press has to know what the hint offers when it comes, so the answer is asked for as the pointer arrives.
|
|
297
308
|
const entry = this.entries.get(target.id);
|
|
309
|
+
|
|
298
310
|
if (entry !== undefined && this.resolvable(entry)) {
|
|
299
311
|
void this.resolve(entry).then(() => this.updateLink());
|
|
300
312
|
}
|
|
@@ -305,6 +317,7 @@ export class InlayHintsFeature {
|
|
|
305
317
|
this.request++;
|
|
306
318
|
this.target = null;
|
|
307
319
|
this.updateLink();
|
|
320
|
+
|
|
308
321
|
if (this.cardOpen()) {
|
|
309
322
|
this.language.hover.hide();
|
|
310
323
|
}
|
|
@@ -318,24 +331,32 @@ export class InlayHintsFeature {
|
|
|
318
331
|
/* The card for a hint or part, once the server has said what it can. Says whether it opened. */
|
|
319
332
|
private async showTooltip(target: { id: string; part: number | null }, request: number, pointer = true): Promise<boolean> {
|
|
320
333
|
const entry = this.entries.get(target.id);
|
|
334
|
+
|
|
321
335
|
if (entry === undefined) {
|
|
322
336
|
return false;
|
|
323
337
|
}
|
|
338
|
+
|
|
324
339
|
if (lacksInfo(entry.hint, target.part)) {
|
|
325
340
|
await this.resolve(entry);
|
|
326
341
|
this.updateLink();
|
|
327
342
|
}
|
|
343
|
+
|
|
328
344
|
if ((pointer && request !== this.request) || entry.epoch !== this.epoch) {
|
|
329
345
|
return false;
|
|
330
346
|
}
|
|
347
|
+
|
|
331
348
|
const text = tooltipOf(entry.hint, target.part);
|
|
349
|
+
|
|
332
350
|
if (text === null) {
|
|
333
351
|
if (this.cardOpen()) {
|
|
334
352
|
this.language.hover.hide();
|
|
335
353
|
}
|
|
354
|
+
|
|
336
355
|
return false;
|
|
337
356
|
}
|
|
357
|
+
|
|
338
358
|
this.language.hover.showText(entry.hint.position, text);
|
|
359
|
+
|
|
339
360
|
return true;
|
|
340
361
|
}
|
|
341
362
|
|
|
@@ -344,22 +365,27 @@ export class InlayHintsFeature {
|
|
|
344
365
|
if (entry.resolution !== null) {
|
|
345
366
|
return entry.resolution;
|
|
346
367
|
}
|
|
368
|
+
|
|
347
369
|
const { service } = this.language.project;
|
|
370
|
+
|
|
348
371
|
if (entry.epoch !== this.epoch || !service.supports(RESOLVE_METHOD, this.language.uri)) {
|
|
349
372
|
return Promise.resolve(entry.hint);
|
|
350
373
|
}
|
|
374
|
+
|
|
351
375
|
entry.resolution = service
|
|
352
376
|
.resolveInlayHint(this.language.uri, entry.hint)
|
|
353
377
|
.then((resolved) => {
|
|
354
378
|
if (entry.epoch === this.epoch) {
|
|
355
379
|
entry.hint = { ...entry.hint, ...resolved, position: entry.hint.position };
|
|
356
380
|
}
|
|
381
|
+
|
|
357
382
|
return entry.hint;
|
|
358
383
|
})
|
|
359
384
|
.catch(() => entry.hint)
|
|
360
385
|
.finally(() => {
|
|
361
386
|
entry.answered = true;
|
|
362
387
|
});
|
|
388
|
+
|
|
363
389
|
return entry.resolution;
|
|
364
390
|
}
|
|
365
391
|
|
|
@@ -379,23 +405,30 @@ export class InlayHintsFeature {
|
|
|
379
405
|
|
|
380
406
|
private pressed(click: EditorInlayClick): boolean {
|
|
381
407
|
const entry = this.entries.get(click.id);
|
|
408
|
+
|
|
382
409
|
if (entry === undefined || entry.epoch !== this.epoch) {
|
|
383
410
|
return false;
|
|
384
411
|
}
|
|
412
|
+
|
|
385
413
|
if (click.touch) {
|
|
386
414
|
this.timers.clear(this.showTimer);
|
|
387
415
|
const request = ++this.request;
|
|
388
416
|
const worth = tooltipOf(entry.hint, click.part) !== null || this.resolvable(entry);
|
|
417
|
+
|
|
389
418
|
if (worth) {
|
|
390
419
|
void this.showTooltip({ id: click.id, part: click.part }, request);
|
|
391
420
|
}
|
|
421
|
+
|
|
392
422
|
return worth;
|
|
393
423
|
}
|
|
424
|
+
|
|
394
425
|
if (click.alt || click.shift) {
|
|
395
426
|
return false;
|
|
396
427
|
}
|
|
428
|
+
|
|
397
429
|
this.timers.clear(this.commandTimer);
|
|
398
430
|
const press = ++this.pressCount;
|
|
431
|
+
|
|
399
432
|
if (this.resolvable(entry) && (click.clicks === 2 || actionOf(entry.hint, click) !== null)) {
|
|
400
433
|
// The server may still add edits, which would make this the first press of a double click.
|
|
401
434
|
void this.resolve(entry).then(() => {
|
|
@@ -403,8 +436,10 @@ export class InlayHintsFeature {
|
|
|
403
436
|
this.press(entry, click);
|
|
404
437
|
}
|
|
405
438
|
});
|
|
439
|
+
|
|
406
440
|
return true;
|
|
407
441
|
}
|
|
442
|
+
|
|
408
443
|
return this.press(entry, click);
|
|
409
444
|
}
|
|
410
445
|
|
|
@@ -412,26 +447,33 @@ export class InlayHintsFeature {
|
|
|
412
447
|
this.timers.clear(this.commandTimer);
|
|
413
448
|
const action = actionOf(entry.hint, click);
|
|
414
449
|
const insertable = entry.hint.textEdits !== undefined && entry.hint.textEdits.length > 0;
|
|
450
|
+
|
|
415
451
|
if (action?.kind === 'run' && insertable) {
|
|
416
452
|
// A double click inserts the edits, so the command of its first press waits to see whether a second one comes.
|
|
417
453
|
const epoch = this.epoch;
|
|
454
|
+
|
|
418
455
|
this.commandTimer = this.timers.set(() => {
|
|
419
456
|
if (epoch === this.epoch) {
|
|
420
457
|
void this.perform(entry, action);
|
|
421
458
|
}
|
|
422
459
|
}, DOUBLE_PRESS_MS);
|
|
460
|
+
|
|
423
461
|
return true;
|
|
424
462
|
}
|
|
463
|
+
|
|
425
464
|
if (action !== null) {
|
|
426
465
|
void this.perform(entry, action);
|
|
466
|
+
|
|
427
467
|
return true;
|
|
428
468
|
}
|
|
469
|
+
|
|
429
470
|
// The first press of a double click must not move the caret either, or the second would land beside the hint.
|
|
430
471
|
return click.clicks === 1 && !click.mod && insertable;
|
|
431
472
|
}
|
|
432
473
|
|
|
433
474
|
private async perform(entry: Entry, action: Action): Promise<void> {
|
|
434
475
|
const { editor, project, uri } = this.language;
|
|
476
|
+
|
|
435
477
|
try {
|
|
436
478
|
if (action.kind === 'go') {
|
|
437
479
|
this.cancelHover();
|
|
@@ -441,6 +483,7 @@ export class InlayHintsFeature {
|
|
|
441
483
|
} else if (entry.epoch === this.epoch && editsFit(editor, action.edits)) {
|
|
442
484
|
this.cancelHover();
|
|
443
485
|
const applied = editor.applyEdits(action.edits.map((edit) => ({ range: edit.range, text: edit.newText })));
|
|
486
|
+
|
|
444
487
|
if (applied) {
|
|
445
488
|
this.shown = this.shown.filter((hint) => hint.id !== entry.id);
|
|
446
489
|
editor.setInlayHints(this.shown);
|
|
@@ -455,7 +498,3 @@ export class InlayHintsFeature {
|
|
|
455
498
|
}
|
|
456
499
|
}
|
|
457
500
|
}
|
|
458
|
-
|
|
459
|
-
function sameSpot(left: EditorPosition, right: EditorPosition): boolean {
|
|
460
|
-
return left.line === right.line && left.character === right.character;
|
|
461
|
-
}
|
package/src/key-priority.ts
CHANGED
|
@@ -35,8 +35,10 @@ export class KeyDispatcher {
|
|
|
35
35
|
const entry = { handler, priority };
|
|
36
36
|
const index = this.entries.findIndex((other) => other.priority < priority);
|
|
37
37
|
this.entries.splice(index === -1 ? this.entries.length : index, 0, entry);
|
|
38
|
+
|
|
38
39
|
return () => {
|
|
39
40
|
const at = this.entries.indexOf(entry);
|
|
41
|
+
|
|
40
42
|
if (at !== -1) {
|
|
41
43
|
this.entries.splice(at, 1);
|
|
42
44
|
}
|
package/src/line-edits.ts
CHANGED
|
@@ -19,16 +19,21 @@ export function replaceLines(text: string, replacement: LineReplacement): Editor
|
|
|
19
19
|
const raw = text.split('\n');
|
|
20
20
|
const count = splitLines(text).length;
|
|
21
21
|
const body = lines.join(eol);
|
|
22
|
+
|
|
22
23
|
if (to < count || text === '' || text.endsWith('\n')) {
|
|
23
24
|
return { range: { start: { line: from, character: 0 }, end: { line: to, character: 0 } }, text: lines.length === 0 ? '' : `${body}${eol}` };
|
|
24
25
|
}
|
|
26
|
+
|
|
25
27
|
const end = { line: count - 1, character: raw[count - 1]!.length };
|
|
28
|
+
|
|
26
29
|
if (from === count) {
|
|
27
30
|
return { range: { start: end, end }, text: lines.length === 0 ? '' : `${eol}${body}` };
|
|
28
31
|
}
|
|
32
|
+
|
|
29
33
|
if (lines.length === 0 && from > 0) {
|
|
30
34
|
return { range: { start: { line: from - 1, character: raw[from - 1]!.length }, end }, text: '' };
|
|
31
35
|
}
|
|
36
|
+
|
|
32
37
|
return { range: { start: { line: from, character: 0 }, end }, text: body };
|
|
33
38
|
}
|
|
34
39
|
|
|
@@ -36,13 +41,16 @@ export function replaceLines(text: string, replacement: LineReplacement): Editor
|
|
|
36
41
|
export function replaceAllLines(text: string, replacements: readonly LineReplacement[]): EditorContentChange[] {
|
|
37
42
|
const ordered = [...replacements].sort((left, right) => left.from - right.from);
|
|
38
43
|
const merged: LineReplacement[] = [];
|
|
44
|
+
|
|
39
45
|
for (const next of ordered) {
|
|
40
46
|
const last = merged.at(-1);
|
|
47
|
+
|
|
41
48
|
if (last !== undefined && last.to >= next.from) {
|
|
42
49
|
merged[merged.length - 1] = { from: last.from, to: Math.max(last.to, next.to), lines: [...last.lines, ...next.lines] };
|
|
43
50
|
} else {
|
|
44
51
|
merged.push(next);
|
|
45
52
|
}
|
|
46
53
|
}
|
|
54
|
+
|
|
47
55
|
return merged.map((replacement) => replaceLines(text, replacement));
|
|
48
56
|
}
|
package/src/models.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { type ActionGroup, type ActionEntry, type EditPreview } from './code-act
|
|
|
12
12
|
export { type ResourceOperationView } from './workspace-edit.ts';
|
|
13
13
|
export { type CodeVisionDeclaration, type DeclarationSource } from './code-vision-model.ts';
|
|
14
14
|
export { type CodeAuthor, type CodeAuthorship } from './code-authors.ts';
|
|
15
|
+
|
|
15
16
|
export {
|
|
16
17
|
type SnippetTransform,
|
|
17
18
|
type SnippetStop,
|
|
@@ -24,6 +25,7 @@ export {
|
|
|
24
25
|
parseSnippet,
|
|
25
26
|
tabOrder
|
|
26
27
|
} from './snippet.ts';
|
|
28
|
+
|
|
27
29
|
export {
|
|
28
30
|
type LineSpan,
|
|
29
31
|
lineSpanOf,
|
|
@@ -40,8 +42,10 @@ export {
|
|
|
40
42
|
replaceRange,
|
|
41
43
|
locateSelection
|
|
42
44
|
} from './proposal-model.ts';
|
|
45
|
+
|
|
43
46
|
export { type LineReplacement, replaceLines, replaceAllLines } from './line-edits.ts';
|
|
44
47
|
export { replacedWords } from './word-diff.ts';
|
|
48
|
+
|
|
45
49
|
export {
|
|
46
50
|
type FindOptions,
|
|
47
51
|
type FindQuery,
|
|
@@ -53,8 +57,10 @@ export {
|
|
|
53
57
|
matchesIn,
|
|
54
58
|
stepIndex
|
|
55
59
|
} from './find-query.ts';
|
|
60
|
+
|
|
56
61
|
export { type PopupSize, type PopupPlacement, type PlaceOptions } from './popup-placement.ts';
|
|
57
62
|
export { planConflict, type ConflictStretch, type ConflictPlan } from './conflict-model.ts';
|
|
63
|
+
|
|
58
64
|
export {
|
|
59
65
|
patchLayoutOf,
|
|
60
66
|
type PatchLayout,
|
|
@@ -27,10 +27,12 @@ export class NavigationHistory {
|
|
|
27
27
|
this.backs[this.backs.length - 1] = from;
|
|
28
28
|
} else {
|
|
29
29
|
this.backs.push(from);
|
|
30
|
+
|
|
30
31
|
if (this.backs.length > LIMIT) {
|
|
31
32
|
this.backs.shift();
|
|
32
33
|
}
|
|
33
34
|
}
|
|
35
|
+
|
|
34
36
|
this.forwards = [];
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -59,11 +61,14 @@ export class NavigationHistory {
|
|
|
59
61
|
private walk(from: Place[], to: Place[], current: Place): Place | null {
|
|
60
62
|
while (from.length > 0) {
|
|
61
63
|
const place = from.pop()!;
|
|
64
|
+
|
|
62
65
|
if (!samePlace(place, current)) {
|
|
63
66
|
to.push(current);
|
|
67
|
+
|
|
64
68
|
return place;
|
|
65
69
|
}
|
|
66
70
|
}
|
|
71
|
+
|
|
67
72
|
return null;
|
|
68
73
|
}
|
|
69
74
|
}
|