@adecore/editor-react 0.22.1 → 0.23.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/dist/CallHierarchyPanel.d.ts +6 -0
- package/dist/CallHierarchyPanel.js +92 -0
- package/dist/CompletionPopup.js +11 -3
- package/dist/DiffView.d.ts +22 -0
- package/dist/DiffView.js +203 -0
- package/dist/EditorContextMenu.js +1 -0
- package/dist/EditorView.js +18 -1
- package/dist/FindReplace.d.ts +1 -1
- package/dist/FindReplace.js +41 -11
- package/dist/HoverCard.js +2 -2
- package/dist/LanguagePopups.js +4 -1
- package/dist/Markdown.d.ts +2 -1
- package/dist/Markdown.js +8 -4
- package/dist/PanelResizeHandle.d.ts +8 -0
- package/dist/PanelResizeHandle.js +71 -0
- package/dist/PeekPanel.d.ts +3 -0
- package/dist/PeekPanel.js +7 -4
- package/dist/PickPopup.js +15 -2
- package/dist/RenameCard.js +10 -5
- package/dist/RenderedDocs.d.ts +9 -0
- package/dist/RenderedDocs.js +84 -0
- package/dist/SplitDiffView.d.ts +9 -0
- package/dist/SplitDiffView.js +276 -0
- package/dist/SymbolPicker.d.ts +3 -0
- package/dist/SymbolPicker.js +4 -3
- package/dist/call-hierarchy-model.d.ts +52 -0
- package/dist/call-hierarchy-model.js +93 -0
- package/dist/call-hierarchy.d.ts +42 -0
- package/dist/call-hierarchy.js +398 -0
- package/dist/code-actions-model.d.ts +2 -0
- package/dist/code-actions-model.js +12 -10
- package/dist/code-actions.js +3 -1
- package/dist/completion-model.d.ts +14 -4
- package/dist/completion-model.js +40 -33
- package/dist/completion.d.ts +4 -0
- package/dist/completion.js +257 -42
- package/dist/diagnostics.js +8 -6
- package/dist/diff-expansion.d.ts +9 -0
- package/dist/diff-expansion.js +41 -0
- package/dist/diff-overlays.d.ts +15 -0
- package/dist/diff-overlays.js +28 -0
- package/dist/diff-syntax.d.ts +16 -0
- package/dist/diff-syntax.js +49 -0
- package/dist/diff-wordmarks.d.ts +5 -0
- package/dist/diff-wordmarks.js +26 -0
- package/dist/doc-comment-model.d.ts +23 -0
- package/dist/doc-comment-model.js +291 -0
- package/dist/editor-language.d.ts +13 -1
- package/dist/editor-language.js +15 -5
- package/dist/editor-react.css +79 -0
- package/dist/format-on-type.d.ts +14 -0
- package/dist/format-on-type.js +96 -0
- package/dist/host-types.d.ts +19 -0
- package/dist/hover.d.ts +4 -0
- package/dist/hover.js +15 -2
- package/dist/index.d.ts +19 -5
- package/dist/index.js +9 -1
- package/dist/inlay-hints.d.ts +47 -2
- package/dist/inlay-hints.js +353 -16
- package/dist/language-ids.js +4 -2
- package/dist/locales/en.json +117 -3
- package/dist/locales/nl.json +117 -3
- package/dist/models.d.ts +6 -2
- package/dist/models.js +5 -1
- package/dist/panel-size.d.ts +6 -0
- package/dist/panel-size.js +25 -0
- package/dist/patch-decorations.d.ts +27 -0
- package/dist/patch-decorations.js +182 -0
- package/dist/patch-layout.d.ts +40 -0
- package/dist/patch-layout.js +304 -0
- package/dist/peek.d.ts +3 -1
- package/dist/peek.js +6 -1
- package/dist/pick.d.ts +1 -0
- package/dist/pick.js +11 -1
- package/dist/popups.d.ts +17 -0
- package/dist/popups.js +1 -0
- package/dist/project-language.d.ts +12 -0
- package/dist/project-language.js +52 -2
- package/dist/refresher.d.ts +1 -0
- package/dist/refresher.js +5 -0
- package/dist/rename-model.d.ts +1 -1
- package/dist/rename-model.js +3 -7
- package/dist/rename.js +5 -8
- package/dist/rendered-docs.d.ts +64 -0
- package/dist/rendered-docs.js +374 -0
- package/dist/row-host.d.ts +2 -0
- package/dist/row-host.js +11 -3
- package/dist/semantic-delta.d.ts +2 -0
- package/dist/semantic-delta.js +27 -0
- package/dist/semantic-model.js +16 -0
- package/dist/semantic-tokens.d.ts +26 -1
- package/dist/semantic-tokens.js +174 -14
- package/dist/shortcut-keys.d.ts +2 -0
- package/dist/shortcut-keys.js +6 -2
- package/dist/signature.js +1 -1
- package/dist/snippet-field-transforms.d.ts +30 -0
- package/dist/snippet-field-transforms.js +116 -0
- package/dist/snippet-indent.d.ts +2 -0
- package/dist/snippet-indent.js +49 -0
- package/dist/snippet-range-order.d.ts +3 -0
- package/dist/snippet-range-order.js +19 -0
- package/dist/snippet-session.d.ts +42 -6
- package/dist/snippet-session.js +650 -53
- package/dist/snippet-transform-client.d.ts +19 -0
- package/dist/snippet-transform-client.js +66 -0
- package/dist/snippet-transform-format.d.ts +15 -0
- package/dist/snippet-transform-format.js +302 -0
- package/dist/snippet-transform-protocol.d.ts +45 -0
- package/dist/snippet-transform-protocol.js +8 -0
- package/dist/snippet-transform-queue.d.ts +32 -0
- package/dist/snippet-transform-queue.js +173 -0
- package/dist/snippet-transform-worker.d.ts +10 -0
- package/dist/snippet-transform-worker.js +41 -0
- package/dist/snippet-variable-names.d.ts +1 -0
- package/dist/snippet-variable-names.js +36 -0
- package/dist/snippet-variables.d.ts +7 -0
- package/dist/snippet-variables.js +66 -0
- package/dist/snippet.d.ts +30 -3
- package/dist/snippet.js +402 -87
- package/dist/split-alignment.d.ts +6 -0
- package/dist/split-alignment.js +38 -0
- package/dist/split-scroll.d.ts +14 -0
- package/dist/split-scroll.js +45 -0
- package/dist/symbols.d.ts +2 -1
- package/dist/symbols.js +39 -7
- package/dist/testing.d.ts +5 -1
- package/dist/testing.js +12 -0
- package/dist/timers.d.ts +1 -0
- package/dist/timers.js +4 -0
- package/dist/use-panel-body.d.ts +6 -0
- package/dist/use-panel-body.js +13 -0
- package/dist/workspace-edit.d.ts +25 -3
- package/dist/workspace-edit.js +282 -143
- package/package.json +10 -5
- package/src/CallHierarchyPanel.tsx +244 -0
- package/src/CompletionPopup.tsx +12 -1
- package/src/DiffView.tsx +262 -0
- package/src/EditorContextMenu.tsx +1 -0
- package/src/EditorView.tsx +21 -2
- package/src/FindReplace.tsx +51 -13
- package/src/HoverCard.tsx +23 -21
- package/src/LanguagePopups.tsx +5 -0
- package/src/Markdown.tsx +10 -3
- package/src/PanelResizeHandle.tsx +104 -0
- package/src/PeekPanel.tsx +11 -6
- package/src/PickPopup.tsx +18 -2
- package/src/RenameCard.tsx +18 -0
- package/src/RenderedDocs.tsx +155 -0
- package/src/SplitDiffView.tsx +337 -0
- package/src/SymbolPicker.tsx +4 -3
- package/src/call-hierarchy-model.ts +151 -0
- package/src/call-hierarchy.ts +440 -0
- package/src/code-actions-model.ts +17 -11
- package/src/code-actions.ts +3 -1
- package/src/completion-model.ts +61 -35
- package/src/completion.ts +302 -42
- package/src/diagnostics.ts +8 -6
- package/src/diff-expansion.ts +49 -0
- package/src/diff-overlays.tsx +69 -0
- package/src/diff-syntax.ts +67 -0
- package/src/diff-wordmarks.ts +31 -0
- package/src/doc-comment-model.ts +336 -0
- package/src/editor-language.ts +22 -6
- package/src/editor-react.css +79 -0
- package/src/format-on-type.ts +111 -0
- package/src/host-types.ts +23 -0
- package/src/hover.ts +18 -3
- package/src/index.ts +27 -4
- package/src/inlay-hints.ts +394 -18
- package/src/language-ids.ts +4 -2
- package/src/locales/en.json +117 -3
- package/src/locales/nl.json +117 -3
- package/src/models.ts +40 -1
- package/src/panel-size.ts +28 -0
- package/src/patch-decorations.ts +224 -0
- package/src/patch-layout.ts +388 -0
- package/src/peek.ts +6 -1
- package/src/pick.ts +12 -1
- package/src/popups.ts +24 -0
- package/src/project-language.ts +63 -2
- package/src/refresher.ts +6 -0
- package/src/rename-model.ts +4 -8
- package/src/rename.ts +5 -8
- package/src/rendered-docs.ts +430 -0
- package/src/row-host.ts +12 -3
- package/src/semantic-delta.ts +29 -0
- package/src/semantic-model.ts +17 -0
- package/src/semantic-tokens.ts +202 -19
- package/src/shortcut-keys.ts +6 -2
- package/src/signature.ts +1 -1
- package/src/snippet-field-transforms.ts +144 -0
- package/src/snippet-indent.ts +53 -0
- package/src/snippet-range-order.ts +19 -0
- package/src/snippet-session.ts +797 -56
- package/src/snippet-transform-client.ts +84 -0
- package/src/snippet-transform-format.ts +325 -0
- package/src/snippet-transform-protocol.ts +24 -0
- package/src/snippet-transform-queue.ts +199 -0
- package/src/snippet-transform-worker.ts +58 -0
- package/src/snippet-variable-names.ts +36 -0
- package/src/snippet-variables.ts +74 -0
- package/src/snippet.ts +452 -89
- package/src/split-alignment.ts +45 -0
- package/src/split-scroll.ts +54 -0
- package/src/symbols.ts +43 -7
- package/src/testing.ts +25 -0
- package/src/timers.ts +5 -0
- package/src/use-panel-body.ts +17 -0
- package/src/workspace-edit.ts +347 -169
package/dist/rename.js
CHANGED
|
@@ -2,6 +2,7 @@ import { StaleResultError } from '@adecore/lsp';
|
|
|
2
2
|
import { lineTextOf } from './line-text.js';
|
|
3
3
|
import { occurrencesOf, renamePreviewOf, renameTargetOf, wordRangeAt } from './rename-model.js';
|
|
4
4
|
import { isShortcut } from './shortcut-keys.js';
|
|
5
|
+
import { resourceOperationsOf } from './workspace-edit.js';
|
|
5
6
|
import { messageOf } from './error-message.js';
|
|
6
7
|
const METHOD = 'textDocument/rename';
|
|
7
8
|
const TOAST_ID = 'language-rename';
|
|
@@ -66,7 +67,7 @@ export class RenameFeature {
|
|
|
66
67
|
}
|
|
67
68
|
const session = { target, original: editor.textInRange(target.range), edit: null, names: new AbortController() };
|
|
68
69
|
this.session = session;
|
|
69
|
-
this.publish({ phase: 'input', busy: false, name: target.placeholder, files: [], occurrences: null, suggestions: [] });
|
|
70
|
+
this.publish({ phase: 'input', busy: false, name: target.placeholder, files: [], operations: [], occurrences: null, suggestions: [] });
|
|
70
71
|
void this.light(session).then((locations) => this.suggest(session, locations));
|
|
71
72
|
}
|
|
72
73
|
/* The name was typed: Enter applies it, Shift+Enter lists what it would change first. */
|
|
@@ -100,12 +101,7 @@ export class RenameFeature {
|
|
|
100
101
|
if (this.session !== session) {
|
|
101
102
|
return;
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
-
// An edit that renames files too has no list of lines to show, so it is applied or refused as a whole.
|
|
105
|
-
await this.apply(session);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
this.publish({ phase: 'preview', busy: false, name: next, files });
|
|
104
|
+
this.publish({ phase: 'preview', busy: false, name: next, files, operations: resourceOperationsOf(edit) });
|
|
109
105
|
}
|
|
110
106
|
catch (error) {
|
|
111
107
|
if (this.session === session) {
|
|
@@ -148,7 +144,7 @@ export class RenameFeature {
|
|
|
148
144
|
fail(message) {
|
|
149
145
|
if (this.session !== null) {
|
|
150
146
|
this.session.edit = null;
|
|
151
|
-
this.publish({ phase: 'input', busy: false, files: [], error: this.say('failed', { message }) });
|
|
147
|
+
this.publish({ phase: 'input', busy: false, files: [], operations: [], error: this.say('failed', { message }) });
|
|
152
148
|
}
|
|
153
149
|
}
|
|
154
150
|
finish() {
|
|
@@ -244,6 +240,7 @@ export class RenameFeature {
|
|
|
244
240
|
error: null,
|
|
245
241
|
name: session.target.placeholder,
|
|
246
242
|
files: [],
|
|
243
|
+
operations: [],
|
|
247
244
|
suggestions: [],
|
|
248
245
|
...current,
|
|
249
246
|
...patch
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type DocComment } from '@adecore/editor';
|
|
2
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
3
|
+
import { type ParsedDocComment } from './doc-comment-model.ts';
|
|
4
|
+
import { type Timers } from './timers.ts';
|
|
5
|
+
export interface RenderedDocsSettings {
|
|
6
|
+
readonly renderDocComments: boolean;
|
|
7
|
+
readonly enabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface RenderedDocEntry {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly comment: DocComment;
|
|
12
|
+
readonly parsed: ParsedDocComment;
|
|
13
|
+
readonly container: HTMLElement;
|
|
14
|
+
}
|
|
15
|
+
export declare class RenderedDocsFeature {
|
|
16
|
+
private readonly language;
|
|
17
|
+
private readonly timers;
|
|
18
|
+
private settings;
|
|
19
|
+
private enabled;
|
|
20
|
+
private comments;
|
|
21
|
+
private overrides;
|
|
22
|
+
private readonly containers;
|
|
23
|
+
private readonly markerLines;
|
|
24
|
+
private readonly parsed;
|
|
25
|
+
private indexes;
|
|
26
|
+
private readonly revealed;
|
|
27
|
+
private readonly listeners;
|
|
28
|
+
private version;
|
|
29
|
+
private settleTimer;
|
|
30
|
+
private caretTimer;
|
|
31
|
+
private drawn;
|
|
32
|
+
private marked;
|
|
33
|
+
private stale;
|
|
34
|
+
private disposed;
|
|
35
|
+
constructor(language: EditorLanguage, timers?: Timers);
|
|
36
|
+
configure(settings: RenderedDocsSettings): void;
|
|
37
|
+
get all(): readonly DocComment[];
|
|
38
|
+
isRendered(comment: DocComment): boolean;
|
|
39
|
+
toggle(): boolean;
|
|
40
|
+
toggleAt(startLine: number): boolean;
|
|
41
|
+
entries(): readonly RenderedDocEntry[];
|
|
42
|
+
getVersion: () => number;
|
|
43
|
+
subscribe: (listener: () => void) => (() => void);
|
|
44
|
+
get canFollow(): boolean;
|
|
45
|
+
follow(name: string, comment: DocComment): void;
|
|
46
|
+
private idOf;
|
|
47
|
+
private canRender;
|
|
48
|
+
private wantsRendered;
|
|
49
|
+
private at;
|
|
50
|
+
private touched;
|
|
51
|
+
private setRendered;
|
|
52
|
+
private leave;
|
|
53
|
+
private reveal;
|
|
54
|
+
private releaseRevealed;
|
|
55
|
+
private clearOverrides;
|
|
56
|
+
private settle;
|
|
57
|
+
private read;
|
|
58
|
+
private parse;
|
|
59
|
+
private publish;
|
|
60
|
+
private labelOf;
|
|
61
|
+
private mounted;
|
|
62
|
+
private sweep;
|
|
63
|
+
private bump;
|
|
64
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { findDocComments } from '@adecore/editor';
|
|
2
|
+
import { parseDocComment } from './doc-comment-model.js';
|
|
3
|
+
import { isShortcut } from './shortcut-keys.js';
|
|
4
|
+
import { realTimers } from './timers.js';
|
|
5
|
+
const OWNER = 'rendered-docs';
|
|
6
|
+
const DOCS_PREFIX = 'docs-';
|
|
7
|
+
const SUMMARY_WORDS = 6;
|
|
8
|
+
const SETTLE_MS = 100;
|
|
9
|
+
/* What a row is taken to be tall until its content is in and measured. */
|
|
10
|
+
const ESTIMATED_LINE_HEIGHT = 20;
|
|
11
|
+
const MAX_ESTIMATED_LINES = 8;
|
|
12
|
+
const MAX_PARSED = 200;
|
|
13
|
+
function startOf(comment) {
|
|
14
|
+
return { line: comment.startLine, character: comment.indent.length };
|
|
15
|
+
}
|
|
16
|
+
/* A selection touches a comment unless it merely ends where the comment begins or swallows the comment whole. */
|
|
17
|
+
function touches(range, comment) {
|
|
18
|
+
if (range.end.line < comment.startLine || range.start.line > comment.endLine) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
const empty = range.start.line === range.end.line && range.start.character === range.end.character;
|
|
22
|
+
if (!empty && range.end.line === comment.startLine && range.end.character === 0) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return empty || range.start.line >= comment.startLine || range.end.line <= comment.endLine;
|
|
26
|
+
}
|
|
27
|
+
/* The first words of what a comment says, to tell its row from the others for a person who hears it. */
|
|
28
|
+
function summaryOf(body) {
|
|
29
|
+
const lead = body.split('\n').findIndex((line) => /^\s*@/.test(line));
|
|
30
|
+
const text = (lead === -1 ? body : body.split('\n').slice(0, lead).join('\n')).replace(/[`*_#>[\]()]/g, ' ');
|
|
31
|
+
const words = text.split(/\s+/).filter((word) => word !== '');
|
|
32
|
+
return words.slice(0, SUMMARY_WORDS).join(' ');
|
|
33
|
+
}
|
|
34
|
+
function samePlace(range, position) {
|
|
35
|
+
return range !== null && range.start.line === position.line && range.start.character === position.character;
|
|
36
|
+
}
|
|
37
|
+
/*
|
|
38
|
+
* Documentation comments drawn as formatted text in place of their lines, the way the Reader mode of the
|
|
39
|
+
* documentation does, and the button in the gutter that switches one between that and its source. The
|
|
40
|
+
* text of the document never changes. A comment the caret or a selection touches is shown as source so
|
|
41
|
+
* it can be edited, and shown formatted again when the caret leaves it.
|
|
42
|
+
*/
|
|
43
|
+
export class RenderedDocsFeature {
|
|
44
|
+
language;
|
|
45
|
+
timers;
|
|
46
|
+
settings = { renderDocComments: false };
|
|
47
|
+
enabled = true;
|
|
48
|
+
comments = [];
|
|
49
|
+
overrides = [];
|
|
50
|
+
containers = new Map();
|
|
51
|
+
markerLines = new Map();
|
|
52
|
+
parsed = new Map();
|
|
53
|
+
indexes = new Map();
|
|
54
|
+
/* Comments a reveal showed as source until the caret has settled where it was going, or the comment has scrolled out of view if the caret never moves. */
|
|
55
|
+
revealed = new Set();
|
|
56
|
+
listeners = new Set();
|
|
57
|
+
version = 0;
|
|
58
|
+
settleTimer;
|
|
59
|
+
caretTimer;
|
|
60
|
+
drawn = '';
|
|
61
|
+
marked = '';
|
|
62
|
+
/* The comments are from before the last edit, so nothing is decided from them until they are read again. */
|
|
63
|
+
stale = false;
|
|
64
|
+
disposed = false;
|
|
65
|
+
constructor(language, timers = realTimers) {
|
|
66
|
+
this.language = language;
|
|
67
|
+
this.timers = timers;
|
|
68
|
+
const { editor } = language;
|
|
69
|
+
const offs = [
|
|
70
|
+
editor.onTextChange(() => {
|
|
71
|
+
this.stale = true;
|
|
72
|
+
this.timers.clear(this.settleTimer);
|
|
73
|
+
this.settleTimer = this.timers.set(() => this.read(), SETTLE_MS);
|
|
74
|
+
}),
|
|
75
|
+
// The caret is told before the text change that moved it, so what the lines are is decided a moment later.
|
|
76
|
+
editor.onCaret(() => {
|
|
77
|
+
this.timers.clear(this.caretTimer);
|
|
78
|
+
this.caretTimer = this.timers.set(() => {
|
|
79
|
+
this.revealed.clear();
|
|
80
|
+
this.publish();
|
|
81
|
+
}, 0);
|
|
82
|
+
}),
|
|
83
|
+
editor.onGutterMarker((id) => {
|
|
84
|
+
const line = this.markerLines.get(id);
|
|
85
|
+
if (line !== undefined) {
|
|
86
|
+
this.toggleAt(line);
|
|
87
|
+
}
|
|
88
|
+
}),
|
|
89
|
+
editor.onKeyDown((event) => {
|
|
90
|
+
return isShortcut(this.language.shortcuts.toggleRenderedDocs, event) && this.toggle();
|
|
91
|
+
}),
|
|
92
|
+
editor.onViewChange(() => {
|
|
93
|
+
this.sweep();
|
|
94
|
+
this.releaseRevealed();
|
|
95
|
+
})
|
|
96
|
+
];
|
|
97
|
+
language.onDispose(() => {
|
|
98
|
+
this.disposed = true;
|
|
99
|
+
for (const off of offs) {
|
|
100
|
+
off();
|
|
101
|
+
}
|
|
102
|
+
this.timers.clear(this.settleTimer);
|
|
103
|
+
this.timers.clear(this.caretTimer);
|
|
104
|
+
for (const override of this.overrides) {
|
|
105
|
+
override.tracked.dispose();
|
|
106
|
+
}
|
|
107
|
+
this.overrides = [];
|
|
108
|
+
editor.setReplacements?.([], OWNER);
|
|
109
|
+
editor.setGutterMarkers([], OWNER);
|
|
110
|
+
this.containers.clear();
|
|
111
|
+
this.bump();
|
|
112
|
+
});
|
|
113
|
+
this.read();
|
|
114
|
+
}
|
|
115
|
+
/* Whether every documentation comment is drawn as text until a person switches it. Changing it puts every comment back under it. */
|
|
116
|
+
configure(settings) {
|
|
117
|
+
const enabled = settings.enabled !== false;
|
|
118
|
+
if (settings.renderDocComments === this.settings.renderDocComments && enabled === this.enabled) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const sameRendering = settings.renderDocComments === this.settings.renderDocComments;
|
|
122
|
+
this.settings = settings;
|
|
123
|
+
this.enabled = enabled;
|
|
124
|
+
if (!sameRendering) {
|
|
125
|
+
this.clearOverrides();
|
|
126
|
+
}
|
|
127
|
+
this.publish();
|
|
128
|
+
}
|
|
129
|
+
/* The documentation comments of the document, from the last time it settled. */
|
|
130
|
+
get all() {
|
|
131
|
+
return this.comments;
|
|
132
|
+
}
|
|
133
|
+
/* Whether a comment is drawn as text now: its own switch, else the setting. A comment the caret is in is not, whatever its switch says. */
|
|
134
|
+
isRendered(comment) {
|
|
135
|
+
return this.wantsRendered(comment) && !this.touched(comment) && !this.revealed.has(comment);
|
|
136
|
+
}
|
|
137
|
+
/* Switches the comment the caret is in between formatted and source. False when the caret is in none. */
|
|
138
|
+
toggle() {
|
|
139
|
+
this.settle();
|
|
140
|
+
const comment = this.at(this.language.editor.getCaret().line);
|
|
141
|
+
if (!this.enabled || comment === undefined || !this.canRender(comment)) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
this.setRendered(comment, !this.wantsRendered(comment));
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
/* Switches the comment that starts on a line, which is what its gutter button does. */
|
|
148
|
+
toggleAt(startLine) {
|
|
149
|
+
this.settle();
|
|
150
|
+
const comment = this.comments.find((candidate) => candidate.startLine === startLine);
|
|
151
|
+
if (!this.enabled || comment === undefined || !this.canRender(comment)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
this.setRendered(comment, !this.wantsRendered(comment));
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
/* The elements of the rows now in view, for a host that draws them itself; `RenderedDocs` does. */
|
|
158
|
+
entries() {
|
|
159
|
+
const result = [];
|
|
160
|
+
for (const [id, container] of this.containers) {
|
|
161
|
+
const comment = this.comments[Number(id.slice(DOCS_PREFIX.length))];
|
|
162
|
+
if (comment !== undefined) {
|
|
163
|
+
result.push({ id, comment, parsed: this.parse(comment), container });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
getVersion = () => this.version;
|
|
169
|
+
subscribe = (listener) => {
|
|
170
|
+
this.listeners.add(listener);
|
|
171
|
+
return () => {
|
|
172
|
+
this.listeners.delete(listener);
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
/* Whether names in `{@link}` and `@see` can be followed, which takes a server that finds symbols by name. */
|
|
176
|
+
get canFollow() {
|
|
177
|
+
const { project, uri } = this.language;
|
|
178
|
+
return project.service.supports('workspace/symbol', uri);
|
|
179
|
+
}
|
|
180
|
+
/* Goes to what a link in a comment names. */
|
|
181
|
+
follow(name, comment) {
|
|
182
|
+
const member = name.split(/::|[#.\\]/).filter((part) => part !== '');
|
|
183
|
+
const target = (member.at(-1) ?? name).replace(/\(\)$/, '');
|
|
184
|
+
void this.language.navigation.goToName(target, startOf(comment), null);
|
|
185
|
+
}
|
|
186
|
+
/* By place in the document, so a row keeps its element when an edit above it moves its lines. */
|
|
187
|
+
idOf(comment) {
|
|
188
|
+
return `${DOCS_PREFIX}${this.indexes.get(comment) ?? -1}`;
|
|
189
|
+
}
|
|
190
|
+
/* A comment that is all the document has has no line to leave the caret on, so it stays source. */
|
|
191
|
+
canRender(comment) {
|
|
192
|
+
return comment.startLine > 0 || comment.endLine < this.language.editor.positionAt(Number.MAX_SAFE_INTEGER).line;
|
|
193
|
+
}
|
|
194
|
+
wantsRendered(comment) {
|
|
195
|
+
if (!this.enabled || !this.canRender(comment)) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
const start = startOf(comment);
|
|
199
|
+
const override = this.overrides.find((candidate) => samePlace(candidate.tracked.get(), start));
|
|
200
|
+
return override?.rendered ?? this.settings.renderDocComments;
|
|
201
|
+
}
|
|
202
|
+
at(line) {
|
|
203
|
+
return this.comments.find((comment) => line >= comment.startLine && line <= comment.endLine);
|
|
204
|
+
}
|
|
205
|
+
touched(comment) {
|
|
206
|
+
return this.language.editor.getSelections().some((range) => touches(range, comment));
|
|
207
|
+
}
|
|
208
|
+
setRendered(comment, rendered) {
|
|
209
|
+
const start = startOf(comment);
|
|
210
|
+
const existing = this.overrides.find((candidate) => samePlace(candidate.tracked.get(), start));
|
|
211
|
+
if (existing !== undefined) {
|
|
212
|
+
existing.tracked.dispose();
|
|
213
|
+
this.overrides = this.overrides.filter((candidate) => candidate !== existing);
|
|
214
|
+
}
|
|
215
|
+
if (rendered !== this.settings.renderDocComments) {
|
|
216
|
+
const { editor } = this.language;
|
|
217
|
+
this.overrides.push({ tracked: editor.trackRange({ start, end: editor.positionAt(comment.to) }), rendered });
|
|
218
|
+
}
|
|
219
|
+
if (rendered) {
|
|
220
|
+
this.leave(comment);
|
|
221
|
+
}
|
|
222
|
+
this.publish();
|
|
223
|
+
}
|
|
224
|
+
/* Takes the carets and selections that touch a comment about to be drawn as text to the line under it, else the one above it. The others stay. */
|
|
225
|
+
leave(comment) {
|
|
226
|
+
const { editor } = this.language;
|
|
227
|
+
const selections = editor.getSelections();
|
|
228
|
+
if (!selections.some((range) => touches(range, comment))) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const count = editor.positionAt(Number.MAX_SAFE_INTEGER).line + 1;
|
|
232
|
+
const line = comment.endLine + 1 < count ? comment.endLine + 1 : comment.startLine - 1;
|
|
233
|
+
if (line < 0) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const text = editor.textInRange({ start: { line, character: 0 }, end: { line, character: Number.MAX_SAFE_INTEGER } });
|
|
237
|
+
const character = line > comment.endLine ? (/^[ \t]*/.exec(text)?.[0].length ?? 0) : text.length;
|
|
238
|
+
const place = { line, character };
|
|
239
|
+
editor.setSelections(selections.map((range) => (touches(range, comment) ? { start: place, end: place } : range)));
|
|
240
|
+
}
|
|
241
|
+
/* Shows a comment as source for the reveal that is about to scroll into it, since its row would only be in the way. */
|
|
242
|
+
reveal(comment) {
|
|
243
|
+
this.revealed.add(comment);
|
|
244
|
+
this.publish();
|
|
245
|
+
}
|
|
246
|
+
releaseRevealed() {
|
|
247
|
+
if (this.revealed.size === 0) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const { start, end } = this.language.editor.getVisibleRange();
|
|
251
|
+
const away = [...this.revealed].filter((comment) => comment.endLine < start.line || comment.startLine > end.line);
|
|
252
|
+
if (away.length > 0) {
|
|
253
|
+
away.forEach((comment) => this.revealed.delete(comment));
|
|
254
|
+
this.publish();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
clearOverrides() {
|
|
258
|
+
for (const override of this.overrides) {
|
|
259
|
+
override.tracked.dispose();
|
|
260
|
+
}
|
|
261
|
+
this.overrides = [];
|
|
262
|
+
}
|
|
263
|
+
/* Reads the comments now when an edit is waiting to be read. */
|
|
264
|
+
settle() {
|
|
265
|
+
if (this.stale) {
|
|
266
|
+
this.timers.clear(this.settleTimer);
|
|
267
|
+
this.read();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
read() {
|
|
271
|
+
if (this.disposed) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
this.stale = false;
|
|
275
|
+
const { editor, languageId } = this.language;
|
|
276
|
+
this.comments = findDocComments(editor.getText(), languageId);
|
|
277
|
+
this.indexes = new Map(this.comments.map((comment, index) => [comment, index]));
|
|
278
|
+
this.revealed.clear();
|
|
279
|
+
const starts = this.comments.map(startOf);
|
|
280
|
+
this.overrides = this.overrides.filter((override) => {
|
|
281
|
+
const kept = starts.some((start) => samePlace(override.tracked.get(), start));
|
|
282
|
+
if (!kept) {
|
|
283
|
+
override.tracked.dispose();
|
|
284
|
+
}
|
|
285
|
+
return kept;
|
|
286
|
+
});
|
|
287
|
+
// An edit that replaced everything leaves the rows on offsets at the end, which only a new set of rows repairs.
|
|
288
|
+
this.publish(true);
|
|
289
|
+
this.bump();
|
|
290
|
+
}
|
|
291
|
+
parse(comment) {
|
|
292
|
+
const key = `${comment.body}`;
|
|
293
|
+
let result = this.parsed.get(key);
|
|
294
|
+
if (result === undefined) {
|
|
295
|
+
if (this.parsed.size >= MAX_PARSED) {
|
|
296
|
+
this.parsed.delete(this.parsed.keys().next().value);
|
|
297
|
+
}
|
|
298
|
+
result = parseDocComment(comment.body, { php: /^php$/i.test(this.language.languageId) });
|
|
299
|
+
this.parsed.set(key, result);
|
|
300
|
+
}
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
/* Hands the editor the rows and the buttons, unless they are what it has already or `force` says to. */
|
|
304
|
+
publish(force = false) {
|
|
305
|
+
if (this.disposed || this.stale) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const { editor } = this.language;
|
|
309
|
+
const t = (key, options) => this.language.project.i18n.t(`editor:language.docs.${key}`, options);
|
|
310
|
+
const rendered = this.comments.filter((comment) => this.isRendered(comment));
|
|
311
|
+
const signature = rendered.map((comment) => `${this.idOf(comment)}@${comment.startLine}-${comment.endLine}`).join('|');
|
|
312
|
+
if (force || signature !== this.drawn) {
|
|
313
|
+
this.drawn = signature;
|
|
314
|
+
const lineHeight = Math.max(editor.getBaseLineHeight?.() ?? 0, ESTIMATED_LINE_HEIGHT);
|
|
315
|
+
const rows = rendered.map((comment) => ({
|
|
316
|
+
id: this.idOf(comment),
|
|
317
|
+
startLine: comment.startLine,
|
|
318
|
+
endLine: comment.endLine,
|
|
319
|
+
height: Math.min(comment.endLine - comment.startLine + 1, MAX_ESTIMATED_LINES) * lineHeight,
|
|
320
|
+
label: this.labelOf(comment, t),
|
|
321
|
+
onReveal: () => this.reveal(comment),
|
|
322
|
+
render: (container) => this.mounted(this.idOf(comment), container)
|
|
323
|
+
}));
|
|
324
|
+
const kept = new Set(rows.map((row) => row.id));
|
|
325
|
+
for (const id of [...this.containers.keys()]) {
|
|
326
|
+
if (!kept.has(id)) {
|
|
327
|
+
this.containers.delete(id);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
editor.setReplacements?.(rows, OWNER);
|
|
331
|
+
this.bump();
|
|
332
|
+
}
|
|
333
|
+
this.markerLines.clear();
|
|
334
|
+
const markers = (this.enabled ? this.comments : []).map((comment) => {
|
|
335
|
+
const id = `docs:${comment.startLine}`;
|
|
336
|
+
this.markerLines.set(id, comment.startLine);
|
|
337
|
+
const on = this.wantsRendered(comment);
|
|
338
|
+
return { id, line: comment.startLine, label: t(on ? 'showSource' : 'render'), icon: 'book-open', pressed: on };
|
|
339
|
+
});
|
|
340
|
+
const marking = markers.map((marker) => `${marker.line}${marker.pressed}`).join('|');
|
|
341
|
+
if (marking !== this.marked) {
|
|
342
|
+
this.marked = marking;
|
|
343
|
+
editor.setGutterMarkers(markers, OWNER);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
labelOf(comment, t) {
|
|
347
|
+
const summary = summaryOf(comment.body);
|
|
348
|
+
return summary === '' ? t('label') : t('labelNamed', { summary });
|
|
349
|
+
}
|
|
350
|
+
mounted(id, container) {
|
|
351
|
+
this.containers.set(id, container);
|
|
352
|
+
this.sweep(container);
|
|
353
|
+
this.bump();
|
|
354
|
+
}
|
|
355
|
+
/* The editor drops the element of a row that scrolled out of view, and what React drew into it goes with it. The element just made is not in the page yet. */
|
|
356
|
+
sweep(fresh) {
|
|
357
|
+
let removed = false;
|
|
358
|
+
for (const [id, container] of this.containers) {
|
|
359
|
+
if (container !== fresh && !container.isConnected) {
|
|
360
|
+
this.containers.delete(id);
|
|
361
|
+
removed = true;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (removed && fresh === undefined) {
|
|
365
|
+
this.bump();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
bump() {
|
|
369
|
+
this.version++;
|
|
370
|
+
for (const listener of [...this.listeners]) {
|
|
371
|
+
listener();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
package/dist/row-host.d.ts
CHANGED
package/dist/row-host.js
CHANGED
|
@@ -52,16 +52,24 @@ class ContainerHost {
|
|
|
52
52
|
*/
|
|
53
53
|
export class RowHost extends ContainerHost {
|
|
54
54
|
set(rows) {
|
|
55
|
-
const signature = rows.map((row) => `${row.id}@${row.line}${row.placement}`).join('|');
|
|
55
|
+
const signature = rows.map((row) => `${row.id}@${row.line}${row.placement}:${row.height}:${row.fixed}:${row.filler}`).join('|');
|
|
56
56
|
if (!this.replace(signature, rows.map((row) => row.id))) {
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
59
|
this.editor.setWidgets(rows.map((row) => ({
|
|
60
|
-
id: row.id,
|
|
60
|
+
id: row.filler ? `${row.id}-filler` : row.id,
|
|
61
61
|
line: row.line,
|
|
62
62
|
placement: row.placement,
|
|
63
63
|
height: row.height,
|
|
64
|
-
|
|
64
|
+
fixed: row.fixed,
|
|
65
|
+
render: (container) => {
|
|
66
|
+
if (row.filler) {
|
|
67
|
+
container.classList.add('adecore-editor-diff-filler');
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
this.mounted(row.id, container);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
65
73
|
})), this.owner);
|
|
66
74
|
this.bump();
|
|
67
75
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The data of a `semanticTokens/full/delta` answer applied to the data it was computed against, or null when
|
|
3
|
+
* the edits cannot be trusted: out of order, overlapping, outside the data, or leaving a length that is no
|
|
4
|
+
* whole number of tokens. The caller then asks for the full tokens instead. One pass over data and edits.
|
|
5
|
+
*/
|
|
6
|
+
export function applySemanticEdits(data, edits) {
|
|
7
|
+
const result = [];
|
|
8
|
+
let cursor = 0;
|
|
9
|
+
for (const edit of edits) {
|
|
10
|
+
const { start, deleteCount } = edit;
|
|
11
|
+
const inserted = edit.data ?? [];
|
|
12
|
+
if (!Number.isInteger(start) || !Number.isInteger(deleteCount) || start < cursor || deleteCount < 0 || start + deleteCount > data.length) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
for (let at = cursor; at < start; at++) {
|
|
16
|
+
result.push(data[at]);
|
|
17
|
+
}
|
|
18
|
+
for (const value of inserted) {
|
|
19
|
+
result.push(value);
|
|
20
|
+
}
|
|
21
|
+
cursor = start + deleteCount;
|
|
22
|
+
}
|
|
23
|
+
for (let at = cursor; at < data.length; at++) {
|
|
24
|
+
result.push(data[at]);
|
|
25
|
+
}
|
|
26
|
+
return result.length % 5 === 0 ? result : null;
|
|
27
|
+
}
|
package/dist/semantic-model.js
CHANGED
|
@@ -48,6 +48,17 @@ export function scopesOf(type, modifiers, scoped = false) {
|
|
|
48
48
|
return null;
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
/*
|
|
52
|
+
* What a server sends for the language it reads inside a string (SQL in a PHP string). Outside a string the grammar
|
|
53
|
+
* colors these better, so they only apply where the grammar says it is a string.
|
|
54
|
+
*/
|
|
55
|
+
const EMBEDDED_SCOPES = {
|
|
56
|
+
keyword: ['keyword'],
|
|
57
|
+
string: ['string'],
|
|
58
|
+
number: ['constant.numeric'],
|
|
59
|
+
comment: ['comment'],
|
|
60
|
+
operator: ['keyword.operator']
|
|
61
|
+
};
|
|
51
62
|
/* The encoded data of `textDocument/semanticTokens` as one token per classified range, the ones the theme has nothing to say about left out. */
|
|
52
63
|
export function decodeSemanticTokens(tokens, legend) {
|
|
53
64
|
const result = [];
|
|
@@ -68,6 +79,11 @@ export function decodeSemanticTokens(tokens, legend) {
|
|
|
68
79
|
const scopes = scopesOf(type, modifiers, scoped);
|
|
69
80
|
if (scopes !== null) {
|
|
70
81
|
result.push({ line, character, length: data[at + 2], scopes });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const embedded = EMBEDDED_SCOPES[type];
|
|
85
|
+
if (embedded !== undefined) {
|
|
86
|
+
result.push({ line, character, length: data[at + 2], scopes: embedded, within: 'string' });
|
|
71
87
|
}
|
|
72
88
|
}
|
|
73
89
|
return result;
|
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import type { EditorLanguage } from './editor-language.ts';
|
|
2
2
|
import { type Timers } from './timers.ts';
|
|
3
|
+
export declare const DEFAULT_RANGE_LINES = 5000;
|
|
4
|
+
export declare const DEFAULT_RANGE_CHARACTERS = 500000;
|
|
5
|
+
export interface SemanticTokensOptions {
|
|
6
|
+
rangeLines?: number;
|
|
7
|
+
rangeCharacters?: number;
|
|
8
|
+
}
|
|
3
9
|
export declare class SemanticTokensFeature {
|
|
4
10
|
private readonly refresher;
|
|
5
|
-
|
|
11
|
+
private readonly scrolled;
|
|
12
|
+
private readonly language;
|
|
13
|
+
private readonly options;
|
|
14
|
+
private held;
|
|
15
|
+
private heldLegend;
|
|
16
|
+
private complete;
|
|
17
|
+
private covered;
|
|
18
|
+
private drawn;
|
|
19
|
+
private epoch;
|
|
20
|
+
constructor(language: EditorLanguage, timers?: Timers, options?: SemanticTokensOptions);
|
|
21
|
+
private forget;
|
|
22
|
+
private legend;
|
|
23
|
+
private isLarge;
|
|
24
|
+
private viewport;
|
|
25
|
+
private refresh;
|
|
26
|
+
private askFull;
|
|
27
|
+
private fromDelta;
|
|
28
|
+
private hold;
|
|
29
|
+
private askRange;
|
|
30
|
+
private fillViewport;
|
|
6
31
|
}
|