@adecore/editor-react 0.0.1 → 0.17.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +110 -0
- package/README.md +55 -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 +15 -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 +4 -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 +29 -0
- package/dist/code-actions.js +329 -0
- package/dist/code-authors.d.ts +17 -0
- package/dist/code-authors.js +80 -0
- package/dist/code-vision-model.d.ts +20 -0
- package/dist/code-vision-model.js +184 -0
- package/dist/code-vision.d.ts +48 -0
- package/dist/code-vision.js +234 -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 +464 -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 +64 -0
- package/dist/editor-language.js +148 -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 +97 -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 +47 -0
- package/dist/holder.d.ts +5 -0
- package/dist/holder.js +19 -0
- package/dist/host-types.d.ts +43 -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 +261 -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 +73 -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 +22 -0
- package/dist/navigation.js +156 -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 +29 -0
- package/dist/peek.js +216 -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 +34 -0
- package/dist/project-language.js +187 -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 +20 -0
- package/dist/rename.js +258 -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 +40 -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 +77 -0
- package/dist/symbols.d.ts +11 -0
- package/dist/symbols.js +97 -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 +45 -0
- package/src/FindReplace.tsx +89 -0
- package/src/HoverCard.tsx +121 -0
- package/src/HoverSections.tsx +146 -0
- package/src/LanguagePopups.tsx +61 -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 +387 -0
- package/src/code-authors.ts +104 -0
- package/src/code-vision-model.ts +230 -0
- package/src/code-vision.ts +267 -0
- package/src/completion-call.ts +139 -0
- package/src/completion-model.ts +440 -0
- package/src/completion.ts +501 -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 +163 -0
- package/src/editor-react.css +43 -0
- package/src/find-query.ts +74 -0
- package/src/folding.ts +112 -0
- package/src/highlight-layers.ts +37 -0
- package/src/highlights.ts +123 -0
- package/src/history.ts +57 -0
- package/src/holder.ts +19 -0
- package/src/host-types.ts +49 -0
- package/src/hover-content.ts +264 -0
- package/src/hover.ts +281 -0
- package/src/index.ts +63 -0
- package/src/inlay-hints.ts +87 -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 +169 -0
- package/src/paths.ts +3 -0
- package/src/peek-model.ts +147 -0
- package/src/peek.ts +248 -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 +220 -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 +291 -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 +48 -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 +86 -0
- package/src/symbols.ts +112 -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/dist/row-host.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The rows of one owner in an editor, and the elements the editor draws them in, so React can portal
|
|
3
|
+
* into them. The editor makes an element again whenever its row scrolls back into view, which is why a
|
|
4
|
+
* row's content never keeps state of its own.
|
|
5
|
+
*/
|
|
6
|
+
export class RowHost {
|
|
7
|
+
editor;
|
|
8
|
+
owner;
|
|
9
|
+
containers = new Map();
|
|
10
|
+
listeners = new Set();
|
|
11
|
+
version = 0;
|
|
12
|
+
signature = '';
|
|
13
|
+
constructor(editor, owner) {
|
|
14
|
+
this.editor = editor;
|
|
15
|
+
this.owner = owner;
|
|
16
|
+
}
|
|
17
|
+
getVersion = () => this.version;
|
|
18
|
+
subscribe = (listener) => {
|
|
19
|
+
this.listeners.add(listener);
|
|
20
|
+
return () => {
|
|
21
|
+
this.listeners.delete(listener);
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
container(id) {
|
|
25
|
+
return this.containers.get(id);
|
|
26
|
+
}
|
|
27
|
+
/* Sets the owner's rows; the same rows again are left alone, so an editor does not measure them twice. */
|
|
28
|
+
set(rows) {
|
|
29
|
+
const signature = rows.map((row) => `${row.id}@${row.line}${row.placement}`).join('|');
|
|
30
|
+
if (signature === this.signature) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.signature = signature;
|
|
34
|
+
const kept = new Set(rows.map((row) => row.id));
|
|
35
|
+
for (const id of [...this.containers.keys()]) {
|
|
36
|
+
if (!kept.has(id)) {
|
|
37
|
+
this.containers.delete(id);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
this.editor.setWidgets(rows.map((row) => ({
|
|
41
|
+
id: row.id,
|
|
42
|
+
line: row.line,
|
|
43
|
+
placement: row.placement,
|
|
44
|
+
height: row.height,
|
|
45
|
+
render: (container) => this.mounted(row.id, container)
|
|
46
|
+
})), this.owner);
|
|
47
|
+
this.bump();
|
|
48
|
+
}
|
|
49
|
+
clear() {
|
|
50
|
+
this.set([]);
|
|
51
|
+
}
|
|
52
|
+
mounted(id, container) {
|
|
53
|
+
this.containers.set(id, container);
|
|
54
|
+
this.bump();
|
|
55
|
+
}
|
|
56
|
+
bump() {
|
|
57
|
+
this.version++;
|
|
58
|
+
for (const listener of [...this.listeners]) {
|
|
59
|
+
listener();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/*
|
|
64
|
+
* The actions of one owner after the end of lines in an editor, and the elements the editor makes for
|
|
65
|
+
* them. An element lives as long as its action does, unlike a row's, so what React draws into it keeps
|
|
66
|
+
* its state.
|
|
67
|
+
*/
|
|
68
|
+
export class LineActionHost {
|
|
69
|
+
editor;
|
|
70
|
+
owner;
|
|
71
|
+
containers = new Map();
|
|
72
|
+
listeners = new Set();
|
|
73
|
+
version = 0;
|
|
74
|
+
signature = '';
|
|
75
|
+
constructor(editor, owner) {
|
|
76
|
+
this.editor = editor;
|
|
77
|
+
this.owner = owner;
|
|
78
|
+
}
|
|
79
|
+
getVersion = () => this.version;
|
|
80
|
+
subscribe = (listener) => {
|
|
81
|
+
this.listeners.add(listener);
|
|
82
|
+
return () => {
|
|
83
|
+
this.listeners.delete(listener);
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
container(id) {
|
|
87
|
+
return this.containers.get(id);
|
|
88
|
+
}
|
|
89
|
+
set(actions) {
|
|
90
|
+
const signature = actions.map((action) => `${action.id}@${action.line}`).join('|');
|
|
91
|
+
if (signature === this.signature) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.signature = signature;
|
|
95
|
+
const kept = new Set(actions.map((action) => action.id));
|
|
96
|
+
for (const id of [...this.containers.keys()]) {
|
|
97
|
+
if (!kept.has(id)) {
|
|
98
|
+
this.containers.delete(id);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
this.editor.setLineActions(actions.map((action) => ({
|
|
102
|
+
id: action.id,
|
|
103
|
+
line: action.line,
|
|
104
|
+
render: (container) => {
|
|
105
|
+
this.containers.set(action.id, container);
|
|
106
|
+
this.bump();
|
|
107
|
+
}
|
|
108
|
+
})), this.owner);
|
|
109
|
+
this.bump();
|
|
110
|
+
}
|
|
111
|
+
clear() {
|
|
112
|
+
this.set([]);
|
|
113
|
+
}
|
|
114
|
+
bump() {
|
|
115
|
+
this.version++;
|
|
116
|
+
for (const listener of [...this.listeners]) {
|
|
117
|
+
listener();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EditorRange } from '@adecore/editor';
|
|
2
|
+
import type { SelectionRange } from '@adecore/lsp';
|
|
3
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
4
|
+
export declare function selectionChainOf(selection: SelectionRange): EditorRange[];
|
|
5
|
+
export declare class SelectionRangesFeature {
|
|
6
|
+
constructor(language: EditorLanguage);
|
|
7
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const METHOD = 'textDocument/selectionRange';
|
|
2
|
+
/* The links of one answer, the smallest range first. */
|
|
3
|
+
export function selectionChainOf(selection) {
|
|
4
|
+
const chain = [];
|
|
5
|
+
for (let link = selection; link; link = link.parent) {
|
|
6
|
+
chain.push(link.range);
|
|
7
|
+
}
|
|
8
|
+
return chain;
|
|
9
|
+
}
|
|
10
|
+
/*
|
|
11
|
+
* Extend and Shrink Selection grow through the ranges the language server knows around the caret, which
|
|
12
|
+
* follow the syntax of the file. Where no server offers them the editor grows by its own rule.
|
|
13
|
+
*/
|
|
14
|
+
export class SelectionRangesFeature {
|
|
15
|
+
constructor(language) {
|
|
16
|
+
const { editor, project, uri } = language;
|
|
17
|
+
editor.setSelectionRanges(async (positions) => {
|
|
18
|
+
if (!project.service.supports(METHOD, uri)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const chains = await project.service.selectionRanges(uri, positions);
|
|
22
|
+
return chains === null || chains.length !== positions.length ? null : chains.map(selectionChainOf);
|
|
23
|
+
});
|
|
24
|
+
language.onDispose(() => editor.setSelectionRanges(null));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { EditorSemanticToken } from '@adecore/editor';
|
|
2
|
+
import type { SemanticTokens, SemanticTokensLegend } from '@adecore/lsp';
|
|
3
|
+
export declare function scopesOf(type: string, modifiers: ReadonlySet<string>, scoped?: boolean): string[] | null;
|
|
4
|
+
export declare function decodeSemanticTokens(tokens: SemanticTokens, legend: SemanticTokensLegend): EditorSemanticToken[];
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The TextMate scopes a classification stands for, so the editor's theme colors it the way its own
|
|
3
|
+
* grammar colors the same thing. Null where the grammar already did better: a keyword, a string, a
|
|
4
|
+
* local variable. A function being called, a method, a static member and a declaration are scopes of
|
|
5
|
+
* their own, as the platform's color scheme tells them apart. `scoped` says the server marks what is
|
|
6
|
+
* local to a function, so a variable it leaves unmarked lives at the top of a file or comes from a
|
|
7
|
+
* library, which the scheme draws as a global.
|
|
8
|
+
*/
|
|
9
|
+
export function scopesOf(type, modifiers, scoped = false) {
|
|
10
|
+
switch (type) {
|
|
11
|
+
case 'namespace':
|
|
12
|
+
return ['entity.name.namespace'];
|
|
13
|
+
case 'class':
|
|
14
|
+
return ['entity.name.type.class'];
|
|
15
|
+
case 'enum':
|
|
16
|
+
return ['entity.name.type.enum'];
|
|
17
|
+
case 'interface':
|
|
18
|
+
return ['entity.name.type.interface'];
|
|
19
|
+
case 'struct':
|
|
20
|
+
return ['entity.name.type.struct'];
|
|
21
|
+
case 'type':
|
|
22
|
+
return ['entity.name.type'];
|
|
23
|
+
case 'typeParameter':
|
|
24
|
+
return ['entity.name.type.parameter'];
|
|
25
|
+
case 'parameter':
|
|
26
|
+
return ['variable.parameter'];
|
|
27
|
+
case 'property':
|
|
28
|
+
return [modifiers.has('static') ? 'variable.other.property.static' : 'variable.other.property'];
|
|
29
|
+
case 'enumMember':
|
|
30
|
+
return ['variable.other.enummember'];
|
|
31
|
+
case 'function':
|
|
32
|
+
case 'method':
|
|
33
|
+
if (modifiers.has('static')) {
|
|
34
|
+
return ['entity.name.function.static'];
|
|
35
|
+
}
|
|
36
|
+
if (modifiers.has('declaration')) {
|
|
37
|
+
return ['entity.name.function'];
|
|
38
|
+
}
|
|
39
|
+
return [type === 'method' ? 'entity.name.function.method' : 'entity.name.function.call'];
|
|
40
|
+
case 'decorator':
|
|
41
|
+
return ['meta.decorator'];
|
|
42
|
+
case 'macro':
|
|
43
|
+
return ['entity.name.function.macro'];
|
|
44
|
+
case 'variable':
|
|
45
|
+
if (scoped) {
|
|
46
|
+
return modifiers.has('local') ? null : ['variable.other.constant'];
|
|
47
|
+
}
|
|
48
|
+
return modifiers.has('readonly') ? ['variable.other.constant'] : null;
|
|
49
|
+
default:
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/* The encoded data of `textDocument/semanticTokens` as one token per classified range, the ones the theme has nothing to say about left out. */
|
|
54
|
+
export function decodeSemanticTokens(tokens, legend) {
|
|
55
|
+
const result = [];
|
|
56
|
+
const { data } = tokens;
|
|
57
|
+
const scoped = legend.tokenModifiers.includes('local');
|
|
58
|
+
let line = 0;
|
|
59
|
+
let character = 0;
|
|
60
|
+
for (let at = 0; at + 4 < data.length; at += 5) {
|
|
61
|
+
const deltaLine = data[at];
|
|
62
|
+
line += deltaLine;
|
|
63
|
+
character = deltaLine === 0 ? character + data[at + 1] : data[at + 1];
|
|
64
|
+
const type = legend.tokenTypes[data[at + 3]];
|
|
65
|
+
if (type === undefined) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const bits = data[at + 4];
|
|
69
|
+
const modifiers = new Set(legend.tokenModifiers.filter((_, index) => (bits & (1 << index)) !== 0));
|
|
70
|
+
const scopes = scopesOf(type, modifiers, scoped);
|
|
71
|
+
if (scopes !== null) {
|
|
72
|
+
result.push({ line, character, length: data[at + 2], scopes });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Refresher } from './refresher.js';
|
|
2
|
+
import { decodeSemanticTokens } from './semantic-model.js';
|
|
3
|
+
import { realTimers } from './timers.js';
|
|
4
|
+
const METHOD = 'textDocument/semanticTokens/full';
|
|
5
|
+
const PAUSE_MS = 200;
|
|
6
|
+
/*
|
|
7
|
+
* Colors the editor draws from what the language servers know, over what the grammar made of the text:
|
|
8
|
+
* a class is a class and a call is a call where the grammar could only guess. It asks after a pause in
|
|
9
|
+
* typing and when a server says its offer changed, and keeps drawing the last answer, which follows its
|
|
10
|
+
* text through edits, in between, so the colors never blink out while the next one is on its way.
|
|
11
|
+
*/
|
|
12
|
+
export class SemanticTokensFeature {
|
|
13
|
+
refresher;
|
|
14
|
+
constructor(language, timers = realTimers) {
|
|
15
|
+
const { editor, project, uri } = language;
|
|
16
|
+
this.refresher = new Refresher(async (signal) => {
|
|
17
|
+
const { service } = project;
|
|
18
|
+
const legend = service.providerOptions(METHOD, uri)?.legend;
|
|
19
|
+
if (!service.supports(METHOD, uri) || legend === undefined) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const tokens = await service.semanticTokens(uri, { signal });
|
|
23
|
+
if (!signal.aborted) {
|
|
24
|
+
editor.setSemanticTokens(tokens === null ? null : decodeSemanticTokens(tokens, legend));
|
|
25
|
+
}
|
|
26
|
+
}, PAUSE_MS, timers);
|
|
27
|
+
const edits = editor.onTextChange(() => this.refresher.later());
|
|
28
|
+
const providers = project.service.onProvidersChanged((changed) => {
|
|
29
|
+
if (changed === uri) {
|
|
30
|
+
this.refresher.now();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
language.onDispose(() => {
|
|
34
|
+
edits();
|
|
35
|
+
providers.dispose();
|
|
36
|
+
this.refresher.dispose();
|
|
37
|
+
editor.setSemanticTokens(null);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { type Keymap } from '@adecore/editor/keymap';
|
|
2
|
+
import type { KeyChord } from '@adecore/editor';
|
|
3
|
+
export interface LanguageShortcut extends KeyChord {
|
|
4
|
+
readonly apple: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function shortcutsOf(keymap: Keymap, apple: boolean): {
|
|
7
|
+
rename: LanguageShortcut | null;
|
|
8
|
+
acceptGhostWord: LanguageShortcut | null;
|
|
9
|
+
addCaretAbove: LanguageShortcut | null;
|
|
10
|
+
addCaretBelow: LanguageShortcut | null;
|
|
11
|
+
addCaretPerSelectedLine: LanguageShortcut | null;
|
|
12
|
+
autoIndentLines: LanguageShortcut | null;
|
|
13
|
+
codeActions: LanguageShortcut | null;
|
|
14
|
+
collapse: LanguageShortcut | null;
|
|
15
|
+
collapseAll: LanguageShortcut | null;
|
|
16
|
+
collapseDocComments: LanguageShortcut | null;
|
|
17
|
+
collapseRecursively: LanguageShortcut | null;
|
|
18
|
+
deleteLine: LanguageShortcut | null;
|
|
19
|
+
deleteWordLeft: LanguageShortcut | null;
|
|
20
|
+
deleteWordRight: LanguageShortcut | null;
|
|
21
|
+
duplicateLine: LanguageShortcut | null;
|
|
22
|
+
expand: LanguageShortcut | null;
|
|
23
|
+
expandAll: LanguageShortcut | null;
|
|
24
|
+
expandAllToLevel1: LanguageShortcut | null;
|
|
25
|
+
expandAllToLevel2: LanguageShortcut | null;
|
|
26
|
+
expandAllToLevel3: LanguageShortcut | null;
|
|
27
|
+
expandAllToLevel4: LanguageShortcut | null;
|
|
28
|
+
expandAllToLevel5: LanguageShortcut | null;
|
|
29
|
+
expandDocComments: LanguageShortcut | null;
|
|
30
|
+
expandRecursively: LanguageShortcut | null;
|
|
31
|
+
expandSelection: LanguageShortcut | null;
|
|
32
|
+
findNext: LanguageShortcut | null;
|
|
33
|
+
findPrevious: LanguageShortcut | null;
|
|
34
|
+
foldSelection: LanguageShortcut | null;
|
|
35
|
+
formatDocument: LanguageShortcut | null;
|
|
36
|
+
goToDefinition: LanguageShortcut | null;
|
|
37
|
+
goToImplementation: LanguageShortcut | null;
|
|
38
|
+
goToSymbol: LanguageShortcut | null;
|
|
39
|
+
goToTypeDefinition: LanguageShortcut | null;
|
|
40
|
+
historyBack: LanguageShortcut | null;
|
|
41
|
+
historyForward: LanguageShortcut | null;
|
|
42
|
+
inlineEdit: LanguageShortcut | null;
|
|
43
|
+
joinLines: LanguageShortcut | null;
|
|
44
|
+
matchBrace: LanguageShortcut | null;
|
|
45
|
+
moveLineDown: LanguageShortcut | null;
|
|
46
|
+
moveLineUp: LanguageShortcut | null;
|
|
47
|
+
nextHighlight: LanguageShortcut | null;
|
|
48
|
+
nextProblem: LanguageShortcut | null;
|
|
49
|
+
organizeImports: LanguageShortcut | null;
|
|
50
|
+
parameterInfo: LanguageShortcut | null;
|
|
51
|
+
peekDefinition: LanguageShortcut | null;
|
|
52
|
+
peekReferences: LanguageShortcut | null;
|
|
53
|
+
previousHighlight: LanguageShortcut | null;
|
|
54
|
+
previousProblem: LanguageShortcut | null;
|
|
55
|
+
quickInfo: LanguageShortcut | null;
|
|
56
|
+
recentLocations: LanguageShortcut | null;
|
|
57
|
+
redo: LanguageShortcut | null;
|
|
58
|
+
refactorThis: LanguageShortcut | null;
|
|
59
|
+
renameSymbol: LanguageShortcut | null;
|
|
60
|
+
replace: LanguageShortcut | null;
|
|
61
|
+
save: LanguageShortcut | null;
|
|
62
|
+
selectAll: LanguageShortcut | null;
|
|
63
|
+
selectAllOccurrences: LanguageShortcut | null;
|
|
64
|
+
selectNextOccurrence: LanguageShortcut | null;
|
|
65
|
+
selectSmartEnd: LanguageShortcut | null;
|
|
66
|
+
selectSmartHome: LanguageShortcut | null;
|
|
67
|
+
selectTextEnd: LanguageShortcut | null;
|
|
68
|
+
selectTextStart: LanguageShortcut | null;
|
|
69
|
+
selectWordLeft: LanguageShortcut | null;
|
|
70
|
+
selectWordRight: LanguageShortcut | null;
|
|
71
|
+
selectionToChat: LanguageShortcut | null;
|
|
72
|
+
shrinkSelection: LanguageShortcut | null;
|
|
73
|
+
smartEnd: LanguageShortcut | null;
|
|
74
|
+
smartHome: LanguageShortcut | null;
|
|
75
|
+
splitLine: LanguageShortcut | null;
|
|
76
|
+
startNewLine: LanguageShortcut | null;
|
|
77
|
+
startNewLineBefore: LanguageShortcut | null;
|
|
78
|
+
suggestInline: LanguageShortcut | null;
|
|
79
|
+
textEnd: LanguageShortcut | null;
|
|
80
|
+
textStart: LanguageShortcut | null;
|
|
81
|
+
toggleBlockComment: LanguageShortcut | null;
|
|
82
|
+
toggleCase: LanguageShortcut | null;
|
|
83
|
+
toggleColumnMode: LanguageShortcut | null;
|
|
84
|
+
toggleLineComment: LanguageShortcut | null;
|
|
85
|
+
triggerCompletion: LanguageShortcut | null;
|
|
86
|
+
undo: LanguageShortcut | null;
|
|
87
|
+
unselectOccurrence: LanguageShortcut | null;
|
|
88
|
+
wordLeft: LanguageShortcut | null;
|
|
89
|
+
wordRight: LanguageShortcut | null;
|
|
90
|
+
};
|
|
91
|
+
export declare function isShortcut(target: LanguageShortcut | null, event: KeyboardEvent): boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { chordOf, KEYMAP_IDS, parseChord } from '@adecore/editor/keymap';
|
|
2
|
+
import { matchesShortcut } from '@adecore/ui';
|
|
3
|
+
export function shortcutsOf(keymap, apple) {
|
|
4
|
+
const shortcuts = Object.fromEntries(KEYMAP_IDS.map((id) => {
|
|
5
|
+
const text = chordOf(id, apple, keymap);
|
|
6
|
+
return [id, text === null ? null : { ...parseChord(text), apple }];
|
|
7
|
+
}));
|
|
8
|
+
return { ...shortcuts, rename: shortcuts.renameSymbol };
|
|
9
|
+
}
|
|
10
|
+
export function isShortcut(target, event) {
|
|
11
|
+
return target !== null && matchesShortcut(target, event, target.apple);
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SignatureHelp, SignatureInformation } from '@adecore/lsp';
|
|
2
|
+
export interface SignatureViewModel {
|
|
3
|
+
readonly label: string;
|
|
4
|
+
readonly active: {
|
|
5
|
+
readonly start: number;
|
|
6
|
+
readonly end: number;
|
|
7
|
+
} | null;
|
|
8
|
+
readonly parameterName: string;
|
|
9
|
+
readonly parameterDocumentation: string;
|
|
10
|
+
readonly documentation: string;
|
|
11
|
+
readonly index: number;
|
|
12
|
+
readonly count: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function parameterSpan(signature: SignatureInformation, index: number): {
|
|
15
|
+
start: number;
|
|
16
|
+
end: number;
|
|
17
|
+
} | null;
|
|
18
|
+
export declare function signatureViewOf(help: SignatureHelp | null): SignatureViewModel | null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { documentationText } from './completion-model.js';
|
|
2
|
+
/* Where a parameter's label stands in the signature's: given as a pair of offsets, or as the text to find in it. */
|
|
3
|
+
export function parameterSpan(signature, index) {
|
|
4
|
+
const parameter = signature.parameters?.[index];
|
|
5
|
+
if (parameter === undefined) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
if (typeof parameter.label !== 'string') {
|
|
9
|
+
const [start, end] = parameter.label;
|
|
10
|
+
return start >= 0 && end <= signature.label.length && start < end ? { start, end } : null;
|
|
11
|
+
}
|
|
12
|
+
const start = signature.label.indexOf(parameter.label);
|
|
13
|
+
return start < 0 ? null : { start, end: start + parameter.label.length };
|
|
14
|
+
}
|
|
15
|
+
export function signatureViewOf(help) {
|
|
16
|
+
if (help === null || help.signatures.length === 0) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const index = Math.min(Math.max(0, help.activeSignature ?? 0), help.signatures.length - 1);
|
|
20
|
+
const signature = help.signatures[index];
|
|
21
|
+
const parameterIndex = signature.activeParameter ?? help.activeParameter ?? 0;
|
|
22
|
+
const active = parameterSpan(signature, parameterIndex);
|
|
23
|
+
const parameter = signature.parameters?.[parameterIndex];
|
|
24
|
+
return {
|
|
25
|
+
label: signature.label,
|
|
26
|
+
active,
|
|
27
|
+
parameterName: active === null ? '' : signature.label.slice(active.start, active.end).replace(/[?:].*$/s, ''),
|
|
28
|
+
parameterDocumentation: documentationText(parameter?.documentation),
|
|
29
|
+
documentation: documentationText(signature.documentation),
|
|
30
|
+
index,
|
|
31
|
+
count: help.signatures.length
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
2
|
+
import { type Timers } from './timers.ts';
|
|
3
|
+
export declare class SignatureFeature {
|
|
4
|
+
private readonly language;
|
|
5
|
+
private readonly timers;
|
|
6
|
+
private timer;
|
|
7
|
+
private request;
|
|
8
|
+
private controller;
|
|
9
|
+
private help;
|
|
10
|
+
private anchor;
|
|
11
|
+
constructor(language: EditorLanguage, timers?: Timers);
|
|
12
|
+
get isOpen(): boolean;
|
|
13
|
+
invoke(): void;
|
|
14
|
+
trigger(): void;
|
|
15
|
+
private cycle;
|
|
16
|
+
close(): void;
|
|
17
|
+
private options;
|
|
18
|
+
private edited;
|
|
19
|
+
private retrigger;
|
|
20
|
+
private ask;
|
|
21
|
+
private fetch;
|
|
22
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { signatureViewOf } from './signature-model.js';
|
|
2
|
+
import { isShortcut } from './shortcut-keys.js';
|
|
3
|
+
import { realTimers } from './timers.js';
|
|
4
|
+
const METHOD = 'textDocument/signatureHelp';
|
|
5
|
+
const PAUSE_MS = 60;
|
|
6
|
+
/*
|
|
7
|
+
* The parameters of the call the caret is in, while its arguments are typed. It opens on a trigger
|
|
8
|
+
* character of the server, such as `(` and `,`, and asks again as the caret moves or the text changes,
|
|
9
|
+
* so the active parameter follows; the server answering nothing is the caret leaving the call.
|
|
10
|
+
*/
|
|
11
|
+
export class SignatureFeature {
|
|
12
|
+
language;
|
|
13
|
+
timers;
|
|
14
|
+
timer;
|
|
15
|
+
request = 0;
|
|
16
|
+
controller = null;
|
|
17
|
+
help = null;
|
|
18
|
+
anchor = null;
|
|
19
|
+
constructor(language, timers = realTimers) {
|
|
20
|
+
this.language = language;
|
|
21
|
+
this.timers = timers;
|
|
22
|
+
const { editor } = language;
|
|
23
|
+
const offs = [
|
|
24
|
+
editor.onTextChange((change) => this.edited(change)),
|
|
25
|
+
editor.onCaret(() => this.retrigger()),
|
|
26
|
+
editor.onBlur(() => this.close()),
|
|
27
|
+
editor.onKeyDown((event) => {
|
|
28
|
+
if (event.key === 'Escape' && this.isOpen) {
|
|
29
|
+
this.close();
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (isShortcut(this.language.shortcuts.parameterInfo, event)) {
|
|
33
|
+
this.invoke();
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if ((event.key === 'ArrowUp' || event.key === 'ArrowDown') && !event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
|
|
37
|
+
return this.cycle(event.key === 'ArrowDown' ? 1 : -1);
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
})
|
|
41
|
+
];
|
|
42
|
+
language.onDispose(() => {
|
|
43
|
+
for (const off of offs) {
|
|
44
|
+
off();
|
|
45
|
+
}
|
|
46
|
+
this.close();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
get isOpen() {
|
|
50
|
+
return this.language.popups.getState().signature !== null;
|
|
51
|
+
}
|
|
52
|
+
/* Asks for the parameters of the call at the caret, as its key does. */
|
|
53
|
+
invoke() {
|
|
54
|
+
this.ask({ triggerKind: 1, isRetrigger: false });
|
|
55
|
+
}
|
|
56
|
+
/* Asks for the parameters as if `(` had just been typed, which is what a call opened by a completion is. */
|
|
57
|
+
trigger() {
|
|
58
|
+
this.ask({ triggerKind: 2, triggerCharacter: '(', isRetrigger: false });
|
|
59
|
+
}
|
|
60
|
+
/* Shows the next overload, or the one before, while the card is up and has more than one; false when it has not, which leaves the arrow to the caret. */
|
|
61
|
+
cycle(step) {
|
|
62
|
+
const { help } = this;
|
|
63
|
+
if (help === null || help.signatures.length < 2 || !this.isOpen) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const count = help.signatures.length;
|
|
67
|
+
const next = { ...help, activeSignature: (Math.min(Math.max(0, help.activeSignature ?? 0), count - 1) + step + count) % count };
|
|
68
|
+
const model = signatureViewOf(next);
|
|
69
|
+
if (model === null || this.anchor === null) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
this.help = next;
|
|
73
|
+
this.language.popups.setState({ signature: { anchor: this.anchor, model } });
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
close() {
|
|
77
|
+
this.request++;
|
|
78
|
+
this.controller?.abort();
|
|
79
|
+
this.timers.clear(this.timer);
|
|
80
|
+
this.help = null;
|
|
81
|
+
this.anchor = null;
|
|
82
|
+
if (this.isOpen) {
|
|
83
|
+
this.language.popups.setState({ signature: null });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
options() {
|
|
87
|
+
const { project, uri } = this.language;
|
|
88
|
+
const options = project.service.providerOptions(METHOD, uri);
|
|
89
|
+
return options === undefined ? null : { triggers: options.triggerCharacters ?? [], retriggers: options.retriggerCharacters ?? [] };
|
|
90
|
+
}
|
|
91
|
+
edited(change) {
|
|
92
|
+
const options = this.options();
|
|
93
|
+
if (change.source !== 'input' || options === null) {
|
|
94
|
+
this.close();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const text = change.changes.length === 1 ? change.changes[0].text : '';
|
|
98
|
+
const last = Array.from(text).at(-1) ?? '';
|
|
99
|
+
if (text.length === 1 && options.triggers.includes(last)) {
|
|
100
|
+
this.ask({ triggerKind: 2, triggerCharacter: last, isRetrigger: this.isOpen });
|
|
101
|
+
}
|
|
102
|
+
else if (this.isOpen) {
|
|
103
|
+
this.ask({
|
|
104
|
+
triggerKind: text.length === 1 && options.retriggers.includes(last) ? 2 : 3,
|
|
105
|
+
...(options.retriggers.includes(last) ? { triggerCharacter: last } : {}),
|
|
106
|
+
isRetrigger: true
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
retrigger() {
|
|
111
|
+
if (this.isOpen) {
|
|
112
|
+
this.ask({ triggerKind: 3, isRetrigger: true });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
ask(context) {
|
|
116
|
+
const { project, uri } = this.language;
|
|
117
|
+
if (!project.service.supports(METHOD, uri)) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.timers.clear(this.timer);
|
|
121
|
+
const request = ++this.request;
|
|
122
|
+
const full = { ...context, ...(this.help === null ? {} : { activeSignatureHelp: this.help }) };
|
|
123
|
+
this.timer = this.timers.set(() => void this.fetch(full, request), PAUSE_MS);
|
|
124
|
+
}
|
|
125
|
+
async fetch(context, request) {
|
|
126
|
+
const { editor, project, uri } = this.language;
|
|
127
|
+
const caret = editor.getCaret();
|
|
128
|
+
this.controller?.abort();
|
|
129
|
+
const controller = new AbortController();
|
|
130
|
+
this.controller = controller;
|
|
131
|
+
let help;
|
|
132
|
+
try {
|
|
133
|
+
help = await project.service.signatureHelp(uri, caret, context, { signal: controller.signal });
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (request !== this.request) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const model = signatureViewOf(help);
|
|
142
|
+
if (model === null) {
|
|
143
|
+
this.close();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
this.help = help;
|
|
147
|
+
this.anchor ??= caret;
|
|
148
|
+
this.language.popups.setState({ signature: { anchor: this.anchor, model } });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { EditorPosition } from '@adecore/editor';
|
|
2
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
3
|
+
import type { SnippetStop } from './snippet.ts';
|
|
4
|
+
export declare function positionInInsertion(start: EditorPosition, text: string, offset: number): EditorPosition;
|
|
5
|
+
export declare class SnippetFeature {
|
|
6
|
+
private readonly language;
|
|
7
|
+
private stops;
|
|
8
|
+
private current;
|
|
9
|
+
constructor(language: EditorLanguage);
|
|
10
|
+
get isActive(): boolean;
|
|
11
|
+
begin(start: EditorPosition, text: string, stops: readonly SnippetStop[]): void;
|
|
12
|
+
end(): void;
|
|
13
|
+
private visit;
|
|
14
|
+
private key;
|
|
15
|
+
private edited;
|
|
16
|
+
private moved;
|
|
17
|
+
}
|