@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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { isIdentifierCharacter } from './completion-model.js';
|
|
2
|
+
import { locationsOf } from './hover-content.js';
|
|
3
|
+
import { wordRangeAt } from './rename-model.js';
|
|
4
|
+
/*
|
|
5
|
+
* The name under the pointer drawn as a link while Cmd (Ctrl elsewhere) is held and a server knows where it
|
|
6
|
+
* is defined, which is what a Mod+click on it follows. The editor only says where the pointer is, so the
|
|
7
|
+
* key comes from the window.
|
|
8
|
+
*/
|
|
9
|
+
export class DefinitionLinkFeature {
|
|
10
|
+
language;
|
|
11
|
+
held = false;
|
|
12
|
+
pointer = null;
|
|
13
|
+
shownKey = '';
|
|
14
|
+
drawn = false;
|
|
15
|
+
request = 0;
|
|
16
|
+
lookup = null;
|
|
17
|
+
constructor(language) {
|
|
18
|
+
this.language = language;
|
|
19
|
+
const { editor } = language;
|
|
20
|
+
const offs = [
|
|
21
|
+
editor.onHover((hover) => {
|
|
22
|
+
this.pointer = hover?.position ?? null;
|
|
23
|
+
this.update();
|
|
24
|
+
}),
|
|
25
|
+
editor.onTextChange(() => this.clear()),
|
|
26
|
+
editor.onViewChange(() => this.clear())
|
|
27
|
+
];
|
|
28
|
+
if (typeof window !== 'undefined') {
|
|
29
|
+
const modKey = language.apple ? 'Meta' : 'Control';
|
|
30
|
+
const keys = (event) => {
|
|
31
|
+
if (event.key === modKey) {
|
|
32
|
+
this.setModHeld(event.type === 'keydown');
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const lost = () => this.setModHeld(false);
|
|
36
|
+
window.addEventListener('keydown', keys);
|
|
37
|
+
window.addEventListener('keyup', keys);
|
|
38
|
+
window.addEventListener('blur', lost);
|
|
39
|
+
offs.push(() => {
|
|
40
|
+
window.removeEventListener('keydown', keys);
|
|
41
|
+
window.removeEventListener('keyup', keys);
|
|
42
|
+
window.removeEventListener('blur', lost);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
language.onDispose(() => {
|
|
46
|
+
for (const off of offs) {
|
|
47
|
+
off();
|
|
48
|
+
}
|
|
49
|
+
this.clear();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
setModHeld(held) {
|
|
53
|
+
if (held !== this.held) {
|
|
54
|
+
this.held = held;
|
|
55
|
+
this.update();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
clear() {
|
|
59
|
+
this.request++;
|
|
60
|
+
this.lookup?.abort();
|
|
61
|
+
this.shownKey = '';
|
|
62
|
+
if (this.drawn) {
|
|
63
|
+
this.drawn = false;
|
|
64
|
+
this.language.editor.setLink(null);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
update() {
|
|
68
|
+
const { pointer } = this;
|
|
69
|
+
if (!this.held || pointer === null || !this.language.project.service.supports('textDocument/definition', this.language.uri)) {
|
|
70
|
+
this.clear();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const { editor, project, uri } = this.language;
|
|
74
|
+
const line = editor.textInRange({ start: { line: pointer.line, character: 0 }, end: { line: pointer.line, character: Number.MAX_SAFE_INTEGER } });
|
|
75
|
+
const word = isIdentifierCharacter(line[pointer.character] ?? '') ? wordRangeAt(line, pointer) : null;
|
|
76
|
+
if (word === null) {
|
|
77
|
+
this.clear();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const key = `${word.start.line}:${word.start.character}`;
|
|
81
|
+
if (key === this.shownKey) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.clear();
|
|
85
|
+
this.shownKey = key;
|
|
86
|
+
const request = ++this.request;
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
this.lookup = controller;
|
|
89
|
+
project.service
|
|
90
|
+
.definition(uri, pointer, { signal: controller.signal })
|
|
91
|
+
.then((result) => {
|
|
92
|
+
if (request === this.request && locationsOf(result).length > 0) {
|
|
93
|
+
this.drawn = true;
|
|
94
|
+
editor.setLink(word);
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
.catch(() => undefined);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EditorContentChange, EditorMarker, EditorMarkerSeverity, EditorPosition, EditorRange } from '@adecore/editor';
|
|
2
|
+
import type { Diagnostic } from '@adecore/lsp';
|
|
3
|
+
export interface Problem {
|
|
4
|
+
readonly diagnostic: Diagnostic;
|
|
5
|
+
readonly server: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function severityOf(diagnostic: Diagnostic): EditorMarkerSeverity;
|
|
8
|
+
export declare function markerOf(diagnostic: Diagnostic): EditorMarker;
|
|
9
|
+
export declare function comparePositions(left: EditorPosition, right: EditorPosition): number;
|
|
10
|
+
export declare function shiftPosition(position: EditorPosition, change: EditorContentChange): EditorPosition;
|
|
11
|
+
export declare function shiftRange(range: EditorRange, changes: readonly EditorContentChange[]): EditorRange;
|
|
12
|
+
export declare function rangeHolds(range: EditorRange, position: EditorPosition): boolean;
|
|
13
|
+
export declare function problemsAt(problems: readonly Problem[], position: EditorPosition): Problem[];
|
|
14
|
+
export declare function neighborProblem(problems: readonly Problem[], from: EditorPosition, direction: 1 | -1): Problem | null;
|
|
15
|
+
export declare function codeLabelOf(diagnostic: Diagnostic): string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const SEVERITIES = { 1: 'error', 2: 'warning', 3: 'info', 4: 'hint' };
|
|
2
|
+
const UNNECESSARY = 1;
|
|
3
|
+
const DEPRECATED = 2;
|
|
4
|
+
/* The scroll track's tip is a line or two, so a long message is cut where it stops being a tip. */
|
|
5
|
+
const TICK_MESSAGE_LENGTH = 300;
|
|
6
|
+
/* A report that names no severity is an error, which is the safer side to be wrong on. */
|
|
7
|
+
export function severityOf(diagnostic) {
|
|
8
|
+
return SEVERITIES[diagnostic.severity ?? 1] ?? 'error';
|
|
9
|
+
}
|
|
10
|
+
export function markerOf(diagnostic) {
|
|
11
|
+
return {
|
|
12
|
+
range: diagnostic.range,
|
|
13
|
+
severity: severityOf(diagnostic),
|
|
14
|
+
unnecessary: diagnostic.tags?.includes(UNNECESSARY) === true,
|
|
15
|
+
deprecated: diagnostic.tags?.includes(DEPRECATED) === true,
|
|
16
|
+
message: diagnostic.message.length > TICK_MESSAGE_LENGTH ? `${diagnostic.message.slice(0, TICK_MESSAGE_LENGTH)}…` : diagnostic.message
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function comparePositions(left, right) {
|
|
20
|
+
return left.line === right.line ? left.character - right.character : left.line - right.line;
|
|
21
|
+
}
|
|
22
|
+
/* Where an insertion of `text` at `start` ends, counting a line break of any kind once. */
|
|
23
|
+
function endOfInsertion(start, text) {
|
|
24
|
+
const lines = text.split(/\r\n|\r|\n/);
|
|
25
|
+
return lines.length === 1
|
|
26
|
+
? { line: start.line, character: start.character + text.length }
|
|
27
|
+
: { line: start.line + lines.length - 1, character: lines[lines.length - 1].length };
|
|
28
|
+
}
|
|
29
|
+
/* Where a position is once one change is applied to the text it was in. One inside the replaced range lands after the new text. */
|
|
30
|
+
export function shiftPosition(position, change) {
|
|
31
|
+
const { start, end } = change.range;
|
|
32
|
+
if (comparePositions(position, start) < 0) {
|
|
33
|
+
return position;
|
|
34
|
+
}
|
|
35
|
+
const inserted = endOfInsertion(start, change.text);
|
|
36
|
+
if (comparePositions(position, end) < 0) {
|
|
37
|
+
return inserted;
|
|
38
|
+
}
|
|
39
|
+
return position.line === end.line
|
|
40
|
+
? { line: inserted.line, character: inserted.character + (position.character - end.character) }
|
|
41
|
+
: { line: position.line + (inserted.line - end.line), character: position.character };
|
|
42
|
+
}
|
|
43
|
+
export function shiftRange(range, changes) {
|
|
44
|
+
let start = range.start;
|
|
45
|
+
let end = range.end;
|
|
46
|
+
for (const change of changes) {
|
|
47
|
+
start = shiftPosition(start, change);
|
|
48
|
+
end = shiftPosition(end, change);
|
|
49
|
+
}
|
|
50
|
+
return { start, end };
|
|
51
|
+
}
|
|
52
|
+
/* A range holds a position from its start up to and including its end, so a caret at either edge of a word is on it. An empty range holds only its own place. */
|
|
53
|
+
export function rangeHolds(range, position) {
|
|
54
|
+
return comparePositions(range.start, position) <= 0 && comparePositions(position, range.end) <= 0;
|
|
55
|
+
}
|
|
56
|
+
/* The problems under a position, worst first. A hint is not a problem, so it is left to the colors and the faded text. */
|
|
57
|
+
export function problemsAt(problems, position) {
|
|
58
|
+
const rank = (problem) => problem.diagnostic.severity ?? 1;
|
|
59
|
+
return problems
|
|
60
|
+
.filter((problem) => (problem.diagnostic.severity ?? 1) < 4 && rangeHolds(problem.diagnostic.range, position))
|
|
61
|
+
.sort((left, right) => rank(left) - rank(right));
|
|
62
|
+
}
|
|
63
|
+
/* The start of the first problem after the position, or the last one before it when `direction` is back; it wraps round the document. */
|
|
64
|
+
export function neighborProblem(problems, from, direction) {
|
|
65
|
+
const candidates = problems
|
|
66
|
+
.filter((problem) => (problem.diagnostic.severity ?? 1) < 4)
|
|
67
|
+
.sort((left, right) => comparePositions(left.diagnostic.range.start, right.diagnostic.range.start));
|
|
68
|
+
if (candidates.length === 0) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (direction === 1) {
|
|
72
|
+
return candidates.find((problem) => comparePositions(problem.diagnostic.range.start, from) > 0) ?? candidates[0];
|
|
73
|
+
}
|
|
74
|
+
return [...candidates].reverse().find((problem) => comparePositions(problem.diagnostic.range.start, from) < 0) ?? candidates[candidates.length - 1];
|
|
75
|
+
}
|
|
76
|
+
/* `ts(2551)`, or the code alone, or the source alone, whichever the server gave. */
|
|
77
|
+
export function codeLabelOf(diagnostic) {
|
|
78
|
+
const code = diagnostic.code === undefined ? '' : String(diagnostic.code);
|
|
79
|
+
if (diagnostic.source !== undefined && code !== '') {
|
|
80
|
+
return `${diagnostic.source}(${code})`;
|
|
81
|
+
}
|
|
82
|
+
return code !== '' ? code : (diagnostic.source ?? '');
|
|
83
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { EditorPosition } from '@adecore/editor';
|
|
2
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
3
|
+
import { type Problem } from './diagnostics-model.ts';
|
|
4
|
+
export interface ProblemCounts {
|
|
5
|
+
readonly error: number;
|
|
6
|
+
readonly warning: number;
|
|
7
|
+
readonly info: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class DiagnosticsFeature {
|
|
10
|
+
private readonly language;
|
|
11
|
+
private readonly reports;
|
|
12
|
+
private readonly listeners;
|
|
13
|
+
private list;
|
|
14
|
+
constructor(language: EditorLanguage);
|
|
15
|
+
get problems(): readonly Problem[];
|
|
16
|
+
counts(): ProblemCounts;
|
|
17
|
+
at(position: EditorPosition): Problem[];
|
|
18
|
+
step(direction: 1 | -1): boolean;
|
|
19
|
+
goToFirst(): boolean;
|
|
20
|
+
onChange(listener: () => void): () => void;
|
|
21
|
+
private shift;
|
|
22
|
+
private rebuild;
|
|
23
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { isShortcut } from './shortcut-keys.js';
|
|
2
|
+
import { comparePositions, markerOf, neighborProblem, problemsAt, shiftRange } from './diagnostics-model.js';
|
|
3
|
+
/*
|
|
4
|
+
* The problems the servers report for one file. A report from a server replaces the one before it
|
|
5
|
+
* from the same server, the editor draws them, and each one follows its text through edits made
|
|
6
|
+
* before the server reports again, so what is under a squiggle is still what it was about.
|
|
7
|
+
*/
|
|
8
|
+
export class DiagnosticsFeature {
|
|
9
|
+
language;
|
|
10
|
+
reports = new Map();
|
|
11
|
+
listeners = new Set();
|
|
12
|
+
list = [];
|
|
13
|
+
constructor(language) {
|
|
14
|
+
this.language = language;
|
|
15
|
+
const { editor, project, uri } = language;
|
|
16
|
+
const reports = project.service.onDiagnostics((report) => {
|
|
17
|
+
if (report.uri === uri) {
|
|
18
|
+
this.reports.set(report.source, report.diagnostics);
|
|
19
|
+
this.rebuild(true);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const edits = editor.onTextChange((change) => this.shift(change.changes));
|
|
23
|
+
// These keys are the editor's while it has the keyboard; elsewhere the menu's commands answer.
|
|
24
|
+
const keys = editor.onKeyDown((event) => {
|
|
25
|
+
const next = isShortcut(this.language.shortcuts.nextProblem, event);
|
|
26
|
+
if (!next && !isShortcut(this.language.shortcuts.previousProblem, event)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
this.step(next ? 1 : -1);
|
|
30
|
+
return true;
|
|
31
|
+
});
|
|
32
|
+
language.onDispose(() => {
|
|
33
|
+
keys();
|
|
34
|
+
reports.dispose();
|
|
35
|
+
edits();
|
|
36
|
+
this.listeners.clear();
|
|
37
|
+
editor.setMarkers([]);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
get problems() {
|
|
41
|
+
return this.list;
|
|
42
|
+
}
|
|
43
|
+
counts() {
|
|
44
|
+
const count = (severity) => this.list.filter((problem) => (problem.diagnostic.severity ?? 1) === severity).length;
|
|
45
|
+
return { error: count(1), warning: count(2), info: count(3) };
|
|
46
|
+
}
|
|
47
|
+
at(position) {
|
|
48
|
+
return problemsAt(this.list, position);
|
|
49
|
+
}
|
|
50
|
+
/* Puts the caret on the next problem, or the one before; false when there is none. */
|
|
51
|
+
step(direction) {
|
|
52
|
+
const { editor } = this.language;
|
|
53
|
+
const target = neighborProblem(this.list, editor.getCaret(), direction);
|
|
54
|
+
if (target === null) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
editor.setCaret(target.diagnostic.range.start, comparePositions(target.diagnostic.range.start, editor.getCaret()) > 0 ? 'centerDown' : 'centerUp');
|
|
58
|
+
editor.focus();
|
|
59
|
+
this.language.hover.showProblemsAt(editor.getCaret());
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
/* Puts the caret on the first problem of the file. */
|
|
63
|
+
goToFirst() {
|
|
64
|
+
const { editor } = this.language;
|
|
65
|
+
const target = neighborProblem(this.list, { line: -1, character: 0 }, 1);
|
|
66
|
+
if (target === null) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
editor.setCaret(target.diagnostic.range.start, 'center');
|
|
70
|
+
editor.focus();
|
|
71
|
+
this.language.hover.showProblemsAt(editor.getCaret());
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
onChange(listener) {
|
|
75
|
+
this.listeners.add(listener);
|
|
76
|
+
return () => {
|
|
77
|
+
this.listeners.delete(listener);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
shift(changes) {
|
|
81
|
+
for (const [server, diagnostics] of this.reports) {
|
|
82
|
+
this.reports.set(server, diagnostics.map((diagnostic) => ({ ...diagnostic, range: shiftRange(diagnostic.range, changes) })));
|
|
83
|
+
}
|
|
84
|
+
this.rebuild(false);
|
|
85
|
+
}
|
|
86
|
+
rebuild(draw) {
|
|
87
|
+
this.list = [...this.reports].flatMap(([server, diagnostics]) => diagnostics.map((diagnostic) => ({ diagnostic, server })));
|
|
88
|
+
if (draw) {
|
|
89
|
+
this.language.editor.setMarkers(this.list.map((problem) => markerOf(problem.diagnostic)));
|
|
90
|
+
}
|
|
91
|
+
for (const listener of [...this.listeners]) {
|
|
92
|
+
listener();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type Keymap } from '@adecore/editor/keymap';
|
|
2
|
+
import { shortcutsOf } from './shortcut-keys.ts';
|
|
3
|
+
import type { Editor, EditorPosition } from '@adecore/editor';
|
|
4
|
+
import type { Location } from '@adecore/lsp';
|
|
5
|
+
import { CodeActionsFeature } from './code-actions.ts';
|
|
6
|
+
import { CodeVisionFeature } from './code-vision.ts';
|
|
7
|
+
import { CompletionFeature } from './completion.ts';
|
|
8
|
+
import { ContextMenuFeature } from './context-menu.ts';
|
|
9
|
+
import { DefinitionLinkFeature } from './definition-link.ts';
|
|
10
|
+
import { DiagnosticsFeature } from './diagnostics.ts';
|
|
11
|
+
import { HighlightsFeature } from './highlights.ts';
|
|
12
|
+
import { HistoryFeature } from './history.ts';
|
|
13
|
+
import { HoverFeature } from './hover.ts';
|
|
14
|
+
import { NavigationFeature } from './navigation.ts';
|
|
15
|
+
import { PeekFeature } from './peek.ts';
|
|
16
|
+
import { PickFeature } from './pick.ts';
|
|
17
|
+
import { SymbolPickerFeature } from './symbol-picker.ts';
|
|
18
|
+
import { RenameFeature } from './rename.ts';
|
|
19
|
+
import { type Timers } from './timers.ts';
|
|
20
|
+
import { SignatureFeature } from './signature.ts';
|
|
21
|
+
import { SnippetFeature } from './snippet-session.ts';
|
|
22
|
+
import { SymbolsFeature } from './symbols.ts';
|
|
23
|
+
import type { Place } from './navigation-history.ts';
|
|
24
|
+
import type { LanguageDocumentHandle, ProjectLanguage } from './project-language.ts';
|
|
25
|
+
export declare class EditorLanguage {
|
|
26
|
+
readonly editor: Editor;
|
|
27
|
+
readonly apple: boolean;
|
|
28
|
+
readonly keymap: Keymap;
|
|
29
|
+
readonly shortcuts: ReturnType<typeof shortcutsOf>;
|
|
30
|
+
readonly project: ProjectLanguage;
|
|
31
|
+
readonly document: LanguageDocumentHandle;
|
|
32
|
+
readonly languageId: string;
|
|
33
|
+
readonly popups: import("./popups.ts").PopupStore;
|
|
34
|
+
readonly diagnostics: DiagnosticsFeature;
|
|
35
|
+
readonly hover: HoverFeature;
|
|
36
|
+
readonly completion: CompletionFeature;
|
|
37
|
+
readonly signature: SignatureFeature;
|
|
38
|
+
readonly snippets: SnippetFeature;
|
|
39
|
+
readonly highlights: HighlightsFeature;
|
|
40
|
+
readonly pick: PickFeature;
|
|
41
|
+
readonly codeActions: CodeActionsFeature;
|
|
42
|
+
readonly rename: RenameFeature;
|
|
43
|
+
readonly navigation: NavigationFeature;
|
|
44
|
+
readonly peek: PeekFeature;
|
|
45
|
+
readonly symbolPicker: SymbolPickerFeature;
|
|
46
|
+
readonly contextMenu: ContextMenuFeature;
|
|
47
|
+
readonly history: HistoryFeature;
|
|
48
|
+
readonly definitionLink: DefinitionLinkFeature;
|
|
49
|
+
readonly symbols: SymbolsFeature;
|
|
50
|
+
readonly codeVision: CodeVisionFeature;
|
|
51
|
+
private readonly disposers;
|
|
52
|
+
private disposed;
|
|
53
|
+
constructor(project: ProjectLanguage, editor: Editor, uri: string, languageId: string, timers?: Timers);
|
|
54
|
+
private placeOpenedCaret;
|
|
55
|
+
get uri(): string;
|
|
56
|
+
get isDisposed(): boolean;
|
|
57
|
+
get place(): Place;
|
|
58
|
+
goTo(location: Location): void;
|
|
59
|
+
jump(position: EditorPosition): void;
|
|
60
|
+
visit(place: Place): void;
|
|
61
|
+
locations(places: readonly Location[], anchor: EditorPosition, title: string): void;
|
|
62
|
+
onDispose(dispose: () => void): void;
|
|
63
|
+
dispose(): void;
|
|
64
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { KEYMAP } from '@adecore/editor/keymap';
|
|
2
|
+
import { shortcutsOf } from './shortcut-keys.js';
|
|
3
|
+
import { CodeActionsFeature } from './code-actions.js';
|
|
4
|
+
import { CodeVisionFeature } from './code-vision.js';
|
|
5
|
+
import { CompletionFeature } from './completion.js';
|
|
6
|
+
import { ContextMenuFeature } from './context-menu.js';
|
|
7
|
+
import { DefinitionLinkFeature } from './definition-link.js';
|
|
8
|
+
import { DiagnosticsFeature } from './diagnostics.js';
|
|
9
|
+
import { FoldingFeature } from './folding.js';
|
|
10
|
+
import { HighlightsFeature } from './highlights.js';
|
|
11
|
+
import { HistoryFeature } from './history.js';
|
|
12
|
+
import { HoverFeature } from './hover.js';
|
|
13
|
+
import { NavigationFeature, locationRow } from './navigation.js';
|
|
14
|
+
import { PeekFeature } from './peek.js';
|
|
15
|
+
import { PickFeature } from './pick.js';
|
|
16
|
+
import { SymbolPickerFeature } from './symbol-picker.js';
|
|
17
|
+
import { RenameFeature } from './rename.js';
|
|
18
|
+
import { createPopupStore } from './popups.js';
|
|
19
|
+
import { realTimers } from './timers.js';
|
|
20
|
+
import { InlayHintsFeature } from './inlay-hints.js';
|
|
21
|
+
import { SelectionRangesFeature } from './selection-ranges.js';
|
|
22
|
+
import { SemanticTokensFeature } from './semantic-tokens.js';
|
|
23
|
+
import { SignatureFeature } from './signature.js';
|
|
24
|
+
import { SnippetFeature } from './snippet-session.js';
|
|
25
|
+
import { SymbolsFeature } from './symbols.js';
|
|
26
|
+
/*
|
|
27
|
+
* The one place that connects an editor to the language servers: it opens the file's document for
|
|
28
|
+
* the editor and lets go of it again. Every language feature of the editor hangs off this object.
|
|
29
|
+
*/
|
|
30
|
+
export class EditorLanguage {
|
|
31
|
+
editor;
|
|
32
|
+
apple;
|
|
33
|
+
keymap;
|
|
34
|
+
shortcuts;
|
|
35
|
+
project;
|
|
36
|
+
document;
|
|
37
|
+
languageId;
|
|
38
|
+
/* What the features of this editor have open over it: the hover card, the suggestions, the signature. */
|
|
39
|
+
popups = createPopupStore();
|
|
40
|
+
diagnostics;
|
|
41
|
+
hover;
|
|
42
|
+
completion;
|
|
43
|
+
signature;
|
|
44
|
+
snippets;
|
|
45
|
+
highlights;
|
|
46
|
+
pick;
|
|
47
|
+
codeActions;
|
|
48
|
+
rename;
|
|
49
|
+
navigation;
|
|
50
|
+
peek;
|
|
51
|
+
symbolPicker;
|
|
52
|
+
contextMenu;
|
|
53
|
+
history;
|
|
54
|
+
definitionLink;
|
|
55
|
+
symbols;
|
|
56
|
+
codeVision;
|
|
57
|
+
disposers = [];
|
|
58
|
+
disposed = false;
|
|
59
|
+
constructor(project, editor, uri, languageId, timers = realTimers) {
|
|
60
|
+
this.editor = editor;
|
|
61
|
+
this.apple = project.host.apple ?? false;
|
|
62
|
+
this.keymap = project.host.keymap ?? KEYMAP;
|
|
63
|
+
this.shortcuts = shortcutsOf(this.keymap, this.apple);
|
|
64
|
+
this.project = project;
|
|
65
|
+
this.languageId = languageId;
|
|
66
|
+
this.document = project.acquire(uri, languageId, editor);
|
|
67
|
+
this.pick = new PickFeature(this, timers);
|
|
68
|
+
this.diagnostics = new DiagnosticsFeature(this);
|
|
69
|
+
this.hover = new HoverFeature(this, timers);
|
|
70
|
+
this.completion = new CompletionFeature(this, timers);
|
|
71
|
+
this.snippets = new SnippetFeature(this);
|
|
72
|
+
this.signature = new SignatureFeature(this, timers);
|
|
73
|
+
this.highlights = new HighlightsFeature(this, timers);
|
|
74
|
+
this.symbols = new SymbolsFeature(this, timers);
|
|
75
|
+
new SelectionRangesFeature(this);
|
|
76
|
+
new SemanticTokensFeature(this, timers);
|
|
77
|
+
new InlayHintsFeature(this, timers);
|
|
78
|
+
this.codeActions = new CodeActionsFeature(this, timers);
|
|
79
|
+
this.rename = new RenameFeature(this);
|
|
80
|
+
this.navigation = new NavigationFeature(this);
|
|
81
|
+
this.peek = new PeekFeature(this);
|
|
82
|
+
this.symbolPicker = new SymbolPickerFeature(this);
|
|
83
|
+
this.contextMenu = new ContextMenuFeature(this);
|
|
84
|
+
this.history = new HistoryFeature(this);
|
|
85
|
+
this.definitionLink = new DefinitionLinkFeature(this);
|
|
86
|
+
this.codeVision = new CodeVisionFeature(this, (listener) => this.symbols.onResult(listener), timers);
|
|
87
|
+
new FoldingFeature(this, (listener) => this.symbols.onResult(listener), timers);
|
|
88
|
+
this.placeOpenedCaret(editor.getCaret());
|
|
89
|
+
this.onDispose(editor.onCaret((position) => this.placeOpenedCaret(position)));
|
|
90
|
+
}
|
|
91
|
+
/* A file opened from another one lands on the line; this puts the caret on the column the jump meant. */
|
|
92
|
+
placeOpenedCaret(position) {
|
|
93
|
+
const column = this.project.takeCaret(this.uri, position.line);
|
|
94
|
+
if (column !== null && column.character !== position.character) {
|
|
95
|
+
this.editor.setCaret(column);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.project.noteCaret({ uri: this.uri, position });
|
|
99
|
+
}
|
|
100
|
+
get uri() {
|
|
101
|
+
return this.document.uri;
|
|
102
|
+
}
|
|
103
|
+
get isDisposed() {
|
|
104
|
+
return this.disposed;
|
|
105
|
+
}
|
|
106
|
+
/* Where the caret is, as a place the history can come back to. */
|
|
107
|
+
get place() {
|
|
108
|
+
return { uri: this.uri, position: this.editor.getCaret() };
|
|
109
|
+
}
|
|
110
|
+
/* Takes the caret to a place the servers named, in this file or in another one that opens beside it, and remembers where it was. */
|
|
111
|
+
goTo(location) {
|
|
112
|
+
this.project.history.record(this.place);
|
|
113
|
+
this.visit({ uri: location.uri, position: location.range.start });
|
|
114
|
+
}
|
|
115
|
+
/* Takes the caret to a position of this file and remembers where it was. */
|
|
116
|
+
jump(position) {
|
|
117
|
+
this.goTo({ uri: this.uri, range: { start: position, end: position } });
|
|
118
|
+
}
|
|
119
|
+
/* Goes to a place without touching the history, which is how Back and Forward move. */
|
|
120
|
+
visit(place) {
|
|
121
|
+
if (place.uri === this.uri) {
|
|
122
|
+
this.editor.setCaret(place.position, 'center');
|
|
123
|
+
this.editor.focus();
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
this.project.openPlace(place);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/* Lists places under a position to choose from; choosing one goes there. */
|
|
130
|
+
locations(places, anchor, title) {
|
|
131
|
+
const rows = places.map((place, index) => ({ id: String(index), ...locationRow(place, this.project.pathOfLocation(place.uri)) }));
|
|
132
|
+
this.pick.open({ anchor, title, groups: [{ title: null, rows }], accept: (id) => this.goTo(places[Number(id)]) });
|
|
133
|
+
}
|
|
134
|
+
/* Runs `dispose` when the editor lets go of its document. */
|
|
135
|
+
onDispose(dispose) {
|
|
136
|
+
this.disposers.push(dispose);
|
|
137
|
+
}
|
|
138
|
+
dispose() {
|
|
139
|
+
if (this.disposed) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.disposed = true;
|
|
143
|
+
for (const dispose of this.disposers.splice(0).reverse()) {
|
|
144
|
+
dispose();
|
|
145
|
+
}
|
|
146
|
+
this.document.release();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
.peek-hatch {
|
|
2
|
+
flex-shrink: 0;
|
|
3
|
+
background: repeating-linear-gradient(135deg, transparent 0 6px, var(--editor-hatch) 6px 7px);
|
|
4
|
+
}
|
|
5
|
+
.adecore-editor-markdown {
|
|
6
|
+
overflow-wrap: anywhere;
|
|
7
|
+
white-space: normal;
|
|
8
|
+
}
|
|
9
|
+
.adecore-editor-markdown p {
|
|
10
|
+
margin: 0;
|
|
11
|
+
}
|
|
12
|
+
.adecore-editor-markdown pre {
|
|
13
|
+
overflow: auto;
|
|
14
|
+
font-family: var(--font-mono);
|
|
15
|
+
}
|
|
16
|
+
.adecore-editor-markdown a {
|
|
17
|
+
color: var(--accent);
|
|
18
|
+
text-decoration: underline;
|
|
19
|
+
}
|
|
20
|
+
.adecore-editor-find {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
gap: 8px;
|
|
24
|
+
padding: 8px;
|
|
25
|
+
background: var(--surface-raised);
|
|
26
|
+
color: var(--text);
|
|
27
|
+
}
|
|
28
|
+
.adecore-editor-find-row {
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: 8px;
|
|
32
|
+
}
|
|
33
|
+
.adecore-editor-find-row input[type='text'] {
|
|
34
|
+
min-width: 0;
|
|
35
|
+
flex: 1;
|
|
36
|
+
}
|
|
37
|
+
.adecore-editor-find :focus-visible {
|
|
38
|
+
outline: 2px solid var(--accent);
|
|
39
|
+
outline-offset: 2px;
|
|
40
|
+
}
|
|
41
|
+
.adecore-editor-find output {
|
|
42
|
+
font-variant-numeric: tabular-nums;
|
|
43
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface FindOptions {
|
|
2
|
+
caseSensitive: boolean;
|
|
3
|
+
wholeWord: boolean;
|
|
4
|
+
regex: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface FindQuery extends FindOptions {
|
|
7
|
+
text: string;
|
|
8
|
+
inSelection?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const EMPTY_FIND_QUERY: FindQuery;
|
|
11
|
+
export interface TextMatch {
|
|
12
|
+
start: number;
|
|
13
|
+
end: number;
|
|
14
|
+
}
|
|
15
|
+
export type CompiledFind = {
|
|
16
|
+
kind: 'empty';
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'invalid';
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'pattern';
|
|
21
|
+
pattern: RegExp;
|
|
22
|
+
};
|
|
23
|
+
export declare const MATCH_LIMIT = 10000;
|
|
24
|
+
export declare function compileFind(query: FindQuery): CompiledFind;
|
|
25
|
+
export declare function matchesIn(text: string, pattern: RegExp, limit?: number): TextMatch[];
|
|
26
|
+
export declare function stepIndex(current: number | null, count: number, direction: 1 | -1): number | null;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const EMPTY_FIND_QUERY = { text: '', caseSensitive: false, wholeWord: false, regex: false };
|
|
2
|
+
/* Past this a count stops meaning anything to a reader, and marking every one would stall the page. */
|
|
3
|
+
export const MATCH_LIMIT = 10000;
|
|
4
|
+
// A letter or digit in any script, so a whole word in Dutch ends at the same place as one in English.
|
|
5
|
+
const WORD_CHAR = '[\\p{L}\\p{N}_]';
|
|
6
|
+
function escapeRegExp(text) {
|
|
7
|
+
return text.replace(/[.*+?^${}()|[\]\\/]/g, '\\$&');
|
|
8
|
+
}
|
|
9
|
+
/* `u` in both modes, since a whole word needs Unicode classes; a pattern that only parses without it counts as invalid. */
|
|
10
|
+
export function compileFind(query) {
|
|
11
|
+
if (query.text === '') {
|
|
12
|
+
return { kind: 'empty' };
|
|
13
|
+
}
|
|
14
|
+
const body = query.regex ? query.text : escapeRegExp(query.text);
|
|
15
|
+
const source = query.wholeWord ? `(?<!${WORD_CHAR})(?:${body})(?!${WORD_CHAR})` : body;
|
|
16
|
+
// Multiline, so `^` and `$` in a pattern mean a line, the way an editor's find reads them.
|
|
17
|
+
const flags = `gmu${query.caseSensitive ? '' : 'i'}`;
|
|
18
|
+
try {
|
|
19
|
+
return { kind: 'pattern', pattern: new RegExp(source, flags) };
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return { kind: 'invalid' };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/* Every match in a text, in order. An empty match (`a*` between two letters) is nothing to show, so it is stepped over. */
|
|
26
|
+
export function matchesIn(text, pattern, limit = MATCH_LIMIT) {
|
|
27
|
+
const matches = [];
|
|
28
|
+
const global = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`);
|
|
29
|
+
let found = global.exec(text);
|
|
30
|
+
while (found !== null && matches.length < limit) {
|
|
31
|
+
if (found[0] === '') {
|
|
32
|
+
// A `u` pattern moves a lastIndex inside a surrogate pair back to its start, so one step would never leave it.
|
|
33
|
+
global.lastIndex = found.index + ((text.codePointAt(found.index) ?? 0) > 0xffff ? 2 : 1);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
matches.push({ start: found.index, end: found.index + found[0].length });
|
|
37
|
+
}
|
|
38
|
+
found = global.exec(text);
|
|
39
|
+
}
|
|
40
|
+
return matches;
|
|
41
|
+
}
|
|
42
|
+
/* The step from one match to the next or the one before, round at either end. */
|
|
43
|
+
export function stepIndex(current, count, direction) {
|
|
44
|
+
if (count === 0) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (current === null) {
|
|
48
|
+
return direction === 1 ? 0 : count - 1;
|
|
49
|
+
}
|
|
50
|
+
return (current + direction + count) % count;
|
|
51
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { EditorFoldRange, EditorFoldSymbol } from '@adecore/editor';
|
|
2
|
+
import type { DocumentSymbolResult, FoldingRange } from '@adecore/lsp';
|
|
3
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
4
|
+
import { type Timers } from './timers.ts';
|
|
5
|
+
export declare function foldSymbolsOf(result: DocumentSymbolResult): EditorFoldSymbol[];
|
|
6
|
+
export declare function foldRangesOf(ranges: readonly FoldingRange[] | null): (EditorFoldRange & {
|
|
7
|
+
kind?: string;
|
|
8
|
+
})[];
|
|
9
|
+
export declare class FoldingFeature {
|
|
10
|
+
private symbols;
|
|
11
|
+
private ranges;
|
|
12
|
+
private readonly refresher;
|
|
13
|
+
private readonly language;
|
|
14
|
+
constructor(language: EditorLanguage, onSymbols: (listener: (result: DocumentSymbolResult) => void) => () => void, timers?: Timers);
|
|
15
|
+
private publish;
|
|
16
|
+
}
|