@adecore/editor-react 0.22.1 → 0.23.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/CallHierarchyPanel.d.ts +6 -0
- package/dist/CallHierarchyPanel.js +88 -0
- package/dist/CompletionPopup.js +11 -3
- package/dist/DiffView.d.ts +22 -0
- package/dist/DiffView.js +203 -0
- package/dist/EditorContextMenu.js +1 -0
- package/dist/EditorView.js +18 -1
- package/dist/FindReplace.d.ts +1 -1
- package/dist/FindReplace.js +41 -11
- package/dist/HoverCard.js +2 -2
- package/dist/LanguagePopups.js +4 -1
- package/dist/Markdown.d.ts +2 -1
- package/dist/Markdown.js +8 -4
- package/dist/PeekPanel.d.ts +3 -0
- package/dist/PeekPanel.js +2 -3
- package/dist/PickPopup.js +15 -2
- package/dist/RenameCard.js +10 -5
- package/dist/RenderedDocs.d.ts +9 -0
- package/dist/RenderedDocs.js +84 -0
- package/dist/SplitDiffView.d.ts +9 -0
- package/dist/SplitDiffView.js +276 -0
- package/dist/SymbolPicker.d.ts +3 -0
- package/dist/SymbolPicker.js +4 -3
- package/dist/call-hierarchy-model.d.ts +52 -0
- package/dist/call-hierarchy-model.js +99 -0
- package/dist/call-hierarchy.d.ts +42 -0
- package/dist/call-hierarchy.js +399 -0
- package/dist/code-actions-model.d.ts +2 -0
- package/dist/code-actions-model.js +12 -10
- package/dist/code-actions.js +3 -1
- package/dist/completion-model.d.ts +14 -4
- package/dist/completion-model.js +40 -33
- package/dist/completion.d.ts +4 -0
- package/dist/completion.js +257 -42
- package/dist/diagnostics.js +8 -6
- package/dist/diff-expansion.d.ts +9 -0
- package/dist/diff-expansion.js +41 -0
- package/dist/diff-overlays.d.ts +15 -0
- package/dist/diff-overlays.js +28 -0
- package/dist/diff-syntax.d.ts +16 -0
- package/dist/diff-syntax.js +49 -0
- package/dist/diff-wordmarks.d.ts +5 -0
- package/dist/diff-wordmarks.js +26 -0
- package/dist/doc-comment-model.d.ts +23 -0
- package/dist/doc-comment-model.js +291 -0
- package/dist/editor-language.d.ts +13 -1
- package/dist/editor-language.js +14 -4
- package/dist/editor-react.css +79 -0
- package/dist/format-on-type.d.ts +14 -0
- package/dist/format-on-type.js +96 -0
- package/dist/host-types.d.ts +17 -0
- package/dist/hover.d.ts +4 -0
- package/dist/hover.js +15 -2
- package/dist/index.d.ts +19 -5
- package/dist/index.js +9 -1
- package/dist/inlay-hints.d.ts +47 -2
- package/dist/inlay-hints.js +353 -16
- package/dist/language-ids.js +4 -2
- package/dist/locales/en.json +114 -3
- package/dist/locales/nl.json +114 -3
- package/dist/models.d.ts +6 -2
- package/dist/models.js +5 -1
- package/dist/patch-decorations.d.ts +27 -0
- package/dist/patch-decorations.js +182 -0
- package/dist/patch-layout.d.ts +40 -0
- package/dist/patch-layout.js +304 -0
- package/dist/peek.js +1 -0
- package/dist/pick.d.ts +1 -0
- package/dist/pick.js +11 -1
- package/dist/popups.d.ts +17 -0
- package/dist/popups.js +1 -0
- package/dist/project-language.d.ts +8 -0
- package/dist/project-language.js +34 -2
- package/dist/refresher.d.ts +1 -0
- package/dist/refresher.js +5 -0
- package/dist/rename-model.d.ts +1 -1
- package/dist/rename-model.js +3 -7
- package/dist/rename.js +5 -8
- package/dist/rendered-docs.d.ts +62 -0
- package/dist/rendered-docs.js +368 -0
- package/dist/row-host.d.ts +2 -0
- package/dist/row-host.js +11 -3
- package/dist/semantic-delta.d.ts +2 -0
- package/dist/semantic-delta.js +27 -0
- package/dist/semantic-model.js +16 -0
- package/dist/semantic-tokens.d.ts +26 -1
- package/dist/semantic-tokens.js +174 -14
- package/dist/shortcut-keys.d.ts +2 -0
- package/dist/shortcut-keys.js +6 -2
- package/dist/signature.js +1 -1
- package/dist/snippet-field-transforms.d.ts +30 -0
- package/dist/snippet-field-transforms.js +116 -0
- package/dist/snippet-indent.d.ts +2 -0
- package/dist/snippet-indent.js +49 -0
- package/dist/snippet-range-order.d.ts +3 -0
- package/dist/snippet-range-order.js +19 -0
- package/dist/snippet-session.d.ts +42 -6
- package/dist/snippet-session.js +650 -53
- package/dist/snippet-transform-client.d.ts +19 -0
- package/dist/snippet-transform-client.js +66 -0
- package/dist/snippet-transform-format.d.ts +15 -0
- package/dist/snippet-transform-format.js +302 -0
- package/dist/snippet-transform-protocol.d.ts +45 -0
- package/dist/snippet-transform-protocol.js +8 -0
- package/dist/snippet-transform-queue.d.ts +32 -0
- package/dist/snippet-transform-queue.js +173 -0
- package/dist/snippet-transform-worker.d.ts +10 -0
- package/dist/snippet-transform-worker.js +41 -0
- package/dist/snippet-variable-names.d.ts +1 -0
- package/dist/snippet-variable-names.js +36 -0
- package/dist/snippet-variables.d.ts +7 -0
- package/dist/snippet-variables.js +66 -0
- package/dist/snippet.d.ts +30 -3
- package/dist/snippet.js +402 -87
- package/dist/split-alignment.d.ts +6 -0
- package/dist/split-alignment.js +38 -0
- package/dist/split-scroll.d.ts +14 -0
- package/dist/split-scroll.js +45 -0
- package/dist/symbols.d.ts +2 -1
- package/dist/symbols.js +39 -7
- package/dist/testing.d.ts +5 -1
- package/dist/testing.js +12 -0
- package/dist/workspace-edit.d.ts +25 -3
- package/dist/workspace-edit.js +282 -143
- package/package.json +10 -5
- package/src/CallHierarchyPanel.tsx +238 -0
- package/src/CompletionPopup.tsx +12 -1
- package/src/DiffView.tsx +262 -0
- package/src/EditorContextMenu.tsx +1 -0
- package/src/EditorView.tsx +21 -2
- package/src/FindReplace.tsx +51 -13
- package/src/HoverCard.tsx +23 -21
- package/src/LanguagePopups.tsx +5 -0
- package/src/Markdown.tsx +10 -3
- package/src/PeekPanel.tsx +2 -3
- package/src/PickPopup.tsx +18 -2
- package/src/RenameCard.tsx +18 -0
- package/src/RenderedDocs.tsx +155 -0
- package/src/SplitDiffView.tsx +337 -0
- package/src/SymbolPicker.tsx +4 -3
- package/src/call-hierarchy-model.ts +158 -0
- package/src/call-hierarchy.ts +441 -0
- package/src/code-actions-model.ts +17 -11
- package/src/code-actions.ts +3 -1
- package/src/completion-model.ts +61 -35
- package/src/completion.ts +302 -42
- package/src/diagnostics.ts +8 -6
- package/src/diff-expansion.ts +49 -0
- package/src/diff-overlays.tsx +69 -0
- package/src/diff-syntax.ts +67 -0
- package/src/diff-wordmarks.ts +31 -0
- package/src/doc-comment-model.ts +336 -0
- package/src/editor-language.ts +21 -5
- package/src/editor-react.css +79 -0
- package/src/format-on-type.ts +111 -0
- package/src/host-types.ts +19 -0
- package/src/hover.ts +18 -3
- package/src/index.ts +27 -4
- package/src/inlay-hints.ts +394 -18
- package/src/language-ids.ts +4 -2
- package/src/locales/en.json +114 -3
- package/src/locales/nl.json +114 -3
- package/src/models.ts +40 -1
- package/src/patch-decorations.ts +224 -0
- package/src/patch-layout.ts +388 -0
- package/src/peek.ts +1 -0
- package/src/pick.ts +12 -1
- package/src/popups.ts +24 -0
- package/src/project-language.ts +43 -2
- package/src/refresher.ts +6 -0
- package/src/rename-model.ts +4 -8
- package/src/rename.ts +5 -8
- package/src/rendered-docs.ts +422 -0
- package/src/row-host.ts +12 -3
- package/src/semantic-delta.ts +29 -0
- package/src/semantic-model.ts +17 -0
- package/src/semantic-tokens.ts +202 -19
- package/src/shortcut-keys.ts +6 -2
- package/src/signature.ts +1 -1
- package/src/snippet-field-transforms.ts +144 -0
- package/src/snippet-indent.ts +53 -0
- package/src/snippet-range-order.ts +19 -0
- package/src/snippet-session.ts +797 -56
- package/src/snippet-transform-client.ts +84 -0
- package/src/snippet-transform-format.ts +325 -0
- package/src/snippet-transform-protocol.ts +24 -0
- package/src/snippet-transform-queue.ts +199 -0
- package/src/snippet-transform-worker.ts +58 -0
- package/src/snippet-variable-names.ts +36 -0
- package/src/snippet-variables.ts +74 -0
- package/src/snippet.ts +452 -89
- package/src/split-alignment.ts +45 -0
- package/src/split-scroll.ts +54 -0
- package/src/symbols.ts +43 -7
- package/src/testing.ts +25 -0
- package/src/workspace-edit.ts +347 -169
package/dist/host-types.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { WorkerRequestPort } from '@adecore/editor/worker-client';
|
|
2
|
+
import type { EditorPosition, EditorRange } from '@adecore/editor';
|
|
1
3
|
import type { i18n } from 'i18next';
|
|
2
4
|
import type { Keymap } from '@adecore/editor/keymap';
|
|
3
5
|
import type { ProjectFiles } from './workspace-edit.ts';
|
|
@@ -31,7 +33,21 @@ export interface RenameSuggestionsRequest {
|
|
|
31
33
|
context: string;
|
|
32
34
|
uses: readonly string[];
|
|
33
35
|
}
|
|
36
|
+
export interface SnippetVariableContext {
|
|
37
|
+
readonly uri: string;
|
|
38
|
+
readonly languageId: string;
|
|
39
|
+
readonly position: EditorPosition;
|
|
40
|
+
readonly selection: EditorRange;
|
|
41
|
+
readonly selectedText: string;
|
|
42
|
+
readonly currentLine: string;
|
|
43
|
+
readonly currentWord: string;
|
|
44
|
+
readonly cursorIndex: number;
|
|
45
|
+
readonly cursorCount: number;
|
|
46
|
+
readonly folder?: string;
|
|
47
|
+
readonly path?: string;
|
|
48
|
+
}
|
|
34
49
|
export interface LanguageHost {
|
|
50
|
+
readonly snippetTransformWorker?: () => WorkerRequestPort;
|
|
35
51
|
readonly i18n?: i18n;
|
|
36
52
|
readonly folder?: string;
|
|
37
53
|
readonly files?: ProjectFiles;
|
|
@@ -41,5 +57,6 @@ export interface LanguageHost {
|
|
|
41
57
|
pathOfUri?(uri: string): string | null;
|
|
42
58
|
serverNames?(uri: string): readonly string[];
|
|
43
59
|
notify?(notification: EditorNotification): void;
|
|
60
|
+
resolveSnippetVariable?(name: string, context: SnippetVariableContext): string | undefined;
|
|
44
61
|
suggestNames?(request: RenameSuggestionsRequest, signal: AbortSignal): Promise<readonly string[]>;
|
|
45
62
|
}
|
package/dist/hover.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { EditorPosition, EditorRange } from '@adecore/editor';
|
|
2
2
|
import type { EditorLanguage } from './editor-language.ts';
|
|
3
|
+
import { type HoverText } from './hover-content.ts';
|
|
3
4
|
import { type Timers } from './timers.ts';
|
|
5
|
+
export declare const SHOW_DELAY_MS = 300;
|
|
4
6
|
export declare class HoverFeature {
|
|
5
7
|
private readonly language;
|
|
6
8
|
private readonly timers;
|
|
@@ -19,6 +21,8 @@ export declare class HoverFeature {
|
|
|
19
21
|
showProblemsAt(position: EditorPosition): boolean;
|
|
20
22
|
keep(): void;
|
|
21
23
|
showRange(range: EditorRange): void;
|
|
24
|
+
showText(position: EditorPosition, text: HoverText): void;
|
|
25
|
+
release(): void;
|
|
22
26
|
hide(): void;
|
|
23
27
|
private moved;
|
|
24
28
|
private scheduleHide;
|
package/dist/hover.js
CHANGED
|
@@ -4,7 +4,7 @@ import { lineTextOf } from './line-text.js';
|
|
|
4
4
|
import { wordRangeAt } from './rename-model.js';
|
|
5
5
|
import { isShortcut } from './shortcut-keys.js';
|
|
6
6
|
import { realTimers } from './timers.js';
|
|
7
|
-
const SHOW_DELAY_MS = 300;
|
|
7
|
+
export const SHOW_DELAY_MS = 300;
|
|
8
8
|
const HIDE_DELAY_MS = 250;
|
|
9
9
|
/* 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. */
|
|
10
10
|
const COUNT_DELAY_MS = 500;
|
|
@@ -61,7 +61,7 @@ export class HoverFeature {
|
|
|
61
61
|
this.quickInfo();
|
|
62
62
|
return true;
|
|
63
63
|
}
|
|
64
|
-
if (event.key === 'Escape' && this.language.popups.getState().hover !== null) {
|
|
64
|
+
if (event.key === 'Escape' && this.language.popups.getState().hover !== null && this.language.popups.getState().callHierarchy == null) {
|
|
65
65
|
this.hide();
|
|
66
66
|
return true;
|
|
67
67
|
}
|
|
@@ -114,6 +114,15 @@ export class HoverFeature {
|
|
|
114
114
|
this.keep();
|
|
115
115
|
this.present(range.start, [], null, range);
|
|
116
116
|
}
|
|
117
|
+
/* A card of text another feature brings, such as the tooltip of an inlay hint, which goes the way any hover card does. */
|
|
118
|
+
showText(position, text) {
|
|
119
|
+
this.hide();
|
|
120
|
+
this.present(position, [], { text, definition: null, word: '', references: null, plain: true }, null);
|
|
121
|
+
}
|
|
122
|
+
/* The pointer left what a card was opened for: it closes unless the pointer reaches the card in time. */
|
|
123
|
+
release() {
|
|
124
|
+
this.scheduleHide();
|
|
125
|
+
}
|
|
117
126
|
hide() {
|
|
118
127
|
this.pinned = false;
|
|
119
128
|
this.held = false;
|
|
@@ -199,6 +208,10 @@ export class HoverFeature {
|
|
|
199
208
|
}
|
|
200
209
|
if (problems.length === 0 && info === null) {
|
|
201
210
|
this.hide();
|
|
211
|
+
if (pinned && (await this.language.inlayHints.showTooltipAt(position))) {
|
|
212
|
+
this.pinned = true;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
202
215
|
if (pinned) {
|
|
203
216
|
this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title: this.language.project.i18n.t('editor:language.hover.none') });
|
|
204
217
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export { EditorLanguage } from './editor-language.ts';
|
|
2
|
-
export {
|
|
1
|
+
export { EditorLanguage, type EditorLanguageOptions } from './editor-language.ts';
|
|
2
|
+
export { DEFAULT_RANGE_LINES, DEFAULT_RANGE_CHARACTERS, type SemanticTokensOptions } from './semantic-tokens.ts';
|
|
3
|
+
export { ProjectLanguage, type FileOperationTarget } from './project-language.ts';
|
|
3
4
|
export type { LanguageDocumentHandle } from './project-language.ts';
|
|
4
|
-
export type { LanguageHost, DiskText, GitBlameCommit, GitBlameResult, EditorNotification, RenameSuggestionsRequest } from './host-types.ts';
|
|
5
|
+
export type { SnippetVariableContext, LanguageHost, DiskText, GitBlameCommit, GitBlameResult, EditorNotification, RenameSuggestionsRequest } from './host-types.ts';
|
|
5
6
|
export { EditorView } from './EditorView.tsx';
|
|
6
7
|
export { createHolder } from './holder.ts';
|
|
7
8
|
export type { EditorViewProps } from './EditorView.tsx';
|
|
@@ -13,8 +14,14 @@ export { SignatureCard } from './SignatureCard.tsx';
|
|
|
13
14
|
export { RenameCard } from './RenameCard.tsx';
|
|
14
15
|
export { PickPopup } from './PickPopup.tsx';
|
|
15
16
|
export { PeekPanel } from './PeekPanel.tsx';
|
|
17
|
+
export { CallHierarchyPanel } from './CallHierarchyPanel.tsx';
|
|
16
18
|
export { SymbolPicker } from './SymbolPicker.tsx';
|
|
17
19
|
export { CodeAuthorsCard } from './CodeAuthorsCard.tsx';
|
|
20
|
+
export { RenderedDocs } from './RenderedDocs.tsx';
|
|
21
|
+
export { RenderedDocsFeature } from './rendered-docs.ts';
|
|
22
|
+
export type { RenderedDocEntry, RenderedDocsSettings } from './rendered-docs.ts';
|
|
23
|
+
export { parseDocComment, sectionsOf, htmlToMarkdown, DOC_LINK_PREFIX } from './doc-comment-model.ts';
|
|
24
|
+
export type { DocTagEntry, DocTagSection, ParsedDocComment } from './doc-comment-model.ts';
|
|
18
25
|
export { EditorContextMenu } from './EditorContextMenu.tsx';
|
|
19
26
|
export { EditorRenderingProvider } from './rendering.tsx';
|
|
20
27
|
export type { EditorRendering } from './rendering-context.ts';
|
|
@@ -23,13 +30,13 @@ export { ChangeReview } from './ChangeReview.tsx';
|
|
|
23
30
|
export type { ChangeReviewProps } from './ChangeReview.tsx';
|
|
24
31
|
export { ProjectProblems, countsOf, compareRows } from './project-problems.ts';
|
|
25
32
|
export type { ProblemFile, ProblemRow } from './project-problems.ts';
|
|
26
|
-
export { applyWorkspaceEdit } from './workspace-edit.ts';
|
|
33
|
+
export { applyWorkspaceEdit, supportedResourceOperations } from './workspace-edit.ts';
|
|
27
34
|
export type { ProjectFiles, StagedFile, WorkspaceEditHost } from './workspace-edit.ts';
|
|
28
35
|
export { useProblemCounts } from './use-problem-counts.ts';
|
|
29
36
|
export type { ProblemCounts } from './diagnostics.ts';
|
|
30
37
|
export type { BlameSource, CodeVisionSettings } from './code-vision.ts';
|
|
31
38
|
export type { Problem } from './diagnostics-model.ts';
|
|
32
|
-
export type { PopupState, PopupStore, HoverInfo, HoverView, CompletionView, CompletionDocs, CompletionRow, SignatureView, RenameView, RenameFileView, PeekView, PickView, PickGroup, PickRow, PickPreview, SymbolsView, MenuView, AuthorsView } from './popups.ts';
|
|
39
|
+
export type { PopupState, PopupStore, HoverInfo, HoverView, CompletionView, CompletionDocs, CompletionRow, SignatureView, RenameView, RenameFileView, PeekView, CallHierarchyView, PickView, PickGroup, PickRow, PickPreview, SymbolsView, MenuView, AuthorsView } from './popups.ts';
|
|
33
40
|
export { RowHost, LineActionHost } from './row-host.ts';
|
|
34
41
|
export type { HostedRow, HostedAction } from './row-host.ts';
|
|
35
42
|
export { HighlightLayers, highlightLayers } from './highlight-layers.ts';
|
|
@@ -42,3 +49,10 @@ export { AttributionCard } from './AttributionCard.tsx';
|
|
|
42
49
|
export type { AttributionCardProps } from './AttributionCard.tsx';
|
|
43
50
|
export { ReviewGroup, joinReviewGroup } from './review-group.ts';
|
|
44
51
|
export type { ReviewMember } from './review-group.ts';
|
|
52
|
+
export { DiffView } from './DiffView.tsx';
|
|
53
|
+
export type { DiffViewProps } from './DiffView.tsx';
|
|
54
|
+
export type { DiffLimits } from './diff-expansion.ts';
|
|
55
|
+
export { diffSyntaxDocument } from './diff-syntax.ts';
|
|
56
|
+
export type { DiffSourceSide, DiffSourceTokens, DiffSyntaxSources } from './diff-syntax.ts';
|
|
57
|
+
export { decoratePatch, usePatchDecorations } from './patch-decorations.ts';
|
|
58
|
+
export type { PatchDecoration, PatchDecorationOptions, PatchLineLabels, PatchTimer } from './patch-decorations.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { EditorLanguage } from './editor-language.js';
|
|
2
|
+
export { DEFAULT_RANGE_LINES, DEFAULT_RANGE_CHARACTERS } from './semantic-tokens.js';
|
|
2
3
|
export { ProjectLanguage } from './project-language.js';
|
|
3
4
|
export { EditorView } from './EditorView.js';
|
|
4
5
|
export { createHolder } from './holder.js';
|
|
@@ -10,14 +11,18 @@ export { SignatureCard } from './SignatureCard.js';
|
|
|
10
11
|
export { RenameCard } from './RenameCard.js';
|
|
11
12
|
export { PickPopup } from './PickPopup.js';
|
|
12
13
|
export { PeekPanel } from './PeekPanel.js';
|
|
14
|
+
export { CallHierarchyPanel } from './CallHierarchyPanel.js';
|
|
13
15
|
export { SymbolPicker } from './SymbolPicker.js';
|
|
14
16
|
export { CodeAuthorsCard } from './CodeAuthorsCard.js';
|
|
17
|
+
export { RenderedDocs } from './RenderedDocs.js';
|
|
18
|
+
export { RenderedDocsFeature } from './rendered-docs.js';
|
|
19
|
+
export { parseDocComment, sectionsOf, htmlToMarkdown, DOC_LINK_PREFIX } from './doc-comment-model.js';
|
|
15
20
|
export { EditorContextMenu } from './EditorContextMenu.js';
|
|
16
21
|
export { EditorRenderingProvider } from './rendering.js';
|
|
17
22
|
export { FindReplace } from './FindReplace.js';
|
|
18
23
|
export { ChangeReview } from './ChangeReview.js';
|
|
19
24
|
export { ProjectProblems, countsOf, compareRows } from './project-problems.js';
|
|
20
|
-
export { applyWorkspaceEdit } from './workspace-edit.js';
|
|
25
|
+
export { applyWorkspaceEdit, supportedResourceOperations } from './workspace-edit.js';
|
|
21
26
|
export { useProblemCounts } from './use-problem-counts.js';
|
|
22
27
|
export { RowHost, LineActionHost } from './row-host.js';
|
|
23
28
|
export { HighlightLayers, highlightLayers } from './highlight-layers.js';
|
|
@@ -26,3 +31,6 @@ export { openingPlace, viewStates, followViewStates, viewStateKey, forgetMovesFr
|
|
|
26
31
|
export { lspLanguageIdOf, shikiLanguageOf, shikiLanguageOfPath } from './language-ids.js';
|
|
27
32
|
export { AttributionCard } from './AttributionCard.js';
|
|
28
33
|
export { ReviewGroup, joinReviewGroup } from './review-group.js';
|
|
34
|
+
export { DiffView } from './DiffView.js';
|
|
35
|
+
export { diffSyntaxDocument } from './diff-syntax.js';
|
|
36
|
+
export { decoratePatch, usePatchDecorations } from './patch-decorations.js';
|
package/dist/inlay-hints.d.ts
CHANGED
|
@@ -1,13 +1,58 @@
|
|
|
1
|
-
import type { EditorInlayHint, EditorPosition, EditorRange } from '@adecore/editor';
|
|
2
|
-
import type { InlayHint } from '@adecore/lsp';
|
|
1
|
+
import type { Editor, EditorInlayClick, EditorInlayHint, EditorPosition, EditorRange } from '@adecore/editor';
|
|
2
|
+
import type { Command, InlayHint, InlayHintLabelPart, Location, MarkupContent, TextEdit } from '@adecore/lsp';
|
|
3
3
|
import type { EditorLanguage } from './editor-language.ts';
|
|
4
|
+
import { type HoverText } from './hover-content.ts';
|
|
4
5
|
import { type Timers } from './timers.ts';
|
|
5
6
|
export declare function hintLabelOf(hint: InlayHint): string;
|
|
7
|
+
export declare function drawableHints(hints: readonly InlayHint[]): InlayHint[];
|
|
8
|
+
export declare function editorHintOf(hint: InlayHint, id: string): EditorInlayHint;
|
|
6
9
|
export declare function editorHintsOf(hints: readonly InlayHint[]): EditorInlayHint[];
|
|
7
10
|
export declare function withMargin(visible: EditorRange, endOfDocument: EditorPosition, endOfLine: (line: number) => EditorPosition): EditorRange;
|
|
8
11
|
export declare function covers(asked: EditorRange, visible: EditorRange): boolean;
|
|
12
|
+
export declare function tooltipTextOf(tooltip: string | MarkupContent | undefined): HoverText | null;
|
|
13
|
+
export declare function partOf(hint: InlayHint, index: number | null): InlayHintLabelPart | null;
|
|
14
|
+
export declare function tooltipOf(hint: InlayHint, index: number | null): HoverText | null;
|
|
15
|
+
export declare function lacksInfo(hint: InlayHint, index: number | null): boolean;
|
|
16
|
+
export declare function editsFit(editor: Editor, edits: readonly TextEdit[]): boolean;
|
|
17
|
+
type Action = {
|
|
18
|
+
readonly kind: 'go';
|
|
19
|
+
readonly location: Location;
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: 'run';
|
|
22
|
+
readonly command: Command;
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: 'insert';
|
|
25
|
+
readonly edits: TextEdit[];
|
|
26
|
+
};
|
|
27
|
+
export declare function actionOf(hint: InlayHint, click: Pick<EditorInlayClick, 'part' | 'mod' | 'clicks'>): Action | null;
|
|
9
28
|
export declare class InlayHintsFeature {
|
|
29
|
+
private readonly language;
|
|
30
|
+
private readonly timers;
|
|
10
31
|
private readonly refresher;
|
|
11
32
|
private asked;
|
|
33
|
+
private readonly entries;
|
|
34
|
+
private shown;
|
|
35
|
+
private generation;
|
|
36
|
+
private epoch;
|
|
37
|
+
private target;
|
|
38
|
+
private modHeld;
|
|
39
|
+
private showTimer;
|
|
40
|
+
private commandTimer;
|
|
41
|
+
private pressCount;
|
|
42
|
+
private request;
|
|
12
43
|
constructor(language: EditorLanguage, timers?: Timers);
|
|
44
|
+
setModHeld(held: boolean): void;
|
|
45
|
+
showTooltipAt(position: EditorPosition): Promise<boolean>;
|
|
46
|
+
private install;
|
|
47
|
+
private hovered;
|
|
48
|
+
private cancelHover;
|
|
49
|
+
private cardOpen;
|
|
50
|
+
private showTooltip;
|
|
51
|
+
private resolve;
|
|
52
|
+
private resolvable;
|
|
53
|
+
private updateLink;
|
|
54
|
+
private pressed;
|
|
55
|
+
private press;
|
|
56
|
+
private perform;
|
|
13
57
|
}
|
|
58
|
+
export {};
|
package/dist/inlay-hints.js
CHANGED
|
@@ -1,20 +1,41 @@
|
|
|
1
|
+
import { hoverTextOf, isEmptyHover } from './hover-content.js';
|
|
2
|
+
import { SHOW_DELAY_MS } from './hover.js';
|
|
3
|
+
import { messageOf } from './error-message.js';
|
|
1
4
|
import { lineTextOf } from './line-text.js';
|
|
2
5
|
import { Refresher } from './refresher.js';
|
|
3
6
|
import { realTimers } from './timers.js';
|
|
4
7
|
const METHOD = 'textDocument/inlayHint';
|
|
8
|
+
const RESOLVE_METHOD = 'inlayHint/resolve';
|
|
9
|
+
const TOAST_ID = 'language-inlay-hint';
|
|
5
10
|
const PAUSE_MS = 300;
|
|
11
|
+
/* 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. */
|
|
12
|
+
const DOUBLE_PRESS_MS = 500;
|
|
6
13
|
/* Lines asked for above and below what is in view, so a little scrolling stays inside what is already drawn. */
|
|
7
14
|
const MARGIN_LINES = 60;
|
|
8
15
|
/* A hint's label is a string or a list of parts, which are read as one text. */
|
|
9
16
|
export function hintLabelOf(hint) {
|
|
10
17
|
return typeof hint.label === 'string' ? hint.label : hint.label.map((part) => part.value).join('');
|
|
11
18
|
}
|
|
12
|
-
|
|
19
|
+
/* The hints worth drawing, in the order of the text. */
|
|
20
|
+
export function drawableHints(hints) {
|
|
13
21
|
return hints
|
|
14
|
-
.
|
|
15
|
-
.filter((hint) => hint.label !== '')
|
|
22
|
+
.filter((hint) => hintLabelOf(hint) !== '')
|
|
16
23
|
.sort((left, right) => left.position.line - right.position.line || left.position.character - right.position.character);
|
|
17
24
|
}
|
|
25
|
+
/* A hint as the editor draws it: a label with parts keeps them apart so each can be pointed at. */
|
|
26
|
+
export function editorHintOf(hint, id) {
|
|
27
|
+
return {
|
|
28
|
+
id,
|
|
29
|
+
position: hint.position,
|
|
30
|
+
label: hintLabelOf(hint),
|
|
31
|
+
...(typeof hint.label === 'string' ? {} : { parts: hint.label.map((part) => part.value) }),
|
|
32
|
+
...(hint.paddingLeft ? { paddingLeft: true } : {}),
|
|
33
|
+
...(hint.paddingRight ? { paddingRight: true } : {})
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function editorHintsOf(hints) {
|
|
37
|
+
return drawableHints(hints).map((hint, index) => editorHintOf(hint, `hint-${index}`));
|
|
38
|
+
}
|
|
18
39
|
/* The lines in view with a margin of lines around them, from the start of the first to the end of the last. */
|
|
19
40
|
export function withMargin(visible, endOfDocument, endOfLine) {
|
|
20
41
|
const first = Math.max(0, visible.start.line - MARGIN_LINES);
|
|
@@ -25,16 +46,102 @@ export function withMargin(visible, endOfDocument, endOfLine) {
|
|
|
25
46
|
export function covers(asked, visible) {
|
|
26
47
|
return asked.start.line <= visible.start.line && asked.end.line >= visible.end.line;
|
|
27
48
|
}
|
|
49
|
+
/* The words of a tooltip: plain text is escaped so it reads as typed, Markdown goes through the hover card's own rendering. */
|
|
50
|
+
export function tooltipTextOf(tooltip) {
|
|
51
|
+
if (tooltip === undefined) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const text = hoverTextOf({ contents: typeof tooltip === 'string' ? { kind: 'plaintext', value: tooltip } : tooltip });
|
|
55
|
+
return isEmptyHover(text) ? null : text;
|
|
56
|
+
}
|
|
57
|
+
/* One part of a hint's label, or null for a hint whose label is a string. */
|
|
58
|
+
export function partOf(hint, index) {
|
|
59
|
+
return index === null || typeof hint.label === 'string' ? null : (hint.label[index] ?? null);
|
|
60
|
+
}
|
|
61
|
+
/* 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
|
+
export function tooltipOf(hint, index) {
|
|
63
|
+
const part = partOf(hint, index);
|
|
64
|
+
const own = part?.tooltip ?? hint.tooltip;
|
|
65
|
+
if (own !== undefined || index !== null || typeof hint.label === 'string') {
|
|
66
|
+
return tooltipTextOf(own);
|
|
67
|
+
}
|
|
68
|
+
return tooltipTextOf(hint.label.find((candidate) => candidate.tooltip !== undefined)?.tooltip);
|
|
69
|
+
}
|
|
70
|
+
/* Whether a server could still say more about the hint: nothing in it gives a tooltip, a place or a command yet. */
|
|
71
|
+
export function lacksInfo(hint, index) {
|
|
72
|
+
if (hint.tooltip !== undefined) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const parts = typeof hint.label === 'string' ? [] : index === null ? hint.label : [hint.label[index]];
|
|
76
|
+
return !parts.some((part) => part !== undefined && (part.tooltip !== undefined || part.location !== undefined || part.command !== undefined));
|
|
77
|
+
}
|
|
78
|
+
/* Whether the edits fit the text as it is now: positions inside it, each range in order, none overlapping the next. */
|
|
79
|
+
export function editsFit(editor, edits) {
|
|
80
|
+
const end = editor.positionAt(Number.MAX_SAFE_INTEGER);
|
|
81
|
+
const inside = (position) => Number.isInteger(position.line) &&
|
|
82
|
+
Number.isInteger(position.character) &&
|
|
83
|
+
position.line >= 0 &&
|
|
84
|
+
position.character >= 0 &&
|
|
85
|
+
position.line <= end.line &&
|
|
86
|
+
position.character <= lineTextOf(editor, position.line).length;
|
|
87
|
+
const before = (left, right) => left.line < right.line || (left.line === right.line && left.character <= right.character);
|
|
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
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/* 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
|
+
export function actionOf(hint, click) {
|
|
99
|
+
if (click.clicks === 2) {
|
|
100
|
+
return hint.textEdits !== undefined && hint.textEdits.length > 0 ? { kind: 'insert', edits: hint.textEdits } : null;
|
|
101
|
+
}
|
|
102
|
+
const part = partOf(hint, click.part);
|
|
103
|
+
if (click.mod) {
|
|
104
|
+
return part?.location === undefined ? null : { kind: 'go', location: part.location };
|
|
105
|
+
}
|
|
106
|
+
return part?.command === undefined ? null : { kind: 'run', command: part.command };
|
|
107
|
+
}
|
|
28
108
|
/*
|
|
29
109
|
* Types and parameter names the servers infer, asked for the lines in view and a margin around them. The
|
|
30
110
|
* last ones stay until the answer comes, so nothing blinks while typing; scrolling asks again only once the
|
|
31
111
|
* view leaves the lines that were asked for.
|
|
112
|
+
*
|
|
113
|
+
* A hint can be acted on with the pointer. The server is asked to resolve a hint as the pointer arrives, and
|
|
114
|
+
* resting on it for the hover delay shows its tooltip. Mod+click on a part follows its place, a click on a
|
|
115
|
+
* part runs its command and a double click inserts its text edits as one undo step, never together with the
|
|
116
|
+
* command: when a hint has both, the command waits for the double press interval. A press on a hint that
|
|
117
|
+
* offers nothing (yet) moves the caret as beside the text. A finger has no hover, so a tap shows the
|
|
118
|
+
* tooltip and does nothing more. All of it is for the pointer: the hints are not part
|
|
119
|
+
* of the text and stay out of the keyboard's way, and Quick Info on the caret shows the tooltip of a hint that
|
|
120
|
+
* sits there.
|
|
32
121
|
*/
|
|
33
122
|
export class InlayHintsFeature {
|
|
123
|
+
language;
|
|
124
|
+
timers;
|
|
34
125
|
refresher;
|
|
35
126
|
/* The lines the hints on screen were asked for. */
|
|
36
127
|
asked = null;
|
|
128
|
+
entries = new Map();
|
|
129
|
+
shown = [];
|
|
130
|
+
generation = 0;
|
|
131
|
+
/* Counts the edits of the text, which hints and answers made for an earlier one are tested against. */
|
|
132
|
+
epoch = 0;
|
|
133
|
+
target = null;
|
|
134
|
+
modHeld = false;
|
|
135
|
+
showTimer;
|
|
136
|
+
/* The command of a click that may still become a double click. */
|
|
137
|
+
commandTimer;
|
|
138
|
+
/* Counts presses, so one that waited for a resolve gives way to a later one. */
|
|
139
|
+
pressCount = 0;
|
|
140
|
+
/* Counts what the pointer does, so a tooltip that was waited for is dropped once the pointer has moved on. */
|
|
141
|
+
request = 0;
|
|
37
142
|
constructor(language, timers = realTimers) {
|
|
143
|
+
this.language = language;
|
|
144
|
+
this.timers = timers;
|
|
38
145
|
const { editor, project, uri } = language;
|
|
39
146
|
this.refresher = new Refresher(async (signal) => {
|
|
40
147
|
const { service } = project;
|
|
@@ -46,26 +153,256 @@ export class InlayHintsFeature {
|
|
|
46
153
|
character: lineTextOf(editor, line).length
|
|
47
154
|
}));
|
|
48
155
|
this.asked = range;
|
|
156
|
+
const epoch = this.epoch;
|
|
49
157
|
const hints = await service.inlayHints(uri, range, { signal });
|
|
50
|
-
if (!signal.aborted) {
|
|
51
|
-
|
|
158
|
+
if (!signal.aborted && epoch === this.epoch) {
|
|
159
|
+
this.install(hints === null ? [] : drawableHints(hints), epoch);
|
|
52
160
|
}
|
|
53
161
|
}, PAUSE_MS, timers);
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
162
|
+
const offs = [
|
|
163
|
+
editor.onTextChange(() => {
|
|
164
|
+
this.epoch++;
|
|
165
|
+
this.cancelHover();
|
|
57
166
|
this.refresher.later();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
167
|
+
}),
|
|
168
|
+
editor.onViewChange(() => {
|
|
169
|
+
if (this.asked !== null && !covers(this.asked, editor.getVisibleRange())) {
|
|
170
|
+
this.refresher.later();
|
|
171
|
+
}
|
|
172
|
+
}),
|
|
173
|
+
language.onProvidersChanged(() => {
|
|
174
|
+
this.refresher.now();
|
|
175
|
+
})
|
|
176
|
+
];
|
|
177
|
+
if (editor.onInlayHover !== undefined) {
|
|
178
|
+
offs.push(editor.onInlayHover((target) => this.hovered(target === null ? null : { id: target.id, part: target.part })));
|
|
179
|
+
}
|
|
180
|
+
if (editor.onInlayClick !== undefined) {
|
|
181
|
+
offs.push(editor.onInlayClick((click) => this.pressed(click)));
|
|
182
|
+
}
|
|
183
|
+
if (typeof window !== 'undefined') {
|
|
184
|
+
const modKey = language.apple ? 'Meta' : 'Control';
|
|
185
|
+
const keys = (event) => {
|
|
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
|
+
}
|
|
63
200
|
language.onDispose(() => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
201
|
+
for (const off of offs) {
|
|
202
|
+
off();
|
|
203
|
+
}
|
|
204
|
+
this.cancelHover();
|
|
205
|
+
this.timers.clear(this.commandTimer);
|
|
67
206
|
this.refresher.dispose();
|
|
207
|
+
this.entries.clear();
|
|
68
208
|
editor.setInlayHints([]);
|
|
69
209
|
});
|
|
70
210
|
}
|
|
211
|
+
/* The key that follows a place is down or up; it comes from the window, as the editor only says where the pointer is. */
|
|
212
|
+
setModHeld(held) {
|
|
213
|
+
if (held !== this.modHeld) {
|
|
214
|
+
this.modHeld = held;
|
|
215
|
+
this.updateLink();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/* 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. */
|
|
219
|
+
async showTooltipAt(position) {
|
|
220
|
+
const found = [...this.entries].find(([, entry]) => entry.epoch === this.epoch && sameSpot(entry.hint.position, position));
|
|
221
|
+
if (found === undefined) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
return this.showTooltip({ id: found[0], part: null }, this.request, false);
|
|
225
|
+
}
|
|
226
|
+
install(hints, epoch) {
|
|
227
|
+
this.generation++;
|
|
228
|
+
this.entries.clear();
|
|
229
|
+
this.shown = hints.map((hint, index) => {
|
|
230
|
+
const id = `hint-${this.generation}-${index}`;
|
|
231
|
+
this.entries.set(id, { id, hint, epoch, resolution: null, answered: false });
|
|
232
|
+
return editorHintOf(hint, id);
|
|
233
|
+
});
|
|
234
|
+
this.language.editor.setInlayHints(this.shown);
|
|
235
|
+
this.target = null;
|
|
236
|
+
this.updateLink();
|
|
237
|
+
}
|
|
238
|
+
hovered(target) {
|
|
239
|
+
this.target = target;
|
|
240
|
+
this.timers.clear(this.showTimer);
|
|
241
|
+
const request = ++this.request;
|
|
242
|
+
this.updateLink();
|
|
243
|
+
if (target === null) {
|
|
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
|
+
}
|
|
255
|
+
}
|
|
256
|
+
cancelHover() {
|
|
257
|
+
this.timers.clear(this.showTimer);
|
|
258
|
+
this.request++;
|
|
259
|
+
this.target = null;
|
|
260
|
+
this.updateLink();
|
|
261
|
+
if (this.cardOpen()) {
|
|
262
|
+
this.language.hover.hide();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/* Whether the card up is a tooltip, which is the only kind that has no footer. */
|
|
266
|
+
cardOpen() {
|
|
267
|
+
return this.language.popups.getState().hover?.info?.plain === true;
|
|
268
|
+
}
|
|
269
|
+
/* The card for a hint or part, once the server has said what it can. Says whether it opened. */
|
|
270
|
+
async showTooltip(target, request, pointer = true) {
|
|
271
|
+
const entry = this.entries.get(target.id);
|
|
272
|
+
if (entry === undefined) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
if (lacksInfo(entry.hint, target.part)) {
|
|
276
|
+
await this.resolve(entry);
|
|
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;
|
|
291
|
+
}
|
|
292
|
+
/* 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. */
|
|
293
|
+
resolve(entry) {
|
|
294
|
+
if (entry.resolution !== null) {
|
|
295
|
+
return entry.resolution;
|
|
296
|
+
}
|
|
297
|
+
const { service } = this.language.project;
|
|
298
|
+
if (entry.epoch !== this.epoch || !service.supports(RESOLVE_METHOD, this.language.uri)) {
|
|
299
|
+
return Promise.resolve(entry.hint);
|
|
300
|
+
}
|
|
301
|
+
entry.resolution = service
|
|
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;
|
|
314
|
+
}
|
|
315
|
+
/* Whether asking the server could still change what the hint offers. */
|
|
316
|
+
resolvable(entry) {
|
|
317
|
+
return !entry.answered && entry.epoch === this.epoch && this.language.project.service.supports(RESOLVE_METHOD, this.language.uri);
|
|
318
|
+
}
|
|
319
|
+
/* Underlines the part under the pointer when a press would do something with it: a command always, a place while the key is down. */
|
|
320
|
+
updateLink() {
|
|
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);
|
|
326
|
+
}
|
|
327
|
+
pressed(click) {
|
|
328
|
+
const entry = this.entries.get(click.id);
|
|
329
|
+
if (entry === undefined || entry.epoch !== this.epoch) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
if (click.touch) {
|
|
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);
|
|
356
|
+
}
|
|
357
|
+
press(entry, click) {
|
|
358
|
+
this.timers.clear(this.commandTimer);
|
|
359
|
+
const action = actionOf(entry.hint, click);
|
|
360
|
+
const insertable = entry.hint.textEdits !== undefined && entry.hint.textEdits.length > 0;
|
|
361
|
+
if (action?.kind === 'run' && insertable) {
|
|
362
|
+
// A double click inserts the edits, so the command of its first press waits to see whether a second one comes.
|
|
363
|
+
const epoch = this.epoch;
|
|
364
|
+
this.commandTimer = this.timers.set(() => {
|
|
365
|
+
if (epoch === this.epoch) {
|
|
366
|
+
void this.perform(entry, action);
|
|
367
|
+
}
|
|
368
|
+
}, DOUBLE_PRESS_MS);
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
if (action !== null) {
|
|
372
|
+
void this.perform(entry, action);
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
// The first press of a double click must not move the caret either, or the second would land beside the hint.
|
|
376
|
+
return click.clicks === 1 && !click.mod && insertable;
|
|
377
|
+
}
|
|
378
|
+
async perform(entry, action) {
|
|
379
|
+
const { editor, project, uri } = this.language;
|
|
380
|
+
try {
|
|
381
|
+
if (action.kind === 'go') {
|
|
382
|
+
this.cancelHover();
|
|
383
|
+
this.language.goTo(action.location);
|
|
384
|
+
}
|
|
385
|
+
else if (action.kind === 'run') {
|
|
386
|
+
await project.service.executeCommand(uri, action.command);
|
|
387
|
+
}
|
|
388
|
+
else if (entry.epoch === this.epoch && editsFit(editor, action.edits)) {
|
|
389
|
+
this.cancelHover();
|
|
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
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
this.language.project.host.notify?.({
|
|
399
|
+
id: TOAST_ID,
|
|
400
|
+
kind: 'error',
|
|
401
|
+
title: project.i18n.t('editor:language.inlay.failed', { message: messageOf(error) })
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function sameSpot(left, right) {
|
|
407
|
+
return left.line === right.line && left.character === right.character;
|
|
71
408
|
}
|