@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/history.ts
CHANGED
|
@@ -9,21 +9,29 @@ export class HistoryFeature {
|
|
|
9
9
|
|
|
10
10
|
constructor(language: EditorLanguage) {
|
|
11
11
|
this.language = language;
|
|
12
|
+
|
|
12
13
|
const off = language.onKey((event) => {
|
|
13
14
|
if (isShortcut(this.language.shortcuts.historyBack, event)) {
|
|
14
15
|
this.back();
|
|
16
|
+
|
|
15
17
|
return true;
|
|
16
18
|
}
|
|
19
|
+
|
|
17
20
|
if (isShortcut(this.language.shortcuts.historyForward, event)) {
|
|
18
21
|
this.forward();
|
|
22
|
+
|
|
19
23
|
return true;
|
|
20
24
|
}
|
|
25
|
+
|
|
21
26
|
if (isShortcut(this.language.shortcuts.recentLocations, event)) {
|
|
22
27
|
this.recent();
|
|
28
|
+
|
|
23
29
|
return true;
|
|
24
30
|
}
|
|
31
|
+
|
|
25
32
|
return false;
|
|
26
33
|
}, KEY_PRIORITY.history);
|
|
34
|
+
|
|
27
35
|
language.onDispose(off);
|
|
28
36
|
}
|
|
29
37
|
|
|
@@ -39,6 +47,7 @@ export class HistoryFeature {
|
|
|
39
47
|
/* Lists the places of the history under the caret; choosing one jumps there like any other jump. */
|
|
40
48
|
recent(): void {
|
|
41
49
|
const places = this.language.project.history.recent(this.language.place);
|
|
50
|
+
|
|
42
51
|
this.language.locations(
|
|
43
52
|
places.map((place) => ({ uri: place.uri, range: { start: place.position, end: place.position } })),
|
|
44
53
|
this.language.editor.getCaret(),
|
|
@@ -50,7 +59,9 @@ export class HistoryFeature {
|
|
|
50
59
|
if (place === null) {
|
|
51
60
|
return false;
|
|
52
61
|
}
|
|
62
|
+
|
|
53
63
|
this.language.visit(place);
|
|
64
|
+
|
|
54
65
|
return true;
|
|
55
66
|
}
|
|
56
67
|
}
|
package/src/holder.ts
CHANGED
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
export function createHolder<T>(): { get(): T | null; set(value: T | null): void; subscribe(listener: () => void): () => void } {
|
|
3
3
|
let value: T | null = null;
|
|
4
4
|
const listeners = new Set<() => void>();
|
|
5
|
+
|
|
5
6
|
return {
|
|
6
7
|
get: () => value,
|
|
7
8
|
set: (next) => {
|
|
8
9
|
value = next;
|
|
10
|
+
|
|
9
11
|
for (const listener of [...listeners]) {
|
|
10
12
|
listener();
|
|
11
13
|
}
|
|
12
14
|
},
|
|
13
15
|
subscribe: (listener) => {
|
|
14
16
|
listeners.add(listener);
|
|
17
|
+
|
|
15
18
|
return () => {
|
|
16
19
|
listeners.delete(listener);
|
|
17
20
|
};
|
package/src/hover-content.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { EditorRange } from '@adecore/editor';
|
|
2
1
|
import type { Hover, Location, LocationLink, MarkedString, NavigationResult } from '@adecore/lsp';
|
|
2
|
+
import { dedent } from './doc-comment-model.ts';
|
|
3
3
|
|
|
4
4
|
export interface SignatureBlock {
|
|
5
5
|
readonly language: string;
|
|
@@ -22,10 +22,12 @@ const FENCE = /^```([\w+#.-]*)[^\n]*\n([\s\S]*?)\n```[ \t]*(?:\n|$)/;
|
|
|
22
22
|
export function splitSignatures(markdown: string): HoverText {
|
|
23
23
|
const signatures: SignatureBlock[] = [];
|
|
24
24
|
let rest = markdown.replace(/^\s+/, '');
|
|
25
|
+
|
|
25
26
|
for (let match = FENCE.exec(rest); match !== null; match = FENCE.exec(rest)) {
|
|
26
27
|
signatures.push({ language: match[1] || 'text', code: match[2]! });
|
|
27
28
|
rest = rest.slice(match[0].length).replace(/^\s+/, '');
|
|
28
29
|
}
|
|
30
|
+
|
|
29
31
|
return { signatures, markdown: rest.replace(/^(?:-{3,}|\*{3,})\s*(?:\n|$)/, '').trim() };
|
|
30
32
|
}
|
|
31
33
|
|
|
@@ -33,14 +35,17 @@ function markedStringText(content: MarkedString): HoverText {
|
|
|
33
35
|
if (typeof content !== 'string') {
|
|
34
36
|
return { signatures: [{ language: content.language || 'text', code: content.value }], markdown: '' };
|
|
35
37
|
}
|
|
38
|
+
|
|
36
39
|
return splitSignatures(content);
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
/* The text of a hover, whichever of the three shapes the protocol allows it in. */
|
|
40
43
|
export function hoverTextOf(hover: Hover): HoverText {
|
|
41
44
|
const { contents } = hover;
|
|
45
|
+
|
|
42
46
|
if (Array.isArray(contents)) {
|
|
43
47
|
const parts = contents.map(markedStringText);
|
|
48
|
+
|
|
44
49
|
return {
|
|
45
50
|
signatures: parts.flatMap((part) => part.signatures),
|
|
46
51
|
markdown: parts
|
|
@@ -49,9 +54,11 @@ export function hoverTextOf(hover: Hover): HoverText {
|
|
|
49
54
|
.join('\n\n')
|
|
50
55
|
};
|
|
51
56
|
}
|
|
57
|
+
|
|
52
58
|
if (typeof contents === 'string' || 'language' in contents) {
|
|
53
59
|
return markedStringText(contents);
|
|
54
60
|
}
|
|
61
|
+
|
|
55
62
|
return contents.kind === 'plaintext' ? { signatures: [], markdown: escapeMarkdown(contents.value).trim() } : splitSignatures(contents.value);
|
|
56
63
|
}
|
|
57
64
|
|
|
@@ -73,17 +80,10 @@ export function locationsOf(result: NavigationResult): Location[] {
|
|
|
73
80
|
if (result === null) {
|
|
74
81
|
return [];
|
|
75
82
|
}
|
|
83
|
+
|
|
76
84
|
const list = Array.isArray(result) ? (result as (Location | LocationLink)[]) : [result];
|
|
77
|
-
return list.map((entry) => (isLink(entry) ? { uri: entry.targetUri, range: entry.targetSelectionRange } : entry));
|
|
78
|
-
}
|
|
79
85
|
|
|
80
|
-
|
|
81
|
-
return (
|
|
82
|
-
left.start.line === right.start.line &&
|
|
83
|
-
left.start.character === right.start.character &&
|
|
84
|
-
left.end.line === right.end.line &&
|
|
85
|
-
left.end.character === right.end.character
|
|
86
|
-
);
|
|
86
|
+
return list.map((entry) => (isLink(entry) ? { uri: entry.targetUri, range: entry.targetSelectionRange } : entry));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/* One `@tag` of a docblock, the way a server writes it into a hover: `_@since_ 1.0` or `*@param* `name` - what it is`. */
|
|
@@ -105,20 +105,25 @@ const BLOCK_TAGS = new Set(['example']);
|
|
|
105
105
|
export function splitDocTags(markdown: string): { readonly markdown: string; readonly tags: readonly DocTag[] } {
|
|
106
106
|
const tags: DocTag[] = [];
|
|
107
107
|
const kept: string[] = [];
|
|
108
|
+
|
|
108
109
|
for (const paragraph of markdown.split(/\n{2,}/)) {
|
|
109
110
|
const lines = paragraph.split('\n');
|
|
110
111
|
const matches = lines.map((line) => TAG_LINE.exec(line.trim()));
|
|
111
112
|
const names = matches.map((match) => (match === null ? null : (match[1] ?? match[2] ?? match[3])!));
|
|
113
|
+
|
|
112
114
|
if (paragraph.trim() === '' || names.some((name) => name === null || BLOCK_TAGS.has(name))) {
|
|
113
115
|
kept.push(paragraph);
|
|
116
|
+
|
|
114
117
|
continue;
|
|
115
118
|
}
|
|
119
|
+
|
|
116
120
|
matches.forEach((match, index) => {
|
|
117
121
|
// The TypeScript server puts a dash between a tag, its parameter and its text.
|
|
118
122
|
const text = match![4]!.replace(/^[\u2014\u2013-]\s*/, '').replace(/^(`[^`]+`)\s+[\u2014\u2013-]\s+/, '$1 ');
|
|
119
123
|
tags.push({ name: names[index]!, markdown: text.trim() });
|
|
120
124
|
});
|
|
121
125
|
}
|
|
126
|
+
|
|
122
127
|
// By name, and in the order written within one name, so parameters keep the order of the signature.
|
|
123
128
|
return { markdown: kept.join('\n\n').trim(), tags: tags.toSorted((left, right) => left.name.localeCompare(right.name)) };
|
|
124
129
|
}
|
|
@@ -147,11 +152,14 @@ const BASELINE = /!\[Baseline icon\]\([^)\s]*\)[ \t]*_([^\n]+?)_[ \t]*(?:\n|$)/;
|
|
|
147
152
|
/* The Baseline status a text carries, which the card draws with a mark of its own, and the text without it. */
|
|
148
153
|
function takeBaseline(markdown: string): { baseline: Baseline | null; rest: string } {
|
|
149
154
|
const match = BASELINE.exec(markdown);
|
|
155
|
+
|
|
150
156
|
if (match === null) {
|
|
151
157
|
return { baseline: null, rest: markdown };
|
|
152
158
|
}
|
|
159
|
+
|
|
153
160
|
const text = match[1]!.trim();
|
|
154
161
|
const level = text.startsWith('Widely') ? 'widely' : text.startsWith('Newly') ? 'newly' : 'limited';
|
|
162
|
+
|
|
155
163
|
return { baseline: { level, text }, rest: (markdown.slice(0, match.index) + markdown.slice(match.index + match[0].length)).replace(/\n{3,}/g, '\n\n') };
|
|
156
164
|
}
|
|
157
165
|
|
|
@@ -176,13 +184,6 @@ function decodeEntities(text: string): string {
|
|
|
176
184
|
.replace(/&/g, '&');
|
|
177
185
|
}
|
|
178
186
|
|
|
179
|
-
function dedent(text: string): string {
|
|
180
|
-
const lines = text.replace(/^\n+|\s+$/g, '').split('\n');
|
|
181
|
-
const indents = lines.filter((line) => line.trim() !== '').map((line) => line.length - line.trimStart().length);
|
|
182
|
-
const common = indents.length === 0 ? 0 : Math.min(...indents);
|
|
183
|
-
return lines.map((line) => line.slice(common)).join('\n');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
187
|
/*
|
|
187
188
|
* What a docblock writes for a reader of its source, written for one of a hover: a `<code>` or `<pre>`
|
|
188
189
|
* example as a block in the symbol's language, and an inline `{@see Name}` or `{@link Name label}` as
|
|
@@ -192,6 +193,7 @@ export function docblockMarkdown(markdown: string, language: string): string {
|
|
|
192
193
|
return markdown
|
|
193
194
|
.replace(HTML_CODE, (_match, _tag: string, body: string) => {
|
|
194
195
|
const code = decodeEntities(body);
|
|
196
|
+
|
|
195
197
|
return code.includes('\n') ? `\n\n\`\`\`${language}\n${dedent(code)}\n\`\`\`\n\n` : `\`${code.trim()}\``;
|
|
196
198
|
})
|
|
197
199
|
.replace(INLINE_TAG, (_match, _tag: string, target: string, label: string | undefined) => (label === undefined ? `\`${target}\`` : label))
|
|
@@ -205,17 +207,22 @@ export type MarkdownPart = { readonly kind: 'prose'; readonly text: string } | {
|
|
|
205
207
|
export function markdownParts(markdown: string): MarkdownPart[] {
|
|
206
208
|
const parts: MarkdownPart[] = [];
|
|
207
209
|
let rest = markdown;
|
|
210
|
+
|
|
208
211
|
for (let match = ANY_FENCE.exec(rest); match !== null; match = ANY_FENCE.exec(rest)) {
|
|
209
212
|
const prose = rest.slice(0, match.index).trim();
|
|
213
|
+
|
|
210
214
|
if (prose !== '') {
|
|
211
215
|
parts.push({ kind: 'prose', text: prose });
|
|
212
216
|
}
|
|
217
|
+
|
|
213
218
|
parts.push({ kind: 'code', block: { language: match[1] || 'text', code: match[2]! } });
|
|
214
219
|
rest = rest.slice(match.index + match[0].length);
|
|
215
220
|
}
|
|
221
|
+
|
|
216
222
|
if (rest.trim() !== '') {
|
|
217
223
|
parts.push({ kind: 'prose', text: rest.trim() });
|
|
218
224
|
}
|
|
225
|
+
|
|
219
226
|
return parts;
|
|
220
227
|
}
|
|
221
228
|
|
|
@@ -227,18 +234,23 @@ function unescapeMarkdown(text: string): string {
|
|
|
227
234
|
function sectionOf(chunk: string, leading: readonly SignatureBlock[]): HoverSection {
|
|
228
235
|
let rest = chunk.trim();
|
|
229
236
|
const title = TITLE.exec(rest);
|
|
237
|
+
|
|
230
238
|
if (title !== null) {
|
|
231
239
|
rest = rest.slice(title[0].length).trim();
|
|
232
240
|
}
|
|
241
|
+
|
|
233
242
|
const signatures = [...leading];
|
|
234
243
|
// A section's own snippet is its first block; one further down after the prose, such as an example, stays prose.
|
|
235
244
|
const fence = ANY_FENCE.exec(rest);
|
|
245
|
+
|
|
236
246
|
if (fence !== null && !/@example/.test(rest.slice(0, fence.index))) {
|
|
237
247
|
signatures.push({ language: fence[1] || 'text', code: fence[2]! });
|
|
238
248
|
rest = (rest.slice(0, fence.index) + rest.slice(fence.index + fence[0].length)).trim();
|
|
239
249
|
}
|
|
250
|
+
|
|
240
251
|
const taken = takeBaseline(rest);
|
|
241
252
|
const doc = splitDocTags(docblockMarkdown(taken.rest, signatures[0]?.language ?? 'text'));
|
|
253
|
+
|
|
242
254
|
return {
|
|
243
255
|
title: title === null ? null : unescapeMarkdown((title[1] ?? title[2])!.trim()),
|
|
244
256
|
signatures: signatures.map(withoutOpenTag),
|
|
@@ -253,14 +265,19 @@ export function hoverSectionsOf(text: HoverText): HoverSection[] {
|
|
|
253
265
|
const chunks = text.markdown === '' ? [''] : text.markdown.split(RULE);
|
|
254
266
|
const sections = chunks.map((chunk, index) => sectionOf(chunk, index === 0 ? text.signatures : []));
|
|
255
267
|
const seen = new Set<string>();
|
|
268
|
+
|
|
256
269
|
return sections.filter((section) => {
|
|
257
270
|
const empty =
|
|
258
271
|
section.title === null && section.signatures.length === 0 && section.markdown === '' && section.tags.length === 0 && section.baseline === null;
|
|
272
|
+
|
|
259
273
|
const key = JSON.stringify([section.title, section.signatures, section.markdown, section.baseline]);
|
|
274
|
+
|
|
260
275
|
if (empty || seen.has(key)) {
|
|
261
276
|
return false;
|
|
262
277
|
}
|
|
278
|
+
|
|
263
279
|
seen.add(key);
|
|
280
|
+
|
|
264
281
|
return true;
|
|
265
282
|
});
|
|
266
283
|
}
|
package/src/hover.ts
CHANGED
|
@@ -10,9 +10,12 @@ import { isShortcut } from './shortcut-keys.ts';
|
|
|
10
10
|
import { realTimers, type Timers } from './timers.ts';
|
|
11
11
|
|
|
12
12
|
export const SHOW_DELAY_MS = 300;
|
|
13
|
+
|
|
13
14
|
const HIDE_DELAY_MS = 250;
|
|
15
|
+
|
|
14
16
|
/* How long a card has to stay up before the references of its symbol are counted, which can be a request over the whole project. */
|
|
15
17
|
const COUNT_DELAY_MS = 500;
|
|
18
|
+
|
|
16
19
|
const TOAST_ID = 'language-hover';
|
|
17
20
|
|
|
18
21
|
export interface HoverSettings {
|
|
@@ -28,18 +31,24 @@ export interface HoverSettings {
|
|
|
28
31
|
export class HoverFeature {
|
|
29
32
|
private readonly language: EditorLanguage;
|
|
30
33
|
private readonly timers: Timers;
|
|
34
|
+
|
|
31
35
|
/* The counts asked for since the text last changed, by the symbol's position, so moving back to a name does not ask again. */
|
|
32
36
|
private readonly counts = new Map<string, number>();
|
|
37
|
+
|
|
33
38
|
private showTimer: unknown;
|
|
34
39
|
private hideTimer: unknown;
|
|
35
40
|
private countTimer: unknown;
|
|
36
41
|
private inCard = false;
|
|
37
42
|
private referenceCount = true;
|
|
43
|
+
|
|
38
44
|
/* Counts every request, so an answer that arrives after the pointer moved on is dropped. */
|
|
39
45
|
private request = 0;
|
|
46
|
+
|
|
40
47
|
private lookup: AbortController | null = null;
|
|
48
|
+
|
|
41
49
|
/* A card the keyboard opened: scrolling does not take it away, since it was never under a pointer. Moving the caret does. */
|
|
42
50
|
private pinned = false;
|
|
51
|
+
|
|
43
52
|
/* The card is being read: an explanation runs in it or is shown, so the pointer going elsewhere neither closes nor replaces it. */
|
|
44
53
|
private held = false;
|
|
45
54
|
|
|
@@ -47,6 +56,7 @@ export class HoverFeature {
|
|
|
47
56
|
this.language = language;
|
|
48
57
|
this.timers = timers;
|
|
49
58
|
const { editor } = language;
|
|
59
|
+
|
|
50
60
|
const offs = [
|
|
51
61
|
editor.onHover((hover) => this.moved(hover)),
|
|
52
62
|
editor.onTextChange(() => {
|
|
@@ -57,6 +67,7 @@ export class HoverFeature {
|
|
|
57
67
|
if (this.held) {
|
|
58
68
|
this.hide();
|
|
59
69
|
}
|
|
70
|
+
|
|
60
71
|
return false;
|
|
61
72
|
}),
|
|
62
73
|
editor.onViewChange(() => {
|
|
@@ -72,25 +83,32 @@ export class HoverFeature {
|
|
|
72
83
|
language.onKey((event) => {
|
|
73
84
|
if (isShortcut(this.language.shortcuts.quickInfo, event)) {
|
|
74
85
|
this.quickInfo();
|
|
86
|
+
|
|
75
87
|
return true;
|
|
76
88
|
}
|
|
89
|
+
|
|
77
90
|
if (event.key === 'Escape' && this.language.popups.getState().hover !== null && this.language.popups.getState().callHierarchy === null) {
|
|
78
91
|
this.hide();
|
|
92
|
+
|
|
79
93
|
return true;
|
|
80
94
|
}
|
|
95
|
+
|
|
81
96
|
return false;
|
|
82
97
|
}, KEY_PRIORITY.hover)
|
|
83
98
|
];
|
|
99
|
+
|
|
84
100
|
language.onDispose(() => {
|
|
85
101
|
for (const off of offs) {
|
|
86
102
|
off();
|
|
87
103
|
}
|
|
104
|
+
|
|
88
105
|
this.hide();
|
|
89
106
|
});
|
|
90
107
|
}
|
|
91
108
|
|
|
92
109
|
configure(settings: HoverSettings): void {
|
|
93
110
|
this.referenceCount = settings.referenceCount !== false;
|
|
111
|
+
|
|
94
112
|
if (!this.referenceCount) {
|
|
95
113
|
this.timers.clear(this.countTimer);
|
|
96
114
|
}
|
|
@@ -99,6 +117,7 @@ export class HoverFeature {
|
|
|
99
117
|
/* The pointer entered or left the card, which is where a link in it is clicked. */
|
|
100
118
|
holdCard(inside: boolean): void {
|
|
101
119
|
this.inCard = inside;
|
|
120
|
+
|
|
102
121
|
if (inside) {
|
|
103
122
|
this.timers.clear(this.hideTimer);
|
|
104
123
|
} else {
|
|
@@ -116,12 +135,15 @@ export class HoverFeature {
|
|
|
116
135
|
/* The problems at a position as a card, at once; what the servers say about the symbol is left for the pointer and Quick Info. */
|
|
117
136
|
showProblemsAt(position: EditorPosition): boolean {
|
|
118
137
|
const problems = this.language.diagnostics.at(position);
|
|
138
|
+
|
|
119
139
|
if (problems.length === 0) {
|
|
120
140
|
return false;
|
|
121
141
|
}
|
|
142
|
+
|
|
122
143
|
this.hide();
|
|
123
144
|
this.pinned = true;
|
|
124
145
|
this.present(position, problems, null, problems[0]!.diagnostic.range);
|
|
146
|
+
|
|
125
147
|
return true;
|
|
126
148
|
}
|
|
127
149
|
|
|
@@ -159,6 +181,7 @@ export class HoverFeature {
|
|
|
159
181
|
this.timers.clear(this.hideTimer);
|
|
160
182
|
this.timers.clear(this.countTimer);
|
|
161
183
|
this.inCard = false;
|
|
184
|
+
|
|
162
185
|
if (this.language.popups.getState().hover !== null) {
|
|
163
186
|
this.language.popups.setState({ hover: null });
|
|
164
187
|
}
|
|
@@ -168,17 +191,24 @@ export class HoverFeature {
|
|
|
168
191
|
if (this.held) {
|
|
169
192
|
return;
|
|
170
193
|
}
|
|
194
|
+
|
|
171
195
|
this.timers.clear(this.showTimer);
|
|
196
|
+
|
|
172
197
|
if (hover === null) {
|
|
173
198
|
this.request++;
|
|
174
199
|
this.scheduleHide();
|
|
200
|
+
|
|
175
201
|
return;
|
|
176
202
|
}
|
|
203
|
+
|
|
177
204
|
const shown = this.language.popups.getState().hover;
|
|
205
|
+
|
|
178
206
|
if (shown !== null && rangeHolds(shown.subject, hover.position)) {
|
|
179
207
|
this.timers.clear(this.hideTimer);
|
|
208
|
+
|
|
180
209
|
return;
|
|
181
210
|
}
|
|
211
|
+
|
|
182
212
|
this.scheduleHide();
|
|
183
213
|
const request = ++this.request;
|
|
184
214
|
this.showTimer = this.timers.set(() => void this.show(hover.position, request, false), SHOW_DELAY_MS);
|
|
@@ -186,9 +216,11 @@ export class HoverFeature {
|
|
|
186
216
|
|
|
187
217
|
private scheduleHide(): void {
|
|
188
218
|
this.timers.clear(this.hideTimer);
|
|
219
|
+
|
|
189
220
|
if (this.language.popups.getState().hover === null || this.inCard || this.held) {
|
|
190
221
|
return;
|
|
191
222
|
}
|
|
223
|
+
|
|
192
224
|
this.hideTimer = this.timers.set(() => {
|
|
193
225
|
if (!this.inCard) {
|
|
194
226
|
this.hide();
|
|
@@ -200,23 +232,30 @@ export class HoverFeature {
|
|
|
200
232
|
private async ask(position: EditorHover['position'], signal: AbortSignal): Promise<{ info: HoverInfo | null; range: EditorRange | null }> {
|
|
201
233
|
const { service } = this.language.project;
|
|
202
234
|
const { uri } = this.language;
|
|
235
|
+
|
|
203
236
|
if (!service.supports('textDocument/hover', uri)) {
|
|
204
237
|
return { info: null, range: null };
|
|
205
238
|
}
|
|
239
|
+
|
|
206
240
|
const [hover, definition] = await Promise.all([
|
|
207
241
|
service.hover(uri, position, { signal }).catch(() => null),
|
|
208
242
|
service.supports('textDocument/definition', uri) ? service.definition(uri, position, { signal }).catch(() => null) : null
|
|
209
243
|
]);
|
|
244
|
+
|
|
210
245
|
if (hover === null) {
|
|
211
246
|
return { info: null, range: null };
|
|
212
247
|
}
|
|
248
|
+
|
|
213
249
|
const text = hoverTextOf(hover);
|
|
250
|
+
|
|
214
251
|
if (isEmptyHover(text)) {
|
|
215
252
|
return { info: null, range: null };
|
|
216
253
|
}
|
|
254
|
+
|
|
217
255
|
const { editor } = this.language;
|
|
218
256
|
const line = lineTextOf(editor, position.line);
|
|
219
257
|
const word = wordRangeAt(line, position);
|
|
258
|
+
|
|
220
259
|
return {
|
|
221
260
|
info: {
|
|
222
261
|
text,
|
|
@@ -234,22 +273,30 @@ export class HoverFeature {
|
|
|
234
273
|
const controller = new AbortController();
|
|
235
274
|
this.lookup = controller;
|
|
236
275
|
const { info, range } = await this.ask(position, controller.signal);
|
|
276
|
+
|
|
237
277
|
if (request !== this.request) {
|
|
238
278
|
return;
|
|
239
279
|
}
|
|
280
|
+
|
|
240
281
|
if (problems.length === 0 && info === null) {
|
|
241
282
|
this.hide();
|
|
283
|
+
|
|
242
284
|
if (pinned && (await this.language.inlayHints.showTooltipAt(position))) {
|
|
243
285
|
this.pinned = true;
|
|
286
|
+
|
|
244
287
|
return;
|
|
245
288
|
}
|
|
289
|
+
|
|
246
290
|
if (pinned) {
|
|
247
291
|
this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title: this.language.project.i18n.t('editor:language.hover.none') });
|
|
248
292
|
}
|
|
293
|
+
|
|
249
294
|
return;
|
|
250
295
|
}
|
|
296
|
+
|
|
251
297
|
this.pinned = pinned;
|
|
252
298
|
this.present(position, problems, info, problems[0]?.diagnostic.range ?? range);
|
|
299
|
+
|
|
253
300
|
if (info !== null && this.referenceCount) {
|
|
254
301
|
this.scheduleCount(position, request, controller.signal);
|
|
255
302
|
}
|
|
@@ -258,6 +305,7 @@ export class HoverFeature {
|
|
|
258
305
|
private present(position: EditorPosition, problems: readonly Problem[], info: HoverInfo | null, range: EditorRange | null): void {
|
|
259
306
|
const subject = range ?? { start: position, end: position };
|
|
260
307
|
this.timers.clear(this.hideTimer);
|
|
308
|
+
|
|
261
309
|
this.language.popups.setState({
|
|
262
310
|
hover: {
|
|
263
311
|
anchor: subject.start.line === position.line ? subject.start : position,
|
|
@@ -273,10 +321,13 @@ export class HoverFeature {
|
|
|
273
321
|
private scheduleCount(position: EditorPosition, request: number, signal: AbortSignal): void {
|
|
274
322
|
const key = `${position.line}:${this.wordStart(position)}`;
|
|
275
323
|
const known = this.counts.get(key);
|
|
324
|
+
|
|
276
325
|
if (known !== undefined) {
|
|
277
326
|
this.showCount(known, request);
|
|
327
|
+
|
|
278
328
|
return;
|
|
279
329
|
}
|
|
330
|
+
|
|
280
331
|
this.timers.clear(this.countTimer);
|
|
281
332
|
this.countTimer = this.timers.set(() => void this.count(position, key, request, signal), COUNT_DELAY_MS);
|
|
282
333
|
}
|
|
@@ -284,19 +335,24 @@ export class HoverFeature {
|
|
|
284
335
|
/* How many places use the symbol, which can take a while in a large project and so arrives after the card does. */
|
|
285
336
|
private async count(position: EditorPosition, key: string, request: number, signal: AbortSignal): Promise<void> {
|
|
286
337
|
const { service } = this.language.project;
|
|
338
|
+
|
|
287
339
|
if (request !== this.request || !this.referenceCount || !service.supports('textDocument/references', this.language.uri)) {
|
|
288
340
|
return;
|
|
289
341
|
}
|
|
342
|
+
|
|
290
343
|
const found = await service.references(this.language.uri, position, false, { signal }).catch(() => null);
|
|
344
|
+
|
|
291
345
|
if (found === null) {
|
|
292
346
|
return;
|
|
293
347
|
}
|
|
348
|
+
|
|
294
349
|
this.counts.set(key, found.length);
|
|
295
350
|
this.showCount(found.length, request);
|
|
296
351
|
}
|
|
297
352
|
|
|
298
353
|
private showCount(count: number, request: number): void {
|
|
299
354
|
const shown = this.language.popups.getState().hover;
|
|
355
|
+
|
|
300
356
|
if (request === this.request && shown?.info != null) {
|
|
301
357
|
this.language.popups.setState({ hover: { ...shown, info: { ...shown.info, references: count } } });
|
|
302
358
|
}
|
|
@@ -305,6 +361,7 @@ export class HoverFeature {
|
|
|
305
361
|
/* Where the word at a position starts, which names the symbol for the cache. */
|
|
306
362
|
wordStart(position: EditorPosition): number {
|
|
307
363
|
const line = lineTextOf(this.language.editor, position.line);
|
|
364
|
+
|
|
308
365
|
return wordRangeAt(line, position)?.start.character ?? position.character;
|
|
309
366
|
}
|
|
310
367
|
}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { realTimers, type Timers } from './timers.ts';
|
|
|
4
4
|
export { type SemanticTokensOptions } from './semantic-tokens.ts';
|
|
5
5
|
export { ProjectLanguage, type FileOperationTarget } from './project-language.ts';
|
|
6
6
|
export type { LanguageDocumentHandle } from './project-language.ts';
|
|
7
|
+
|
|
7
8
|
export type {
|
|
8
9
|
SnippetVariableContext,
|
|
9
10
|
LanguageHost,
|
|
@@ -13,6 +14,7 @@ export type {
|
|
|
13
14
|
EditorNotification,
|
|
14
15
|
RenameSuggestionsRequest
|
|
15
16
|
} from './host-types.ts';
|
|
17
|
+
|
|
16
18
|
export { EditorView } from './EditorView.tsx';
|
|
17
19
|
export { createHolder } from './holder.ts';
|
|
18
20
|
export type { EditorViewProps } from './EditorView.tsx';
|
|
@@ -36,6 +38,7 @@ export { useProblemCounts } from './use-problem-counts.ts';
|
|
|
36
38
|
export type { ProblemCounts } from './diagnostics.ts';
|
|
37
39
|
export type { BlameSource, CodeVisionSettings } from './code-vision.ts';
|
|
38
40
|
export type { HoverSettings } from './hover.ts';
|
|
41
|
+
|
|
39
42
|
export type {
|
|
40
43
|
PopupState,
|
|
41
44
|
PopupStore,
|
|
@@ -57,6 +60,7 @@ export type {
|
|
|
57
60
|
MenuView,
|
|
58
61
|
AuthorsView
|
|
59
62
|
} from './popups.ts';
|
|
63
|
+
|
|
60
64
|
export { RowHost, LineActionHost } from './row-host.ts';
|
|
61
65
|
export type { HostedRow, HostedAction } from './row-host.ts';
|
|
62
66
|
export { type HighlightLayers, highlightLayers } from './highlight-layers.ts';
|