@adecore/editor-react 0.42.0 → 0.43.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.js +177 -181
- package/dist/EditorContextMenu.js +118 -105
- package/dist/LanguagePopups.js +7 -0
- package/dist/PickPopup.js +190 -70
- package/dist/WrapCard.d.ts +8 -0
- package/dist/WrapCard.js +172 -0
- package/dist/call-hierarchy-model.d.ts +4 -2
- package/dist/call-hierarchy-model.js +20 -0
- package/dist/call-hierarchy.d.ts +3 -0
- package/dist/call-hierarchy.js +54 -22
- package/dist/clipboard-history.d.ts +31 -0
- package/dist/clipboard-history.js +70 -0
- package/dist/code-actions.d.ts +3 -1
- package/dist/code-actions.js +50 -10
- package/dist/code-vision.js +2 -3
- package/dist/completion.js +10 -15
- package/dist/context-menu-rows.d.ts +1 -1
- package/dist/context-menu-rows.js +4 -0
- package/dist/definition-link.js +15 -5
- package/dist/document-colors.d.ts +24 -0
- package/dist/document-colors.js +194 -0
- package/dist/document-links.d.ts +22 -0
- package/dist/document-links.js +133 -0
- package/dist/document-query.d.ts +15 -0
- package/dist/document-query.js +54 -0
- package/dist/edit-locations.d.ts +20 -0
- package/dist/edit-locations.js +136 -0
- package/dist/editor-language.d.ts +19 -1
- package/dist/editor-language.js +77 -2
- package/dist/emmet.d.ts +13 -0
- package/dist/emmet.js +62 -0
- package/dist/folding.js +2 -3
- package/dist/format-on-type.js +3 -5
- package/dist/highlights.js +2 -3
- package/dist/history.d.ts +7 -0
- package/dist/history.js +79 -1
- package/dist/host-types.d.ts +6 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.js +8 -0
- package/dist/inlay-hints.js +2 -2
- package/dist/inline-completion.d.ts +39 -0
- package/dist/inline-completion.js +221 -0
- package/dist/key-priority.d.ts +8 -5
- package/dist/key-priority.js +9 -6
- package/dist/linked-editing.d.ts +26 -0
- package/dist/linked-editing.js +215 -0
- package/dist/locales/en.json +31 -2
- package/dist/locales/nl.json +31 -2
- package/dist/markup-context.d.ts +37 -0
- package/dist/markup-context.js +291 -0
- package/dist/markup.d.ts +36 -0
- package/dist/markup.js +453 -0
- package/dist/navigation.js +4 -6
- package/dist/paste-history.d.ts +7 -0
- package/dist/paste-history.js +104 -0
- package/dist/peek.js +3 -4
- package/dist/pick.d.ts +20 -2
- package/dist/pick.js +141 -18
- package/dist/popups.d.ts +9 -0
- package/dist/popups.js +2 -1
- package/dist/project-language.d.ts +18 -1
- package/dist/project-language.js +129 -18
- package/dist/rename.js +8 -11
- package/dist/rendered-docs.js +1 -2
- package/dist/selection-ranges.js +2 -3
- package/dist/shortcut-keys.d.ts +19 -0
- package/dist/signature.js +3 -6
- package/dist/snippet-session.d.ts +2 -2
- package/dist/snippet-session.js +3 -3
- package/dist/symbol-picker.js +4 -6
- package/dist/symbols.js +22 -3
- package/dist/testing.d.ts +23 -1
- package/dist/testing.js +114 -0
- package/package.json +7 -6
- package/src/CallHierarchyPanel.tsx +26 -11
- package/src/EditorContextMenu.tsx +7 -0
- package/src/LanguagePopups.tsx +4 -0
- package/src/PickPopup.tsx +40 -5
- package/src/WrapCard.tsx +64 -0
- package/src/call-hierarchy-model.ts +25 -3
- package/src/call-hierarchy.ts +62 -20
- package/src/clipboard-history.ts +102 -0
- package/src/code-actions.ts +48 -11
- package/src/code-vision.ts +3 -3
- package/src/completion.ts +15 -15
- package/src/context-menu-rows.ts +3 -2
- package/src/definition-link.ts +17 -5
- package/src/document-colors.ts +189 -0
- package/src/document-links.ts +129 -0
- package/src/document-query.ts +71 -0
- package/src/edit-locations.ts +162 -0
- package/src/editor-language.ts +80 -3
- package/src/emmet-module.d.ts +44 -0
- package/src/emmet.ts +73 -0
- package/src/folding.ts +3 -3
- package/src/format-on-type.ts +5 -5
- package/src/highlights.ts +3 -3
- package/src/history.ts +81 -1
- package/src/host-types.ts +10 -0
- package/src/index.ts +12 -3
- package/src/inlay-hints.ts +2 -2
- package/src/inline-completion.ts +221 -0
- package/src/key-priority.ts +9 -6
- package/src/linked-editing.ts +199 -0
- package/src/locales/en.json +31 -2
- package/src/locales/nl.json +31 -2
- package/src/markup-context.ts +262 -0
- package/src/markup.ts +367 -0
- package/src/navigation.ts +6 -6
- package/src/paste-history.ts +111 -0
- package/src/peek.ts +4 -4
- package/src/pick.ts +201 -19
- package/src/popups.ts +19 -1
- package/src/project-language.ts +136 -19
- package/src/rename.ts +11 -11
- package/src/rendered-docs.ts +2 -2
- package/src/selection-ranges.ts +3 -3
- package/src/signature.ts +6 -6
- package/src/snippet-session.ts +9 -4
- package/src/symbol-picker.ts +6 -6
- package/src/symbols.ts +30 -4
- package/src/testing.ts +126 -1
package/src/editor-language.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { KEYMAP, type Keymap } from '@adecore/editor/keymap';
|
|
2
2
|
import type { Editor, EditorKeyHandler, EditorPosition } from '@adecore/editor';
|
|
3
|
-
import type { Location } from '@adecore/lsp';
|
|
3
|
+
import type { LanguageService, Location, TextDocumentSaveReason } from '@adecore/lsp';
|
|
4
|
+
import { DocumentColorsFeature } from './document-colors.ts';
|
|
5
|
+
import { DocumentLinksFeature } from './document-links.ts';
|
|
6
|
+
import { InlineCompletionFeature } from './inline-completion.ts';
|
|
7
|
+
import { LinkedEditingFeature } from './linked-editing.ts';
|
|
4
8
|
import { CallHierarchyFeature } from './call-hierarchy.ts';
|
|
5
9
|
import { CodeActionsFeature } from './code-actions.ts';
|
|
6
10
|
import { CodeVisionFeature } from './code-vision.ts';
|
|
@@ -14,7 +18,9 @@ import { HighlightsFeature } from './highlights.ts';
|
|
|
14
18
|
import { HistoryFeature } from './history.ts';
|
|
15
19
|
import { HoverFeature } from './hover.ts';
|
|
16
20
|
import { KeyDispatcher } from './key-priority.ts';
|
|
21
|
+
import { MarkupFeature } from './markup.ts';
|
|
17
22
|
import { NavigationFeature, locationRow } from './navigation.ts';
|
|
23
|
+
import { PasteHistoryFeature } from './paste-history.ts';
|
|
18
24
|
import { PeekFeature } from './peek.ts';
|
|
19
25
|
import { PickFeature } from './pick.ts';
|
|
20
26
|
import { SymbolPickerFeature } from './symbol-picker.ts';
|
|
@@ -32,10 +38,15 @@ import { SymbolsFeature } from './symbols.ts';
|
|
|
32
38
|
import type { Place } from './navigation-history.ts';
|
|
33
39
|
import type { LanguageDocumentHandle, ProjectLanguage } from './project-language.ts';
|
|
34
40
|
|
|
41
|
+
/* How long the edits servers want before a save are waited for. */
|
|
42
|
+
const SAVE_PARTICIPANT_MS = 1_000;
|
|
43
|
+
|
|
35
44
|
/* What an app tunes about the features of one editor. */
|
|
36
45
|
export interface EditorLanguageOptions {
|
|
37
46
|
/* Where a document counts as large for semantic tokens. */
|
|
38
47
|
semanticTokens?: SemanticTokensOptions;
|
|
48
|
+
/* False for a document no server serves: it counts as open in the project and the features stay inert. True by default. */
|
|
49
|
+
serve?: boolean;
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
/*
|
|
@@ -51,6 +62,9 @@ export class EditorLanguage {
|
|
|
51
62
|
readonly document: LanguageDocumentHandle;
|
|
52
63
|
readonly languageId: string;
|
|
53
64
|
|
|
65
|
+
/* Whether a server serves this document; read it where a host meant "a language server is behind this editor". */
|
|
66
|
+
readonly served: boolean;
|
|
67
|
+
|
|
54
68
|
/* What the features of this editor have open over it: the hover card, the suggestions, the signature. */
|
|
55
69
|
readonly popups = createPopupStore();
|
|
56
70
|
|
|
@@ -60,6 +74,7 @@ export class EditorLanguage {
|
|
|
60
74
|
readonly completion: CompletionFeature;
|
|
61
75
|
readonly signature: SignatureFeature;
|
|
62
76
|
readonly snippets: SnippetFeature;
|
|
77
|
+
readonly markup: MarkupFeature;
|
|
63
78
|
readonly highlights: HighlightsFeature;
|
|
64
79
|
readonly pick: PickFeature;
|
|
65
80
|
readonly codeActions: CodeActionsFeature;
|
|
@@ -75,8 +90,14 @@ export class EditorLanguage {
|
|
|
75
90
|
readonly codeVision: CodeVisionFeature;
|
|
76
91
|
readonly formatOnType: FormatOnTypeFeature;
|
|
77
92
|
readonly renderedDocs: RenderedDocsFeature;
|
|
93
|
+
readonly pasteHistory: PasteHistoryFeature;
|
|
94
|
+
readonly links: DocumentLinksFeature;
|
|
95
|
+
readonly colors: DocumentColorsFeature;
|
|
96
|
+
readonly linkedEditing: LinkedEditingFeature;
|
|
97
|
+
readonly inlineCompletion: InlineCompletionFeature;
|
|
78
98
|
private readonly disposers: Array<() => void> = [];
|
|
79
99
|
private readonly keys = new KeyDispatcher();
|
|
100
|
+
private readonly timers: Timers;
|
|
80
101
|
private disposed = false;
|
|
81
102
|
|
|
82
103
|
constructor(project: ProjectLanguage, editor: Editor, uri: string, languageId: string, timers: Timers = realTimers, options: EditorLanguageOptions = {}) {
|
|
@@ -86,13 +107,16 @@ export class EditorLanguage {
|
|
|
86
107
|
this.shortcuts = shortcutsOf(this.keymap, this.apple);
|
|
87
108
|
this.project = project;
|
|
88
109
|
this.languageId = languageId;
|
|
89
|
-
this.
|
|
110
|
+
this.served = options.serve ?? true;
|
|
111
|
+
this.document = project.acquire(uri, languageId, editor, { serve: this.served });
|
|
112
|
+
this.timers = timers;
|
|
90
113
|
this.onDispose(editor.onKeyDown((event) => this.keys.dispatch(event)));
|
|
91
114
|
this.pick = new PickFeature(this, timers);
|
|
92
115
|
this.diagnostics = new DiagnosticsFeature(this);
|
|
93
116
|
this.hover = new HoverFeature(this, timers);
|
|
94
117
|
this.completion = new CompletionFeature(this, timers);
|
|
95
118
|
this.snippets = new SnippetFeature(this, timers);
|
|
119
|
+
this.markup = new MarkupFeature(this);
|
|
96
120
|
this.signature = new SignatureFeature(this, timers);
|
|
97
121
|
this.highlights = new HighlightsFeature(this, timers);
|
|
98
122
|
this.symbols = new SymbolsFeature(this, timers);
|
|
@@ -101,6 +125,11 @@ export class EditorLanguage {
|
|
|
101
125
|
this.inlayHints = new InlayHintsFeature(this, timers);
|
|
102
126
|
this.codeActions = new CodeActionsFeature(this, timers);
|
|
103
127
|
this.rename = new RenameFeature(this);
|
|
128
|
+
// Before navigation, whose Mod+click goes to a definition: a link under the pointer goes first.
|
|
129
|
+
this.links = new DocumentLinksFeature(this, timers);
|
|
130
|
+
this.colors = new DocumentColorsFeature(this, timers);
|
|
131
|
+
this.linkedEditing = new LinkedEditingFeature(this, timers);
|
|
132
|
+
this.inlineCompletion = new InlineCompletionFeature(this, timers);
|
|
104
133
|
this.navigation = new NavigationFeature(this);
|
|
105
134
|
this.peek = new PeekFeature(this, timers);
|
|
106
135
|
this.callHierarchy = new CallHierarchyFeature(this, timers);
|
|
@@ -111,7 +140,17 @@ export class EditorLanguage {
|
|
|
111
140
|
this.codeVision = new CodeVisionFeature(this, (listener) => this.symbols.onResult(listener), timers);
|
|
112
141
|
this.formatOnType = new FormatOnTypeFeature(this);
|
|
113
142
|
this.renderedDocs = new RenderedDocsFeature(this, timers);
|
|
143
|
+
this.pasteHistory = new PasteHistoryFeature(this);
|
|
114
144
|
new FoldingFeature(this, (listener) => this.symbols.onResult(listener), timers);
|
|
145
|
+
|
|
146
|
+
const copies = editor.onCopy?.((copy) => project.clipboard.add(copy.text, { wholeLines: copy.wholeLines }));
|
|
147
|
+
|
|
148
|
+
if (copies !== undefined) {
|
|
149
|
+
this.onDispose(copies);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
editor.setWordSources?.(() => this.otherTexts());
|
|
153
|
+
this.onDispose(() => editor.setWordSources?.(null));
|
|
115
154
|
this.placeOpenedCaret(editor.getCaret());
|
|
116
155
|
this.onDispose(editor.onCaret((position) => this.placeOpenedCaret(position)));
|
|
117
156
|
}
|
|
@@ -120,6 +159,11 @@ export class EditorLanguage {
|
|
|
120
159
|
return this.document.uri;
|
|
121
160
|
}
|
|
122
161
|
|
|
162
|
+
/* The service this document is asked through: the project's, or one that answers nothing when no server serves it. */
|
|
163
|
+
get service(): LanguageService {
|
|
164
|
+
return this.document.service;
|
|
165
|
+
}
|
|
166
|
+
|
|
123
167
|
get isDisposed(): boolean {
|
|
124
168
|
return this.disposed;
|
|
125
169
|
}
|
|
@@ -173,7 +217,7 @@ export class EditorLanguage {
|
|
|
173
217
|
() => undefined
|
|
174
218
|
);
|
|
175
219
|
|
|
176
|
-
const providers = this.
|
|
220
|
+
const providers = this.service.onProvidersChanged((uri) => {
|
|
177
221
|
if (uri === this.uri) {
|
|
178
222
|
listener();
|
|
179
223
|
}
|
|
@@ -193,6 +237,31 @@ export class EditorLanguage {
|
|
|
193
237
|
return this.keys.add(handler, priority);
|
|
194
238
|
}
|
|
195
239
|
|
|
240
|
+
/*
|
|
241
|
+
* Lets the servers in before the host writes the file: tells them a save is coming and applies the edits they
|
|
242
|
+
* want made first, such as organized imports. A server that has not answered within a second is passed over,
|
|
243
|
+
* as the specification allows. Call it, then save, then let the service send `didSave`.
|
|
244
|
+
*/
|
|
245
|
+
async prepareSave(reason: TextDocumentSaveReason = 1): Promise<void> {
|
|
246
|
+
const { service } = this.project;
|
|
247
|
+
await service.willSave?.(this.uri, reason).catch(() => undefined);
|
|
248
|
+
if (service.willSaveWaitUntil === undefined) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const controller = new AbortController();
|
|
252
|
+
const timer = this.timers.set(() => controller.abort(), SAVE_PARTICIPANT_MS);
|
|
253
|
+
try {
|
|
254
|
+
const edits = await service.willSaveWaitUntil(this.uri, reason, { signal: controller.signal });
|
|
255
|
+
if (!controller.signal.aborted && edits !== null && edits.length > 0) {
|
|
256
|
+
this.editor.applyEdits(edits.map((edit) => ({ range: edit.range, text: edit.newText })));
|
|
257
|
+
}
|
|
258
|
+
} catch {
|
|
259
|
+
// A server that fails or is too slow does not keep the file from being saved.
|
|
260
|
+
} finally {
|
|
261
|
+
this.timers.clear(timer);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
196
265
|
/* Runs `dispose` when the editor lets go of its document. */
|
|
197
266
|
onDispose(dispose: () => void): void {
|
|
198
267
|
this.disposers.push(dispose);
|
|
@@ -212,6 +281,14 @@ export class EditorLanguage {
|
|
|
212
281
|
this.document.release();
|
|
213
282
|
}
|
|
214
283
|
|
|
284
|
+
/* The texts of the other open editors of the project, which Hippie completion takes words from. */
|
|
285
|
+
private otherTexts(): string[] {
|
|
286
|
+
return this.project
|
|
287
|
+
.openEditors()
|
|
288
|
+
.filter((open) => open.uri !== this.uri)
|
|
289
|
+
.map((open) => open.editor.getText());
|
|
290
|
+
}
|
|
291
|
+
|
|
215
292
|
/* A file opened from another one lands on the line; this puts the caret on the column the jump meant. */
|
|
216
293
|
private placeOpenedCaret(position: EditorPosition): void {
|
|
217
294
|
const column = this.project.takeCaret(this.uri, position.line);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// The package ships `dist/index.d.ts`, but its `exports` point TypeScript at none, so the part used here is declared again.
|
|
2
|
+
declare module 'emmet' {
|
|
3
|
+
export interface MarkupAbbreviationNode {
|
|
4
|
+
name?: string;
|
|
5
|
+
children: MarkupAbbreviationNode[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface MarkupAbbreviation {
|
|
9
|
+
type: 'Abbreviation';
|
|
10
|
+
children: MarkupAbbreviationNode[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ExtractedAbbreviation {
|
|
14
|
+
abbreviation: string;
|
|
15
|
+
location: number;
|
|
16
|
+
start: number;
|
|
17
|
+
end: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ExtractOptions {
|
|
21
|
+
lookAhead: boolean;
|
|
22
|
+
type: 'markup' | 'stylesheet';
|
|
23
|
+
prefix: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UserConfig {
|
|
27
|
+
syntax?: string;
|
|
28
|
+
text?: string | string[];
|
|
29
|
+
options?: {
|
|
30
|
+
'output.field'?: (index: number, placeholder: string, offset: number, line: number, column: number) => string;
|
|
31
|
+
'output.text'?: (text: string, offset: number, line: number, column: number) => string;
|
|
32
|
+
'output.indent'?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Config extends UserConfig {
|
|
37
|
+
type: 'markup' | 'stylesheet';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function extract(line: string, pos?: number, options?: Partial<ExtractOptions>): ExtractedAbbreviation | undefined;
|
|
41
|
+
export function markupAbbreviation(abbreviation: string, options?: { text?: string | string[] }): MarkupAbbreviation;
|
|
42
|
+
export function markup(abbreviation: string | MarkupAbbreviation, config: Config): string;
|
|
43
|
+
export function resolveConfig(config?: UserConfig): Config;
|
|
44
|
+
}
|
package/src/emmet.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// A reference and no import, so an app that type-checks this source reads the declaration of the package too.
|
|
2
|
+
// oxlint-disable-next-line typescript/triple-slash-reference
|
|
3
|
+
/// <reference path="./emmet-module.d.ts" />
|
|
4
|
+
import { extract, markup, markupAbbreviation, resolveConfig, type MarkupAbbreviationNode } from 'emmet';
|
|
5
|
+
import type { MarkupSyntax } from './markup-context.ts';
|
|
6
|
+
|
|
7
|
+
export interface AbbreviationOptions {
|
|
8
|
+
readonly syntax: MarkupSyntax;
|
|
9
|
+
/* One level of indentation inside the expansion. */
|
|
10
|
+
readonly indent: string;
|
|
11
|
+
/* Lines the abbreviation wraps; a repeated element without a count takes one line each. */
|
|
12
|
+
readonly wrap?: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface FoundAbbreviation {
|
|
16
|
+
readonly abbreviation: string;
|
|
17
|
+
/* UTF-16 columns of the line, `end` past the caret when brackets the editor closed follow it. */
|
|
18
|
+
readonly start: number;
|
|
19
|
+
readonly end: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* A text of an expansion is snippet syntax, where these mean a tab stop or end one. */
|
|
23
|
+
function escapeText(text: string): string {
|
|
24
|
+
return text.replace(/[$}\\]/g, '\\$&');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function field(index: number, placeholder: string): string {
|
|
28
|
+
return placeholder === '' ? `\${${index}}` : `\${${index}:${escapeText(placeholder)}}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* `lipsum` is the other name of `lorem` that IntelliJ and the Emmet documentation accept. */
|
|
32
|
+
function renameLipsum(node: MarkupAbbreviationNode): void {
|
|
33
|
+
if (node.name !== undefined && /^lipsum(?=\d|$)/i.test(node.name)) {
|
|
34
|
+
node.name = 'lorem' + node.name.slice(6);
|
|
35
|
+
}
|
|
36
|
+
for (const child of node.children) {
|
|
37
|
+
renameLipsum(child);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* The abbreviation that ends at `character` of a line, or null where nothing before the caret reads as one. */
|
|
42
|
+
export function abbreviationBefore(line: string, character: number): FoundAbbreviation | null {
|
|
43
|
+
const found = extract(line, character, { type: 'markup', lookAhead: true });
|
|
44
|
+
if (found === undefined || !/^[A-Za-z!.#([{]/.test(found.abbreviation)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return { abbreviation: found.abbreviation, start: found.start, end: found.end };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/*
|
|
51
|
+
* The expansion of an abbreviation as snippet text, its places to fill as tab stops; null when it does not
|
|
52
|
+
* read as one. Lorem ipsum words after the first sentence differ per expansion.
|
|
53
|
+
*/
|
|
54
|
+
export function expandAbbreviation(abbreviation: string, options: AbbreviationOptions): string | null {
|
|
55
|
+
const text = options.wrap === undefined ? undefined : [...options.wrap];
|
|
56
|
+
try {
|
|
57
|
+
const tree = markupAbbreviation(abbreviation, { text });
|
|
58
|
+
for (const child of tree.children) {
|
|
59
|
+
renameLipsum(child);
|
|
60
|
+
}
|
|
61
|
+
const expanded = markup(
|
|
62
|
+
tree,
|
|
63
|
+
resolveConfig({
|
|
64
|
+
syntax: options.syntax === 'xml' ? 'xml' : 'html',
|
|
65
|
+
text,
|
|
66
|
+
options: { 'output.field': field, 'output.text': escapeText, 'output.indent': options.indent }
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
return expanded === '' ? null : expanded;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/folding.ts
CHANGED
|
@@ -58,15 +58,15 @@ export class FoldingFeature {
|
|
|
58
58
|
|
|
59
59
|
constructor(language: EditorLanguage, onSymbols: (listener: (result: DocumentSymbolResult) => void) => () => void, timers: Timers = realTimers) {
|
|
60
60
|
this.language = language;
|
|
61
|
-
const { editor,
|
|
61
|
+
const { editor, uri } = language;
|
|
62
62
|
|
|
63
63
|
this.refresher = new Refresher(
|
|
64
64
|
async (signal) => {
|
|
65
|
-
if (!
|
|
65
|
+
if (!language.service.supports(METHOD, uri)) {
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const ranges = await
|
|
69
|
+
const ranges = await language.service.foldingRanges(uri, { signal });
|
|
70
70
|
|
|
71
71
|
if (!signal.aborted) {
|
|
72
72
|
this.ranges = foldRangesOf(ranges);
|
package/src/format-on-type.ts
CHANGED
|
@@ -52,9 +52,9 @@ export class FormatOnTypeFeature {
|
|
|
52
52
|
|
|
53
53
|
/* Whether the server of this document formats on type and the service can ask it. */
|
|
54
54
|
get supported(): boolean {
|
|
55
|
-
const {
|
|
55
|
+
const { uri } = this.language;
|
|
56
56
|
|
|
57
|
-
return
|
|
57
|
+
return this.language.service.onTypeFormatting !== undefined && this.language.service.supports(METHOD, uri);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
configure(enabled: boolean): void {
|
|
@@ -67,7 +67,7 @@ export class FormatOnTypeFeature {
|
|
|
67
67
|
|
|
68
68
|
/* The characters that ask the server, the first one and the more. */
|
|
69
69
|
triggerCharacters(): readonly string[] {
|
|
70
|
-
const options = this.language.
|
|
70
|
+
const options = this.language.service.providerOptions(METHOD, this.language.uri);
|
|
71
71
|
|
|
72
72
|
return options === undefined
|
|
73
73
|
? []
|
|
@@ -99,12 +99,12 @@ export class FormatOnTypeFeature {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
private async ask(revision: number, typed: string, caret: { line: number; character: number }, signal: AbortSignal): Promise<void> {
|
|
102
|
-
const { editor,
|
|
102
|
+
const { editor, uri, document } = this.language;
|
|
103
103
|
|
|
104
104
|
try {
|
|
105
105
|
// The change of the typed character reaches the server before this question does.
|
|
106
106
|
await document.ready;
|
|
107
|
-
const edits = await
|
|
107
|
+
const edits = await this.language.service.onTypeFormatting!(uri, caret, typed, { ...editor.getIndentation() }, { signal });
|
|
108
108
|
const here = editor.getCaret();
|
|
109
109
|
const stale = revision !== this.revision || this.language.isDisposed || comparePositions(here, caret) !== 0;
|
|
110
110
|
|
package/src/highlights.ts
CHANGED
|
@@ -46,17 +46,17 @@ export class HighlightsFeature {
|
|
|
46
46
|
|
|
47
47
|
constructor(language: EditorLanguage, timers: Timers = realTimers) {
|
|
48
48
|
this.language = language;
|
|
49
|
-
const { editor,
|
|
49
|
+
const { editor, uri } = language;
|
|
50
50
|
|
|
51
51
|
this.refresher = new Refresher(
|
|
52
52
|
async (signal) => {
|
|
53
|
-
if (!
|
|
53
|
+
if (!language.service.supports(METHOD, uri) || !this.onName()) {
|
|
54
54
|
this.clear();
|
|
55
55
|
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
const result = await
|
|
59
|
+
const result = await language.service.documentHighlights(uri, editor.getCaret(), { signal });
|
|
60
60
|
|
|
61
61
|
if (!signal.aborted) {
|
|
62
62
|
this.set(result === null ? [] : editorHighlightsOf(result));
|
package/src/history.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { EditorPosition, EditorTextChange } from '@adecore/editor';
|
|
2
|
+
import type { EditPlace } from './edit-locations.ts';
|
|
1
3
|
import type { EditorLanguage } from './editor-language.ts';
|
|
2
4
|
import { KEY_PRIORITY } from './key-priority.ts';
|
|
3
5
|
import type { Place } from './navigation-history.ts';
|
|
@@ -6,6 +8,7 @@ import { isShortcut } from './shortcut-keys.ts';
|
|
|
6
8
|
/* Back, Forward and the list of recent locations over the history the whole project shares. */
|
|
7
9
|
export class HistoryFeature {
|
|
8
10
|
private readonly language: EditorLanguage;
|
|
11
|
+
private editing = false;
|
|
9
12
|
|
|
10
13
|
constructor(language: EditorLanguage) {
|
|
11
14
|
this.language = language;
|
|
@@ -29,10 +32,42 @@ export class HistoryFeature {
|
|
|
29
32
|
return true;
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
if (isShortcut(this.language.shortcuts.lastEditLocation, event)) {
|
|
36
|
+
this.lastEdit();
|
|
37
|
+
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (isShortcut(this.language.shortcuts.nextEditLocation, event)) {
|
|
42
|
+
this.nextEdit();
|
|
43
|
+
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
return false;
|
|
33
48
|
}, KEY_PRIORITY.history);
|
|
34
49
|
|
|
35
|
-
|
|
50
|
+
// A jump of the editor's own, such as Method Down, is a place Back returns to like any other jump.
|
|
51
|
+
const navigations = language.editor.onNavigate?.((from) => language.project.history.record({ uri: language.uri, position: from }));
|
|
52
|
+
const changes = language.editor.onTextChange((change) => this.edited(change));
|
|
53
|
+
const carets = language.editor.onCaret((position) => this.moved(position));
|
|
54
|
+
|
|
55
|
+
language.onDispose(() => {
|
|
56
|
+
off();
|
|
57
|
+
navigations?.();
|
|
58
|
+
changes();
|
|
59
|
+
carets();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Goes to where the project was edited before the carets' place; false when there is no such place. */
|
|
64
|
+
lastEdit(): boolean {
|
|
65
|
+
return this.visitEdit(this.language.project.edits.previous(this.currentEdit()));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Goes to where the project was edited after the place Last Edit Location came back to. */
|
|
69
|
+
nextEdit(): boolean {
|
|
70
|
+
return this.visitEdit(this.language.project.edits.next(this.currentEdit()));
|
|
36
71
|
}
|
|
37
72
|
|
|
38
73
|
/* Returns to where the caret was before the last jump; false when there is no such place. */
|
|
@@ -55,6 +90,51 @@ export class HistoryFeature {
|
|
|
55
90
|
);
|
|
56
91
|
}
|
|
57
92
|
|
|
93
|
+
private currentEdit(): EditPlace {
|
|
94
|
+
return { uri: this.language.uri, selections: this.language.editor.getSelections() };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Every edit moves the places of the file; one a person made counts as an edit location once the caret has settled. */
|
|
98
|
+
private edited(change: EditorTextChange): void {
|
|
99
|
+
const { editor, project, uri } = this.language;
|
|
100
|
+
|
|
101
|
+
project.edits.map(uri, change.changes);
|
|
102
|
+
this.editing = true;
|
|
103
|
+
|
|
104
|
+
queueMicrotask(() => {
|
|
105
|
+
this.editing = false;
|
|
106
|
+
|
|
107
|
+
if (change.source !== 'external' && editor.hasFocus?.() !== false && !this.language.isDisposed) {
|
|
108
|
+
project.edits.record(this.currentEdit());
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* A caret that moves on its own ends the walk through the edits, unless an edit moved it. */
|
|
114
|
+
private moved(position: EditorPosition): void {
|
|
115
|
+
if (!this.editing) {
|
|
116
|
+
this.language.project.edits.moved(this.language.uri, position);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private visitEdit(place: EditPlace | null): boolean {
|
|
121
|
+
if (place === null) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const { editor, project, uri } = this.language;
|
|
126
|
+
|
|
127
|
+
if (place.uri === uri) {
|
|
128
|
+
editor.setSelections(place.selections, 'center');
|
|
129
|
+
editor.focus();
|
|
130
|
+
} else {
|
|
131
|
+
// Another file opens on one caret, the primary.
|
|
132
|
+
project.openPlace({ uri: place.uri, position: place.selections.at(-1)!.end });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
58
138
|
private visit(place: Place | null): boolean {
|
|
59
139
|
if (place === null) {
|
|
60
140
|
return false;
|
package/src/host-types.ts
CHANGED
|
@@ -2,9 +2,11 @@ import type { WorkerRequestPort } from '@adecore/editor/worker-client';
|
|
|
2
2
|
import type { EditorPosition, EditorRange } from '@adecore/editor';
|
|
3
3
|
import type { i18n } from 'i18next';
|
|
4
4
|
import type { Keymap } from '@adecore/editor/keymap';
|
|
5
|
+
import type { ClipboardHistory } from './clipboard-history.ts';
|
|
5
6
|
import type { ProjectFiles } from './workspace-edit.ts';
|
|
6
7
|
import type { Place } from './navigation-history.ts';
|
|
7
8
|
import type { ProblemSource } from './project-problems.ts';
|
|
9
|
+
import type { ServerMessage } from '@adecore/lsp';
|
|
8
10
|
|
|
9
11
|
export interface DiskText {
|
|
10
12
|
text: string;
|
|
@@ -64,6 +66,10 @@ export interface LanguageHost {
|
|
|
64
66
|
readonly files?: ProjectFiles;
|
|
65
67
|
readonly apple?: boolean;
|
|
66
68
|
readonly keymap?: Keymap;
|
|
69
|
+
/* What Paste from History lists. A new `ClipboardHistory` that lives as long as the project without one; pass one to choose its size or to keep its entries. */
|
|
70
|
+
readonly clipboard?: ClipboardHistory;
|
|
71
|
+
/* How many places Last Edit Location and Next Edit Location remember; 150 without one. */
|
|
72
|
+
readonly editHistoryLimit?: number;
|
|
67
73
|
/* The height in pixels of the list-and-preview area of the peek and the call hierarchy, so a person's drag outlives the panel; 180 without one. */
|
|
68
74
|
readonly panelBodyHeight?: number;
|
|
69
75
|
/* A person finished resizing a peek or call hierarchy panel; the height is already within what the editor allows. */
|
|
@@ -71,9 +77,13 @@ export interface LanguageHost {
|
|
|
71
77
|
/* Where `project.problems` reads its reports, such as a machine that also reports files no editor here holds; the service's diagnostics without one. */
|
|
72
78
|
readonly problemSource?: ProblemSource;
|
|
73
79
|
openPlace?(place: Place): void;
|
|
80
|
+
/* Opens a web address outside the editor, from a link in the text or a server; a new browser window without it. */
|
|
81
|
+
openExternal?(url: string): void;
|
|
74
82
|
pathOfUri?(uri: string): string | null;
|
|
75
83
|
serverNames?(uri: string): readonly string[];
|
|
76
84
|
notify?(notification: EditorNotification): void;
|
|
85
|
+
/* A message of a server for a person, or a line for its log; without it the messages are not shown. */
|
|
86
|
+
onServerMessage?(message: ServerMessage): void;
|
|
77
87
|
/* Synchronous overrides; undefined delegates to built-ins or an editable unknown-variable placeholder. */
|
|
78
88
|
resolveSnippetVariable?(name: string, context: SnippetVariableContext): string | undefined;
|
|
79
89
|
suggestNames?(request: RenameSuggestionsRequest, signal: AbortSignal): Promise<readonly string[]>;
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { KEY_PRIORITY } from './key-priority.ts';
|
|
|
3
3
|
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
|
-
export type { LanguageDocumentHandle } from './project-language.ts';
|
|
6
|
+
export type { AcquireOptions, LanguageDocumentHandle } from './project-language.ts';
|
|
7
7
|
|
|
8
8
|
export type {
|
|
9
9
|
SnippetVariableContext,
|
|
@@ -23,6 +23,10 @@ export type { LanguagePopupsProps } from './LanguagePopups.tsx';
|
|
|
23
23
|
export { AnchoredPopup } from './AnchoredPopup.tsx';
|
|
24
24
|
export { SignatureCard } from './SignatureCard.tsx';
|
|
25
25
|
export { type RenderedDocsFeature } from './rendered-docs.ts';
|
|
26
|
+
export { type DocumentLinksFeature } from './document-links.ts';
|
|
27
|
+
export { type DocumentColorsFeature } from './document-colors.ts';
|
|
28
|
+
export { type LinkedEditingFeature } from './linked-editing.ts';
|
|
29
|
+
export { type InlineCompletionFeature, type InlineSuggestion } from './inline-completion.ts';
|
|
26
30
|
export type { RenderedDocEntry, RenderedDocsSettings } from './rendered-docs.ts';
|
|
27
31
|
export type { DocTagEntry, DocTagSection, ParsedDocComment } from './doc-comment-model.ts';
|
|
28
32
|
export { EditorRenderingProvider } from './rendering.tsx';
|
|
@@ -58,14 +62,19 @@ export type {
|
|
|
58
62
|
PickPreview,
|
|
59
63
|
SymbolsView,
|
|
60
64
|
MenuView,
|
|
61
|
-
AuthorsView
|
|
65
|
+
AuthorsView,
|
|
66
|
+
WrapView
|
|
62
67
|
} from './popups.ts';
|
|
63
|
-
|
|
68
|
+
export { type MarkupFeature } from './markup.ts';
|
|
69
|
+
export type { MarkupSettings } from './markup.ts';
|
|
64
70
|
export { RowHost, LineActionHost } from './row-host.ts';
|
|
65
71
|
export type { HostedRow, HostedAction } from './row-host.ts';
|
|
66
72
|
export { type HighlightLayers, highlightLayers } from './highlight-layers.ts';
|
|
67
73
|
export { type NavigationHistory } from './navigation-history.ts';
|
|
68
74
|
export type { Place } from './navigation-history.ts';
|
|
75
|
+
export { EditLocations, type EditPlace } from './edit-locations.ts';
|
|
76
|
+
export { ClipboardHistory, type ClipboardEntry, type ClipboardHistoryOptions } from './clipboard-history.ts';
|
|
77
|
+
export { type PasteHistoryFeature } from './paste-history.ts';
|
|
69
78
|
export { openingPlace, viewStates, followViewStates, viewStateKey, forgetMovesFrom } from './view-state.ts';
|
|
70
79
|
export type { ViewState, RevealLineRequest } from './view-state.ts';
|
|
71
80
|
export { lspLanguageIdOf, shikiLanguageOf, shikiLanguageOfPath } from './language-ids.ts';
|
package/src/inlay-hints.ts
CHANGED
|
@@ -391,7 +391,7 @@ export class InlayHintsFeature {
|
|
|
391
391
|
|
|
392
392
|
/* Whether asking the server could still change what the hint offers. */
|
|
393
393
|
private resolvable(entry: Entry): boolean {
|
|
394
|
-
return !entry.answered && entry.epoch === this.epoch && this.language.
|
|
394
|
+
return !entry.answered && entry.epoch === this.epoch && this.language.service.supports(RESOLVE_METHOD, this.language.uri);
|
|
395
395
|
}
|
|
396
396
|
|
|
397
397
|
/* Underlines the part under the pointer when a press would do something with it: a command always, a place while the key is down. */
|
|
@@ -479,7 +479,7 @@ export class InlayHintsFeature {
|
|
|
479
479
|
this.cancelHover();
|
|
480
480
|
this.language.goTo(action.location);
|
|
481
481
|
} else if (action.kind === 'run') {
|
|
482
|
-
await
|
|
482
|
+
await this.language.service.executeCommand(uri, action.command);
|
|
483
483
|
} else if (entry.epoch === this.epoch && editsFit(editor, action.edits)) {
|
|
484
484
|
this.cancelHover();
|
|
485
485
|
const applied = editor.applyEdits(action.edits.map((edit) => ({ range: edit.range, text: edit.newText })));
|