@adecore/editor-react 0.0.1 → 0.17.0-beta.2
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/LICENSE +110 -0
- package/README.md +60 -0
- package/dist/AnchoredPopup.d.ts +12 -0
- package/dist/AnchoredPopup.js +27 -0
- package/dist/AttributionCard.d.ts +14 -0
- package/dist/AttributionCard.js +5 -0
- package/dist/BaselineStatus.d.ts +4 -0
- package/dist/BaselineStatus.js +13 -0
- package/dist/ChangeReview.d.ts +12 -0
- package/dist/ChangeReview.js +27 -0
- package/dist/CodeAuthorsCard.d.ts +6 -0
- package/dist/CodeAuthorsCard.js +22 -0
- package/dist/CompletionPopup.d.ts +8 -0
- package/dist/CompletionPopup.js +103 -0
- package/dist/EditorContextMenu.d.ts +8 -0
- package/dist/EditorContextMenu.js +23 -0
- package/dist/EditorView.d.ts +16 -0
- package/dist/EditorView.js +27 -0
- package/dist/FindReplace.d.ts +6 -0
- package/dist/FindReplace.js +44 -0
- package/dist/HoverCard.d.ts +11 -0
- package/dist/HoverCard.js +38 -0
- package/dist/HoverSections.d.ts +21 -0
- package/dist/HoverSections.js +82 -0
- package/dist/LanguagePopups.d.ts +6 -0
- package/dist/LanguagePopups.js +38 -0
- package/dist/Markdown.d.ts +3 -0
- package/dist/Markdown.js +7 -0
- package/dist/PathText.d.ts +4 -0
- package/dist/PathText.js +12 -0
- package/dist/PeekPanel.d.ts +6 -0
- package/dist/PeekPanel.js +111 -0
- package/dist/PickPopup.d.ts +8 -0
- package/dist/PickPopup.js +33 -0
- package/dist/RenameCard.d.ts +9 -0
- package/dist/RenameCard.js +108 -0
- package/dist/SignatureCard.d.ts +4 -0
- package/dist/SignatureCard.js +8 -0
- package/dist/SymbolPicker.d.ts +6 -0
- package/dist/SymbolPicker.js +108 -0
- package/dist/code-actions-model.d.ts +23 -0
- package/dist/code-actions-model.js +114 -0
- package/dist/code-actions.d.ts +30 -0
- package/dist/code-actions.js +326 -0
- package/dist/code-authors.d.ts +18 -0
- package/dist/code-authors.js +79 -0
- package/dist/code-vision-model.d.ts +21 -0
- package/dist/code-vision-model.js +183 -0
- package/dist/code-vision.d.ts +48 -0
- package/dist/code-vision.js +236 -0
- package/dist/completion-call.d.ts +24 -0
- package/dist/completion-call.js +110 -0
- package/dist/completion-model.d.ts +51 -0
- package/dist/completion-model.js +353 -0
- package/dist/completion.d.ts +43 -0
- package/dist/completion.js +463 -0
- package/dist/conflict-model.d.ts +15 -0
- package/dist/conflict-model.js +36 -0
- package/dist/context-menu.d.ts +10 -0
- package/dist/context-menu.js +55 -0
- package/dist/definition-link.d.ts +14 -0
- package/dist/definition-link.js +99 -0
- package/dist/diagnostics-model.d.ts +15 -0
- package/dist/diagnostics-model.js +83 -0
- package/dist/diagnostics.d.ts +23 -0
- package/dist/diagnostics.js +95 -0
- package/dist/editor-language.d.ts +65 -0
- package/dist/editor-language.js +170 -0
- package/dist/editor-react.css +43 -0
- package/dist/find-query.d.ts +26 -0
- package/dist/find-query.js +51 -0
- package/dist/folding.d.ts +16 -0
- package/dist/folding.js +95 -0
- package/dist/highlight-layers.d.ts +10 -0
- package/dist/highlight-layers.js +30 -0
- package/dist/highlights.d.ts +16 -0
- package/dist/highlights.js +102 -0
- package/dist/history.d.ts +8 -0
- package/dist/history.js +46 -0
- package/dist/holder.d.ts +5 -0
- package/dist/holder.js +19 -0
- package/dist/host-types.d.ts +45 -0
- package/dist/host-types.js +1 -0
- package/dist/hover-content.d.ts +45 -0
- package/dist/hover-content.js +198 -0
- package/dist/hover.d.ts +32 -0
- package/dist/hover.js +260 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +28 -0
- package/dist/inlay-hints.d.ts +13 -0
- package/dist/inlay-hints.js +71 -0
- package/dist/language-ids.d.ts +3 -0
- package/dist/language-ids.js +49 -0
- package/dist/line-edits.d.ts +8 -0
- package/dist/line-edits.js +39 -0
- package/dist/locales/en.json +171 -0
- package/dist/locales/nl.json +171 -0
- package/dist/models.d.ts +20 -0
- package/dist/models.js +20 -0
- package/dist/navigation-history.d.ts +16 -0
- package/dist/navigation-history.js +53 -0
- package/dist/navigation.d.ts +23 -0
- package/dist/navigation.js +155 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +3 -0
- package/dist/peek-model.d.ts +28 -0
- package/dist/peek-model.js +104 -0
- package/dist/peek.d.ts +30 -0
- package/dist/peek.js +215 -0
- package/dist/pick.d.ts +34 -0
- package/dist/pick.js +166 -0
- package/dist/popup-placement.d.ts +23 -0
- package/dist/popup-placement.js +34 -0
- package/dist/popups.d.ts +133 -0
- package/dist/popups.js +14 -0
- package/dist/project-language.d.ts +36 -0
- package/dist/project-language.js +190 -0
- package/dist/project-problems.d.ts +24 -0
- package/dist/project-problems.js +63 -0
- package/dist/proposal-model.d.ts +36 -0
- package/dist/proposal-model.js +197 -0
- package/dist/recent-choices.d.ts +10 -0
- package/dist/recent-choices.js +39 -0
- package/dist/refresher.d.ts +13 -0
- package/dist/refresher.js +50 -0
- package/dist/relative-time.d.ts +1 -0
- package/dist/relative-time.js +9 -0
- package/dist/rename-model.d.ts +25 -0
- package/dist/rename-model.js +93 -0
- package/dist/rename.d.ts +21 -0
- package/dist/rename.js +257 -0
- package/dist/rendering-context.d.ts +6 -0
- package/dist/rendering-context.js +5 -0
- package/dist/rendering.d.ts +6 -0
- package/dist/rendering.js +5 -0
- package/dist/review-group.d.ts +16 -0
- package/dist/review-group.js +54 -0
- package/dist/row-host.d.ts +42 -0
- package/dist/row-host.js +120 -0
- package/dist/selection-ranges.d.ts +7 -0
- package/dist/selection-ranges.js +26 -0
- package/dist/semantic-model.d.ts +4 -0
- package/dist/semantic-model.js +76 -0
- package/dist/semantic-tokens.d.ts +6 -0
- package/dist/semantic-tokens.js +38 -0
- package/dist/shortcut-keys.d.ts +91 -0
- package/dist/shortcut-keys.js +12 -0
- package/dist/signature-model.d.ts +18 -0
- package/dist/signature-model.js +33 -0
- package/dist/signature.d.ts +22 -0
- package/dist/signature.js +150 -0
- package/dist/snippet-session.d.ts +17 -0
- package/dist/snippet-session.js +110 -0
- package/dist/snippet.d.ts +11 -0
- package/dist/snippet.js +134 -0
- package/dist/symbol-links.d.ts +6 -0
- package/dist/symbol-links.js +50 -0
- package/dist/symbol-picker-model.d.ts +44 -0
- package/dist/symbol-picker-model.js +146 -0
- package/dist/symbol-picker.d.ts +12 -0
- package/dist/symbol-picker.js +76 -0
- package/dist/symbols.d.ts +11 -0
- package/dist/symbols.js +95 -0
- package/dist/testing.d.ts +57 -0
- package/dist/testing.js +156 -0
- package/dist/timers.d.ts +14 -0
- package/dist/timers.js +34 -0
- package/dist/use-problem-counts.d.ts +3 -0
- package/dist/use-problem-counts.js +20 -0
- package/dist/view-state.d.ts +21 -0
- package/dist/view-state.js +63 -0
- package/dist/word-diff.d.ts +1 -0
- package/dist/word-diff.js +32 -0
- package/dist/workspace-edit.d.ts +21 -0
- package/dist/workspace-edit.js +147 -0
- package/package.json +74 -2
- package/src/AnchoredPopup.tsx +51 -0
- package/src/AttributionCard.tsx +36 -0
- package/src/BaselineStatus.tsx +20 -0
- package/src/ChangeReview.tsx +43 -0
- package/src/CodeAuthorsCard.tsx +81 -0
- package/src/CompletionPopup.tsx +196 -0
- package/src/EditorContextMenu.tsx +67 -0
- package/src/EditorView.tsx +47 -0
- package/src/FindReplace.tsx +89 -0
- package/src/HoverCard.tsx +121 -0
- package/src/HoverSections.tsx +146 -0
- package/src/LanguagePopups.tsx +65 -0
- package/src/Markdown.tsx +11 -0
- package/src/PathText.tsx +18 -0
- package/src/PeekPanel.tsx +222 -0
- package/src/PickPopup.tsx +102 -0
- package/src/RenameCard.tsx +219 -0
- package/src/SignatureCard.tsx +42 -0
- package/src/SymbolPicker.tsx +219 -0
- package/src/code-actions-model.ts +146 -0
- package/src/code-actions.ts +384 -0
- package/src/code-authors.ts +104 -0
- package/src/code-vision-model.ts +230 -0
- package/src/code-vision.ts +269 -0
- package/src/completion-call.ts +139 -0
- package/src/completion-model.ts +440 -0
- package/src/completion.ts +500 -0
- package/src/conflict-model.ts +56 -0
- package/src/context-menu.ts +63 -0
- package/src/definition-link.ts +106 -0
- package/src/diagnostics-model.ts +102 -0
- package/src/diagnostics.ts +117 -0
- package/src/editor-language.ts +189 -0
- package/src/editor-react.css +43 -0
- package/src/find-query.ts +74 -0
- package/src/folding.ts +110 -0
- package/src/highlight-layers.ts +37 -0
- package/src/highlights.ts +123 -0
- package/src/history.ts +56 -0
- package/src/holder.ts +19 -0
- package/src/host-types.ts +52 -0
- package/src/hover-content.ts +264 -0
- package/src/hover.ts +280 -0
- package/src/index.ts +63 -0
- package/src/inlay-hints.ts +85 -0
- package/src/language-ids.ts +54 -0
- package/src/line-edits.ts +48 -0
- package/src/locales/en.json +171 -0
- package/src/locales/nl.json +171 -0
- package/src/models.ts +157 -0
- package/src/navigation-history.ts +69 -0
- package/src/navigation.ts +168 -0
- package/src/paths.ts +3 -0
- package/src/peek-model.ts +147 -0
- package/src/peek.ts +247 -0
- package/src/pick.ts +197 -0
- package/src/popup-placement.ts +64 -0
- package/src/popups.ts +204 -0
- package/src/project-language.ts +223 -0
- package/src/project-problems.ts +87 -0
- package/src/proposal-model.ts +259 -0
- package/src/recent-choices.ts +48 -0
- package/src/refresher.ts +56 -0
- package/src/relative-time.ts +10 -0
- package/src/rename-model.ts +130 -0
- package/src/rename.ts +290 -0
- package/src/rendering-context.ts +12 -0
- package/src/rendering.tsx +6 -0
- package/src/review-group.ts +70 -0
- package/src/row-host.ts +158 -0
- package/src/selection-ranges.ts +32 -0
- package/src/semantic-model.ts +80 -0
- package/src/semantic-tokens.ts +46 -0
- package/src/shortcut-keys.ts +21 -0
- package/src/signature-model.ts +49 -0
- package/src/signature.ts +164 -0
- package/src/snippet-session.ts +122 -0
- package/src/snippet.ts +147 -0
- package/src/symbol-links.ts +72 -0
- package/src/symbol-picker-model.ts +198 -0
- package/src/symbol-picker.ts +85 -0
- package/src/symbols.ts +110 -0
- package/src/testing.ts +240 -0
- package/src/timers.ts +45 -0
- package/src/use-problem-counts.ts +25 -0
- package/src/view-state.ts +88 -0
- package/src/word-diff.ts +35 -0
- package/src/workspace-edit.ts +194 -0
package/src/pick.ts
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import type { EditorPosition } from '@adecore/editor';
|
|
2
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
3
|
+
import type { PickGroup, PickPreview } from './popups.ts';
|
|
4
|
+
import { realTimers, type Timers } from './timers.ts';
|
|
5
|
+
|
|
6
|
+
const PREVIEW_DELAY_MS = 120;
|
|
7
|
+
|
|
8
|
+
export interface PickSpec {
|
|
9
|
+
readonly anchor: EditorPosition;
|
|
10
|
+
readonly title?: string;
|
|
11
|
+
readonly groups: readonly PickGroup[];
|
|
12
|
+
/* The row the list opens on; the first one without it. */
|
|
13
|
+
readonly active?: string;
|
|
14
|
+
accept(id: string): void;
|
|
15
|
+
/*
|
|
16
|
+
* Asked before the list closes on a row. A reason it cannot be taken keeps the list open with the reason
|
|
17
|
+
* under it, the way a rename keeps its input; null lets the list close and `accept` run.
|
|
18
|
+
*/
|
|
19
|
+
attempt?(id: string): Promise<string | null>;
|
|
20
|
+
/* What the active row would change, asked a moment after it became active. */
|
|
21
|
+
preview?(id: string, signal: AbortSignal): Promise<PickPreview | null>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* The one list an editor has open under a character, for code actions, the places a name is defined and
|
|
26
|
+
* the like. The editor keeps the focus: arrows move through the rows, Enter takes the active one and
|
|
27
|
+
* Escape, typing, a click elsewhere or a change of the text close it.
|
|
28
|
+
*/
|
|
29
|
+
export class PickFeature {
|
|
30
|
+
private readonly language: EditorLanguage;
|
|
31
|
+
private readonly timers: Timers;
|
|
32
|
+
private spec: PickSpec | null = null;
|
|
33
|
+
private ids: readonly string[] = [];
|
|
34
|
+
private active = '';
|
|
35
|
+
private previewTimer: unknown;
|
|
36
|
+
private controller: AbortController | null = null;
|
|
37
|
+
/* The rows that said why they cannot be taken, by id. */
|
|
38
|
+
private refusals = new Map<string, string>();
|
|
39
|
+
private attempting = false;
|
|
40
|
+
|
|
41
|
+
constructor(language: EditorLanguage, timers: Timers = realTimers) {
|
|
42
|
+
this.language = language;
|
|
43
|
+
this.timers = timers;
|
|
44
|
+
const { editor } = language;
|
|
45
|
+
const offs = [
|
|
46
|
+
editor.onKeyDown((event) => this.key(event)),
|
|
47
|
+
editor.onTextChange(() => this.close()),
|
|
48
|
+
editor.onCaret(() => this.close()),
|
|
49
|
+
editor.onBlur(() => this.close())
|
|
50
|
+
];
|
|
51
|
+
language.onDispose(() => {
|
|
52
|
+
for (const off of offs) {
|
|
53
|
+
off();
|
|
54
|
+
}
|
|
55
|
+
this.close();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get isOpen(): boolean {
|
|
60
|
+
return this.spec !== null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
open(spec: PickSpec): void {
|
|
64
|
+
this.close();
|
|
65
|
+
const ids = spec.groups.flatMap((group) => group.rows.map((row) => row.id));
|
|
66
|
+
if (ids.length === 0) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.spec = spec;
|
|
70
|
+
this.refusals = new Map();
|
|
71
|
+
this.ids = ids;
|
|
72
|
+
this.active = spec.active !== undefined && ids.includes(spec.active) ? spec.active : ids[0]!;
|
|
73
|
+
this.publish(null);
|
|
74
|
+
this.schedulePreview();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
close(): void {
|
|
78
|
+
if (this.spec === null) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this.spec = null;
|
|
82
|
+
this.timers.clear(this.previewTimer);
|
|
83
|
+
this.controller?.abort();
|
|
84
|
+
this.controller = null;
|
|
85
|
+
this.language.popups.setState({ pick: null });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
move(delta: number): void {
|
|
89
|
+
const index = this.ids.indexOf(this.active);
|
|
90
|
+
this.activate(this.ids[Math.max(0, Math.min(this.ids.length - 1, index + delta))]!);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Takes a row, by a click or Enter. */
|
|
94
|
+
choose(id: string): void {
|
|
95
|
+
const spec = this.spec;
|
|
96
|
+
if (spec === null || !this.ids.includes(id) || this.attempting) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (spec.attempt === undefined) {
|
|
100
|
+
this.close();
|
|
101
|
+
spec.accept(id);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
this.attempting = true;
|
|
105
|
+
void spec
|
|
106
|
+
.attempt(id)
|
|
107
|
+
.catch(() => null)
|
|
108
|
+
.then((reason) => {
|
|
109
|
+
this.attempting = false;
|
|
110
|
+
if (this.spec !== spec) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (reason === null) {
|
|
114
|
+
this.close();
|
|
115
|
+
spec.accept(id);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
this.refusals.set(id, reason);
|
|
119
|
+
this.publish(null);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private activate(id: string): void {
|
|
124
|
+
if (id === this.active) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
this.active = id;
|
|
128
|
+
this.controller?.abort();
|
|
129
|
+
this.publish(null);
|
|
130
|
+
this.schedulePreview();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private key(event: KeyboardEvent): boolean {
|
|
134
|
+
if (this.spec === null || event.metaKey || event.ctrlKey || event.altKey) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
switch (event.key) {
|
|
138
|
+
case 'ArrowDown':
|
|
139
|
+
this.move(1);
|
|
140
|
+
return true;
|
|
141
|
+
case 'ArrowUp':
|
|
142
|
+
this.move(-1);
|
|
143
|
+
return true;
|
|
144
|
+
case 'PageDown':
|
|
145
|
+
this.move(8);
|
|
146
|
+
return true;
|
|
147
|
+
case 'PageUp':
|
|
148
|
+
this.move(-8);
|
|
149
|
+
return true;
|
|
150
|
+
case 'Home':
|
|
151
|
+
this.activate(this.ids[0]!);
|
|
152
|
+
return true;
|
|
153
|
+
case 'End':
|
|
154
|
+
this.activate(this.ids[this.ids.length - 1]!);
|
|
155
|
+
return true;
|
|
156
|
+
case 'Enter':
|
|
157
|
+
case 'Tab':
|
|
158
|
+
this.choose(this.active);
|
|
159
|
+
return true;
|
|
160
|
+
case 'Escape':
|
|
161
|
+
this.close();
|
|
162
|
+
return true;
|
|
163
|
+
default:
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private schedulePreview(): void {
|
|
169
|
+
const spec = this.spec;
|
|
170
|
+
this.timers.clear(this.previewTimer);
|
|
171
|
+
if (spec?.preview === undefined) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const id = this.active;
|
|
175
|
+
this.previewTimer = this.timers.set(() => {
|
|
176
|
+
const controller = new AbortController();
|
|
177
|
+
this.controller = controller;
|
|
178
|
+
spec.preview!(id, controller.signal)
|
|
179
|
+
.then((preview) => {
|
|
180
|
+
if (this.spec === spec && this.active === id && !controller.signal.aborted) {
|
|
181
|
+
this.publish(preview);
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
.catch(() => undefined);
|
|
185
|
+
}, PREVIEW_DELAY_MS);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private publish(preview: PickPreview | null): void {
|
|
189
|
+
const spec = this.spec;
|
|
190
|
+
if (spec === null) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const refusal = this.refusals.get(this.active);
|
|
194
|
+
const shown = refusal === undefined ? preview : { removed: [], added: [], note: null, refusal };
|
|
195
|
+
this.language.popups.setState({ pick: { anchor: spec.anchor, groups: spec.groups, active: this.active, preview: shown, title: spec.title ?? null } });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { EditorRect } from '@adecore/editor';
|
|
2
|
+
|
|
3
|
+
export interface PopupSize {
|
|
4
|
+
readonly width: number;
|
|
5
|
+
readonly height: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface PopupPlacement {
|
|
9
|
+
readonly left: number;
|
|
10
|
+
readonly top: number;
|
|
11
|
+
/* What is left of the window on the chosen side, for a popup that scrolls inside it. */
|
|
12
|
+
readonly maxHeight: number;
|
|
13
|
+
readonly side: 'above' | 'below';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PlaceOptions {
|
|
17
|
+
/* The space between the popup and the character it belongs to. */
|
|
18
|
+
gap?: number;
|
|
19
|
+
/* The space kept to the window's edge. */
|
|
20
|
+
margin?: number;
|
|
21
|
+
prefer?: 'above' | 'below';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* Where a popup goes next to a character: under it when it fits there, over it when it does not and
|
|
26
|
+
* there is room, and otherwise on the side with more room, shortened to fit. Aligned with the
|
|
27
|
+
* character's left edge, and pushed back in when that would leave the window. The anchor and the window
|
|
28
|
+
* are the page's own pixels, so a canvas's scale never enters.
|
|
29
|
+
*/
|
|
30
|
+
export function placePopup(anchor: EditorRect, size: PopupSize, window: PopupSize, options: PlaceOptions = {}): PopupPlacement {
|
|
31
|
+
const gap = options.gap ?? 4;
|
|
32
|
+
const margin = options.margin ?? 8;
|
|
33
|
+
const roomBelow = window.height - margin - (anchor.bottom + gap);
|
|
34
|
+
const roomAbove = anchor.top - gap - margin;
|
|
35
|
+
const fitsBelow = size.height <= roomBelow;
|
|
36
|
+
const fitsAbove = size.height <= roomAbove;
|
|
37
|
+
const below = options.prefer === 'above' ? !fitsAbove && (fitsBelow || roomBelow >= roomAbove) : fitsBelow || (!fitsAbove && roomBelow >= roomAbove);
|
|
38
|
+
const maxHeight = Math.max(0, below ? roomBelow : roomAbove);
|
|
39
|
+
const height = Math.min(size.height, maxHeight);
|
|
40
|
+
const left = Math.max(margin, Math.min(anchor.left, window.width - size.width - margin));
|
|
41
|
+
return { left, top: below ? anchor.bottom + gap : anchor.top - gap - height, maxHeight, side: below ? 'below' : 'above' };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/*
|
|
45
|
+
* A second box beside a first, such as the documentation of the highlighted suggestion: to the right
|
|
46
|
+
* of it when it fits, to the left when only that fits, and under it, at its left edge, when neither does.
|
|
47
|
+
*/
|
|
48
|
+
export function placeBeside(
|
|
49
|
+
first: { left: number; top: number; width: number; height: number },
|
|
50
|
+
size: PopupSize,
|
|
51
|
+
window: PopupSize,
|
|
52
|
+
margin = 8,
|
|
53
|
+
gap = 4
|
|
54
|
+
): PopupPlacement {
|
|
55
|
+
const fitsRight = first.left + first.width + gap + size.width <= window.width - margin;
|
|
56
|
+
const fitsLeft = first.left - gap - size.width >= margin;
|
|
57
|
+
if (fitsRight || fitsLeft) {
|
|
58
|
+
const top = Math.max(margin, Math.min(first.top, window.height - size.height - margin));
|
|
59
|
+
return { left: fitsRight ? first.left + first.width + gap : first.left - gap - size.width, top, maxHeight: window.height - margin * 2, side: 'below' };
|
|
60
|
+
}
|
|
61
|
+
const top = first.top + first.height + gap;
|
|
62
|
+
const maxHeight = Math.max(0, window.height - margin - top);
|
|
63
|
+
return { left: Math.max(margin, Math.min(first.left, window.width - size.width - margin)), top, maxHeight, side: 'below' };
|
|
64
|
+
}
|
package/src/popups.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { createStore, type StoreApi } from 'zustand';
|
|
2
|
+
import type { EditorPosition, EditorRange, EditorRect } from '@adecore/editor';
|
|
3
|
+
import type { Location } from '@adecore/lsp';
|
|
4
|
+
import type { CodeAuthorship } from './code-authors.ts';
|
|
5
|
+
import type { Problem } from './diagnostics-model.ts';
|
|
6
|
+
import type { PeekFile, PeekSnippet } from './peek-model.ts';
|
|
7
|
+
import type { ActionEntry } from './code-actions-model.ts';
|
|
8
|
+
import type { SymbolEntry } from './symbol-picker-model.ts';
|
|
9
|
+
import type { HoverText } from './hover-content.ts';
|
|
10
|
+
import type { SignatureViewModel } from './signature-model.ts';
|
|
11
|
+
|
|
12
|
+
/* What a hover card shows for the character under the pointer. */
|
|
13
|
+
export interface HoverView {
|
|
14
|
+
/* The card sits under this character, or over it when there is no room below. */
|
|
15
|
+
readonly anchor: EditorPosition;
|
|
16
|
+
/* The character the pointer rests on, which a count of references and a peek are asked at. */
|
|
17
|
+
readonly position: EditorPosition;
|
|
18
|
+
/* While the pointer stays in this range the card stays, so moving along a word does not make it blink. */
|
|
19
|
+
readonly subject: EditorRange;
|
|
20
|
+
readonly problems: readonly Problem[];
|
|
21
|
+
/* What the servers say about the symbol, and where it is defined. */
|
|
22
|
+
readonly info: HoverInfo | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface HoverInfo {
|
|
26
|
+
readonly text: HoverText;
|
|
27
|
+
readonly definition: Location | null;
|
|
28
|
+
/* The name under the pointer, which a signature's own symbol is told by. */
|
|
29
|
+
readonly word: string;
|
|
30
|
+
/* How many other places use the symbol, once a server has counted them. */
|
|
31
|
+
readonly references: number | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* One line of the suggestions list. */
|
|
35
|
+
export interface CompletionRow {
|
|
36
|
+
readonly label: string;
|
|
37
|
+
readonly kind: number | undefined;
|
|
38
|
+
/* The parameters the server puts after the label. */
|
|
39
|
+
readonly detail: string;
|
|
40
|
+
/* Where the item comes from, such as the module or namespace of an import, right-aligned. */
|
|
41
|
+
readonly description: string;
|
|
42
|
+
/* UTF-16 offsets of the characters of the label that match what was typed. */
|
|
43
|
+
readonly matches: readonly number[];
|
|
44
|
+
readonly deprecated: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* What is said about the active suggestion beside the list. */
|
|
48
|
+
export interface CompletionDocs {
|
|
49
|
+
/* Where it comes from, such as `use Raxos\\Orm\\Property` or `Auto import from './lib.ts'`; one line, empty when the server says nothing. */
|
|
50
|
+
readonly source: string;
|
|
51
|
+
/* The signature, prose and tags the same way a hover has them. */
|
|
52
|
+
readonly text: HoverText;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* The suggestions while a word is being typed. */
|
|
56
|
+
export interface CompletionView {
|
|
57
|
+
/* The start of the word, which the list is placed under. */
|
|
58
|
+
readonly anchor: EditorPosition;
|
|
59
|
+
readonly rows: readonly CompletionRow[];
|
|
60
|
+
readonly active: number;
|
|
61
|
+
/* Whether the documentation of the active row is shown beside the list. */
|
|
62
|
+
readonly detailsOpen: boolean;
|
|
63
|
+
readonly docs: CompletionDocs | null;
|
|
64
|
+
/* The server the suggestions came from. */
|
|
65
|
+
readonly server: string;
|
|
66
|
+
/* The shiki id of the file's language, for the signature in the documentation. */
|
|
67
|
+
readonly highlightLanguage: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* The signature of the call the caret is in. */
|
|
71
|
+
export interface SignatureView {
|
|
72
|
+
/* Where the call opened, so the card stays put while the arguments are typed. */
|
|
73
|
+
readonly anchor: EditorPosition;
|
|
74
|
+
readonly model: SignatureViewModel;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* One line of a list the person picks from, such as a code action or a place a name is defined. */
|
|
78
|
+
export interface PickRow {
|
|
79
|
+
readonly id: string;
|
|
80
|
+
readonly label: string;
|
|
81
|
+
/* What stands at the end of the line, such as a folder. */
|
|
82
|
+
readonly detail: string;
|
|
83
|
+
/* The detail is a path, which gives way from its start before the label gives way at all. */
|
|
84
|
+
readonly path?: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface PickGroup {
|
|
88
|
+
readonly title: string | null;
|
|
89
|
+
readonly rows: readonly PickRow[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* The change an action would make, as the lines it takes away and the lines it puts in their place. */
|
|
93
|
+
export interface PickPreview {
|
|
94
|
+
readonly removed: readonly string[];
|
|
95
|
+
readonly added: readonly string[];
|
|
96
|
+
/* Other places the change reaches, such as the files besides this one. */
|
|
97
|
+
readonly note: string | null;
|
|
98
|
+
/* Why the action cannot be taken here, which the server said; nothing would change. */
|
|
99
|
+
readonly refusal?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* A list under a character of the editor; Enter takes the active row, Escape closes it. */
|
|
103
|
+
export interface PickView {
|
|
104
|
+
readonly anchor: EditorPosition;
|
|
105
|
+
readonly groups: readonly PickGroup[];
|
|
106
|
+
readonly active: string;
|
|
107
|
+
readonly preview: PickPreview | null;
|
|
108
|
+
readonly title: string | null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* One file of a rename in the preview: the lines it changes. */
|
|
112
|
+
export interface RenameFileView {
|
|
113
|
+
readonly uri: string;
|
|
114
|
+
readonly rows: readonly { readonly line: number; readonly before: string; readonly after: string }[];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/*
|
|
118
|
+
* The input over the symbol being renamed. In the preview phase the new name stays in the input,
|
|
119
|
+
* read only, and the lines the rename changes are listed under it until Enter applies them.
|
|
120
|
+
*/
|
|
121
|
+
export interface RenameView {
|
|
122
|
+
readonly phase: 'input' | 'preview';
|
|
123
|
+
readonly range: EditorRange;
|
|
124
|
+
/* The name in the text, and the one the input starts with. */
|
|
125
|
+
readonly original: string;
|
|
126
|
+
readonly placeholder: string;
|
|
127
|
+
/* How many places the name has, once the servers said. */
|
|
128
|
+
readonly occurrences: { readonly count: number; readonly files: number } | null;
|
|
129
|
+
/* A request is out, so the input waits. */
|
|
130
|
+
readonly busy: boolean;
|
|
131
|
+
/* Why the last try failed, shown under the input, which stays open for another. */
|
|
132
|
+
readonly error: string | null;
|
|
133
|
+
readonly name: string;
|
|
134
|
+
readonly files: readonly RenameFileView[];
|
|
135
|
+
/* Names the host suggests from how the symbol is used. They fill the input when chosen and never apply by themselves. */
|
|
136
|
+
readonly suggestions: readonly string[];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* The references of a name between the lines of the file: a list of places and the code around the one that is active. */
|
|
140
|
+
export interface PeekView {
|
|
141
|
+
/* What the places are: where the name is used, or where it is defined. */
|
|
142
|
+
readonly kind: 'references' | 'definitions';
|
|
143
|
+
/* The row the editor draws it in, which the editor makes again when it scrolls back into view. */
|
|
144
|
+
readonly container: HTMLElement | null;
|
|
145
|
+
readonly files: readonly PeekFile[];
|
|
146
|
+
readonly active: string;
|
|
147
|
+
/* The files whose places the list leaves out. */
|
|
148
|
+
readonly collapsed: readonly string[];
|
|
149
|
+
readonly count: number;
|
|
150
|
+
readonly preview: (PeekSnippet & { readonly uri: string }) | null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* The picker that jumps to a symbol of the file, over the symbols the servers know in it. */
|
|
154
|
+
export interface SymbolsView {
|
|
155
|
+
/* The file's name, for the placeholder. */
|
|
156
|
+
readonly file: string;
|
|
157
|
+
readonly entries: readonly SymbolEntry[];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* The context menu of the editor, asked for at a point of the page. */
|
|
161
|
+
export interface MenuView {
|
|
162
|
+
readonly x: number;
|
|
163
|
+
readonly y: number;
|
|
164
|
+
/* The refactors the servers offer at the caret, once they have said. */
|
|
165
|
+
readonly refactors: readonly ActionEntry[];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* The card a press on the authors of a declaration opens beside the entry. */
|
|
169
|
+
export interface AuthorsView {
|
|
170
|
+
/* Where the entry stood when it was pressed, in the page's pixels. */
|
|
171
|
+
readonly anchor: EditorRect;
|
|
172
|
+
readonly name: string;
|
|
173
|
+
readonly authorship: CodeAuthorship;
|
|
174
|
+
/* Opens the latest commit in the git panel; null where there is no panel to open it in. */
|
|
175
|
+
readonly openCommit: (() => void) | null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface PopupState {
|
|
179
|
+
readonly hover: HoverView | null;
|
|
180
|
+
readonly completion: CompletionView | null;
|
|
181
|
+
readonly signature: SignatureView | null;
|
|
182
|
+
readonly pick: PickView | null;
|
|
183
|
+
readonly rename: RenameView | null;
|
|
184
|
+
readonly peek: PeekView | null;
|
|
185
|
+
readonly symbols: SymbolsView | null;
|
|
186
|
+
readonly menu: MenuView | null;
|
|
187
|
+
readonly authors: AuthorsView | null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export type PopupStore = StoreApi<PopupState>;
|
|
191
|
+
|
|
192
|
+
export function createPopupStore(): PopupStore {
|
|
193
|
+
return createStore<PopupState>(() => ({
|
|
194
|
+
hover: null,
|
|
195
|
+
completion: null,
|
|
196
|
+
signature: null,
|
|
197
|
+
pick: null,
|
|
198
|
+
rename: null,
|
|
199
|
+
peek: null,
|
|
200
|
+
symbols: null,
|
|
201
|
+
menu: null,
|
|
202
|
+
authors: null
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import i18next, { type i18n } from 'i18next';
|
|
2
|
+
import { fileUriToPath, type ApplyWorkspaceEditResult, type ContentChange, type LanguageService, type WorkspaceEdit } from '@adecore/lsp';
|
|
3
|
+
import type { Editor, EditorPosition } from '@adecore/editor';
|
|
4
|
+
import { NavigationHistory, type Place } from './navigation-history.ts';
|
|
5
|
+
import { ProjectProblems } from './project-problems.ts';
|
|
6
|
+
import { applyWorkspaceEdit, type ProjectFiles } from './workspace-edit.ts';
|
|
7
|
+
import type { LanguageHost } from './host-types.ts';
|
|
8
|
+
|
|
9
|
+
export interface LanguageDocumentHandle {
|
|
10
|
+
readonly uri: string;
|
|
11
|
+
readonly service: LanguageService;
|
|
12
|
+
readonly ready: Promise<void>;
|
|
13
|
+
release(): void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class DocumentSync {
|
|
17
|
+
readonly ready: Promise<void>;
|
|
18
|
+
private pending: ContentChange[] = [];
|
|
19
|
+
private opened = false;
|
|
20
|
+
private disposed = false;
|
|
21
|
+
private readonly stop: () => void;
|
|
22
|
+
|
|
23
|
+
private readonly service: LanguageService;
|
|
24
|
+
private readonly uri: string;
|
|
25
|
+
readonly editor: Editor;
|
|
26
|
+
|
|
27
|
+
constructor(service: LanguageService, uri: string, languageId: string, editor: Editor) {
|
|
28
|
+
this.service = service;
|
|
29
|
+
this.uri = uri;
|
|
30
|
+
this.editor = editor;
|
|
31
|
+
this.stop = editor.onTextChange((change) => this.changed(change.changes));
|
|
32
|
+
this.ready = service.openDocument({ uri, languageId, text: editor.getText() }).then(() => {
|
|
33
|
+
this.opened = true;
|
|
34
|
+
if (!this.disposed) {
|
|
35
|
+
this.flush();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
// A failed open is also a document nothing can be asked of; the features' own requests say so.
|
|
39
|
+
this.ready.catch(() => undefined);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private changed(changes: readonly ContentChange[]): void {
|
|
43
|
+
this.pending.push(...changes);
|
|
44
|
+
if (this.opened) {
|
|
45
|
+
this.flush();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private flush(): void {
|
|
50
|
+
if (this.pending.length === 0) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const changes = this.pending;
|
|
54
|
+
this.pending = [];
|
|
55
|
+
void this.service.changeDocument(this.uri, changes).catch(() => undefined);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
dispose(): void {
|
|
59
|
+
this.disposed = true;
|
|
60
|
+
this.pending = [];
|
|
61
|
+
this.stop();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get text(): string {
|
|
65
|
+
return this.editor.getText();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface Holder {
|
|
70
|
+
readonly editors: Editor[];
|
|
71
|
+
sync: DocumentSync;
|
|
72
|
+
languageId: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/*
|
|
76
|
+
* A shared language service and document ownership for the editors of a project. Two editors on the same
|
|
77
|
+
* file hold one document, and only the first one sends changes, since both would report the same edit.
|
|
78
|
+
*/
|
|
79
|
+
export class ProjectLanguage {
|
|
80
|
+
readonly service: LanguageService;
|
|
81
|
+
readonly host: LanguageHost;
|
|
82
|
+
readonly i18n: i18n;
|
|
83
|
+
readonly folder: string;
|
|
84
|
+
readonly problems: ProjectProblems;
|
|
85
|
+
readonly history = new NavigationHistory();
|
|
86
|
+
private readonly holders = new Map<string, Holder>();
|
|
87
|
+
private readonly files: ProjectFiles | null;
|
|
88
|
+
private pendingCaret: Place | null = null;
|
|
89
|
+
private lastPlace: Place | null = null;
|
|
90
|
+
private disposed = false;
|
|
91
|
+
|
|
92
|
+
constructor(service: LanguageService, host: LanguageHost = {}) {
|
|
93
|
+
this.service = service;
|
|
94
|
+
this.host = host;
|
|
95
|
+
this.i18n = host.i18n ?? i18next;
|
|
96
|
+
this.folder = host.folder ?? '';
|
|
97
|
+
this.files = host.files ?? null;
|
|
98
|
+
this.problems = new ProjectProblems(service, (uri) => this.pathOfLocation(uri) ?? uri);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
pathOfLocation(uri: string): string | null {
|
|
102
|
+
if (this.host.pathOfUri !== undefined) {
|
|
103
|
+
return this.host.pathOfUri(uri);
|
|
104
|
+
}
|
|
105
|
+
const path = fileUriToPath(uri);
|
|
106
|
+
return path !== null && this.folder !== '' && path.startsWith(`${this.folder}/`) ? path.slice(this.folder.length + 1) : path;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Makes an edit of a language server: an open document takes it as one undo step and any other file gets an unsaved draft. */
|
|
110
|
+
applyWorkspaceEdit(edit: WorkspaceEdit): Promise<ApplyWorkspaceEditResult> {
|
|
111
|
+
return applyWorkspaceEdit(edit, { editorOf: (uri) => this.holders.get(uri)?.editors[0], files: this.files });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* The text of a file as it stands now: an open document's, else a draft's or the machine's; null when it is no text file. */
|
|
115
|
+
async readText(uri: string): Promise<string | null> {
|
|
116
|
+
const open = this.holders.get(uri)?.sync.text;
|
|
117
|
+
if (open !== undefined) {
|
|
118
|
+
return open;
|
|
119
|
+
}
|
|
120
|
+
const path = fileUriToPath(uri);
|
|
121
|
+
return path === null || this.files === null ? null : ((await this.files.read(path))?.text ?? null);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/*
|
|
125
|
+
* Opens the file of a place on its line. A file opens on a line only, so the column is kept here until the editor
|
|
126
|
+
* that shows the line asks for it with `takeCaret`.
|
|
127
|
+
*/
|
|
128
|
+
openPlace(place: Place): void {
|
|
129
|
+
this.pendingCaret = place;
|
|
130
|
+
this.host.openPlace?.(place);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Where an editor's caret stands now, so a jump from a surface without an editor, such as the Problems panel, still knows what it left. */
|
|
134
|
+
noteCaret(place: Place): void {
|
|
135
|
+
this.lastPlace = place;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Opens a place from outside any editor and remembers the caret that was last seen. */
|
|
139
|
+
jumpTo(place: Place): void {
|
|
140
|
+
if (this.lastPlace !== null) {
|
|
141
|
+
this.history.record(this.lastPlace);
|
|
142
|
+
}
|
|
143
|
+
this.openPlace(place);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* The column an opened place waits for, once the editor of its file has put its caret on the line; each place is asked for once. */
|
|
147
|
+
takeCaret(uri: string, line: number): EditorPosition | null {
|
|
148
|
+
const pending = this.pendingCaret;
|
|
149
|
+
if (pending === null || pending.uri !== uri || pending.position.line !== line) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
this.pendingCaret = null;
|
|
153
|
+
return pending.position;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Opens `uri` for the editor, or joins the editors that already hold it. */
|
|
157
|
+
acquire(uri: string, languageId: string, editor: Editor): LanguageDocumentHandle {
|
|
158
|
+
if (this.disposed) {
|
|
159
|
+
throw new Error('The project language is disposed');
|
|
160
|
+
}
|
|
161
|
+
let holder = this.holders.get(uri);
|
|
162
|
+
if (holder === undefined) {
|
|
163
|
+
holder = { editors: [], sync: new DocumentSync(this.service, uri, languageId, editor), languageId };
|
|
164
|
+
this.holders.set(uri, holder);
|
|
165
|
+
}
|
|
166
|
+
holder.editors.push(editor);
|
|
167
|
+
let released = false;
|
|
168
|
+
return {
|
|
169
|
+
uri,
|
|
170
|
+
service: this.service,
|
|
171
|
+
get ready() {
|
|
172
|
+
return holder.sync.ready;
|
|
173
|
+
},
|
|
174
|
+
release: () => {
|
|
175
|
+
if (!released) {
|
|
176
|
+
released = true;
|
|
177
|
+
this.release(uri, editor);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private release(uri: string, editor: Editor): void {
|
|
184
|
+
const holder = this.holders.get(uri);
|
|
185
|
+
if (holder === undefined) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
holder.editors.splice(holder.editors.indexOf(editor), 1);
|
|
189
|
+
const next = holder.editors[0];
|
|
190
|
+
if (next === undefined) {
|
|
191
|
+
holder.sync.dispose();
|
|
192
|
+
this.holders.delete(uri);
|
|
193
|
+
void holder.sync.ready
|
|
194
|
+
.catch(() => undefined)
|
|
195
|
+
.then(() => {
|
|
196
|
+
if (!this.holders.has(uri)) {
|
|
197
|
+
return this.service.closeDocument(uri);
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
.catch(() => undefined);
|
|
201
|
+
} else if (holder.sync.editor === editor) {
|
|
202
|
+
// The editor that sent the changes is gone; the next one takes over and opens the document with its own text.
|
|
203
|
+
holder.sync.dispose();
|
|
204
|
+
holder.sync = new DocumentSync(this.service, uri, holder.languageId, next);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
dispose(): void {
|
|
209
|
+
if (this.disposed) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
this.disposed = true;
|
|
213
|
+
for (const [uri, holder] of this.holders) {
|
|
214
|
+
holder.sync.dispose();
|
|
215
|
+
void holder.sync.ready
|
|
216
|
+
.catch(() => undefined)
|
|
217
|
+
.then(() => this.service.closeDocument(uri))
|
|
218
|
+
.catch(() => undefined);
|
|
219
|
+
}
|
|
220
|
+
this.holders.clear();
|
|
221
|
+
this.problems.dispose();
|
|
222
|
+
}
|
|
223
|
+
}
|