@adecore/editor-react 0.22.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CallHierarchyPanel.d.ts +6 -0
- package/dist/CallHierarchyPanel.js +88 -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/PeekPanel.d.ts +3 -0
- package/dist/PeekPanel.js +2 -3
- 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 +99 -0
- package/dist/call-hierarchy.d.ts +42 -0
- package/dist/call-hierarchy.js +399 -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 +14 -4
- 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 +17 -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 +114 -3
- package/dist/locales/nl.json +114 -3
- package/dist/models.d.ts +6 -2
- package/dist/models.js +5 -1
- 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.js +1 -0
- 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 +8 -0
- package/dist/project-language.js +34 -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 +62 -0
- package/dist/rendered-docs.js +368 -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/workspace-edit.d.ts +25 -3
- package/dist/workspace-edit.js +282 -143
- package/package.json +10 -5
- package/src/CallHierarchyPanel.tsx +238 -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/PeekPanel.tsx +2 -3
- 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 +158 -0
- package/src/call-hierarchy.ts +441 -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 +21 -5
- package/src/editor-react.css +79 -0
- package/src/format-on-type.ts +111 -0
- package/src/host-types.ts +19 -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 +114 -3
- package/src/locales/nl.json +114 -3
- package/src/models.ts +40 -1
- package/src/patch-decorations.ts +224 -0
- package/src/patch-layout.ts +388 -0
- package/src/peek.ts +1 -0
- package/src/pick.ts +12 -1
- package/src/popups.ts +24 -0
- package/src/project-language.ts +43 -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 +422 -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/workspace-edit.ts +347 -169
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EditorPosition } from '@adecore/editor';
|
|
2
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
3
|
+
import { type CallDirection } from './call-hierarchy-model.ts';
|
|
4
|
+
import { type Timers } from './timers.ts';
|
|
5
|
+
export declare const CALL_HIERARCHY_HEIGHT: number;
|
|
6
|
+
export declare class CallHierarchyFeature {
|
|
7
|
+
private readonly language;
|
|
8
|
+
private readonly timers;
|
|
9
|
+
private nodes;
|
|
10
|
+
private roots;
|
|
11
|
+
private prepared;
|
|
12
|
+
private direction;
|
|
13
|
+
private controller;
|
|
14
|
+
private epoch;
|
|
15
|
+
private previewEpoch;
|
|
16
|
+
private texts;
|
|
17
|
+
constructor(language: EditorLanguage, timers?: Timers);
|
|
18
|
+
get isOpen(): boolean;
|
|
19
|
+
get supported(): boolean;
|
|
20
|
+
open(position?: EditorPosition, direction?: CallDirection): Promise<void>;
|
|
21
|
+
close(): void;
|
|
22
|
+
setDirection(direction: CallDirection): void;
|
|
23
|
+
toggle(id: string): void;
|
|
24
|
+
retry(id: string): void;
|
|
25
|
+
showMore(parent: string): void;
|
|
26
|
+
select(id: string, site?: number): void;
|
|
27
|
+
stepSite(direction: 1 | -1): void;
|
|
28
|
+
go(id: string): void;
|
|
29
|
+
private reset;
|
|
30
|
+
private seed;
|
|
31
|
+
private mounted;
|
|
32
|
+
private patch;
|
|
33
|
+
private load;
|
|
34
|
+
private preview;
|
|
35
|
+
private hasAncestor;
|
|
36
|
+
private key;
|
|
37
|
+
private land;
|
|
38
|
+
private expandOrEnter;
|
|
39
|
+
private collapseOrLeave;
|
|
40
|
+
private tell;
|
|
41
|
+
private say;
|
|
42
|
+
}
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { StaleResultError } from '@adecore/lsp';
|
|
2
|
+
import { CALL_LEVEL_LIMIT, childrenOf, moreIdOf, rootsOf, rowsOf, statusIdOf, stepsOf, targetOf } from './call-hierarchy-model.js';
|
|
3
|
+
import { PEEK_HEIGHT } from './peek.js';
|
|
4
|
+
import { snippetOf } from './peek-model.js';
|
|
5
|
+
import { isShortcut } from './shortcut-keys.js';
|
|
6
|
+
import { messageOf } from './error-message.js';
|
|
7
|
+
import { realTimers } from './timers.js';
|
|
8
|
+
const TOAST_ID = 'language-call-hierarchy';
|
|
9
|
+
const PREPARE = 'textDocument/prepareCallHierarchy';
|
|
10
|
+
/* The row the panel stands in is as tall as a peek's, plus the segmented control that is a pixel taller than the peek's title bar. */
|
|
11
|
+
export const CALL_HIERARCHY_HEIGHT = PEEK_HEIGHT + 2;
|
|
12
|
+
const OWNER = 'call-hierarchy';
|
|
13
|
+
/*
|
|
14
|
+
* The callers of the symbol at the caret, or the symbols it calls, as a tree between the lines of the file
|
|
15
|
+
* under the line the caret is on, with the code around the call that is active. A node asks the server for its
|
|
16
|
+
* own calls when it is opened, and a level shows its first calls with a row that brings the others.
|
|
17
|
+
* The editor keeps the keyboard: arrows move and open, Enter goes to the call, Escape closes, and the
|
|
18
|
+
* shortcut that opened the tree switches between callers and callees. Typing closes it.
|
|
19
|
+
*/
|
|
20
|
+
export class CallHierarchyFeature {
|
|
21
|
+
language;
|
|
22
|
+
timers;
|
|
23
|
+
nodes = new Map();
|
|
24
|
+
roots = [];
|
|
25
|
+
prepared = [];
|
|
26
|
+
direction = 'incoming';
|
|
27
|
+
controller = new AbortController();
|
|
28
|
+
/* Raised whenever the tree starts over or closes, so an answer for the tree that was never lands in the one that is. */
|
|
29
|
+
epoch = 0;
|
|
30
|
+
previewEpoch = 0;
|
|
31
|
+
texts = new Map();
|
|
32
|
+
constructor(language, timers = realTimers) {
|
|
33
|
+
this.language = language;
|
|
34
|
+
this.timers = timers;
|
|
35
|
+
const { editor } = language;
|
|
36
|
+
const offs = [
|
|
37
|
+
editor.onKeyDown((event) => {
|
|
38
|
+
if (isShortcut(this.language.shortcuts.callHierarchy, event)) {
|
|
39
|
+
if (this.isOpen) {
|
|
40
|
+
this.setDirection(this.direction === 'incoming' ? 'outgoing' : 'incoming');
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
void this.open();
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return this.key(event);
|
|
48
|
+
}),
|
|
49
|
+
editor.onTextChange(() => this.close())
|
|
50
|
+
];
|
|
51
|
+
language.onDispose(() => {
|
|
52
|
+
for (const off of offs) {
|
|
53
|
+
off();
|
|
54
|
+
}
|
|
55
|
+
this.close();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
get isOpen() {
|
|
59
|
+
return (this.language.popups.getState().callHierarchy ?? null) !== null;
|
|
60
|
+
}
|
|
61
|
+
/* Whether a server of this document answers the three requests the tree is made of. */
|
|
62
|
+
get supported() {
|
|
63
|
+
const { project, uri } = this.language;
|
|
64
|
+
const { service } = project;
|
|
65
|
+
return (service.prepareCallHierarchy !== undefined &&
|
|
66
|
+
service.incomingCalls !== undefined &&
|
|
67
|
+
service.outgoingCalls !== undefined &&
|
|
68
|
+
service.supports(PREPARE, uri));
|
|
69
|
+
}
|
|
70
|
+
/* Opens the tree for the symbol at the position, on its callers unless told otherwise. */
|
|
71
|
+
async open(position = this.language.editor.getCaret(), direction = 'incoming') {
|
|
72
|
+
const { editor, project, uri } = this.language;
|
|
73
|
+
if (!this.supported) {
|
|
74
|
+
this.tell(this.say('unavailable'));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.close();
|
|
78
|
+
const epoch = this.epoch;
|
|
79
|
+
let items;
|
|
80
|
+
try {
|
|
81
|
+
items = await project.service.prepareCallHierarchy(uri, position, { signal: this.controller.signal });
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (epoch === this.epoch && !(error instanceof StaleResultError)) {
|
|
85
|
+
this.tell(this.say('failed', { message: messageOf(error) }));
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (epoch !== this.epoch) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (items === null || items.length === 0) {
|
|
93
|
+
this.tell(this.say('none'));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.language.peek.close();
|
|
97
|
+
this.prepared = items;
|
|
98
|
+
this.direction = direction;
|
|
99
|
+
this.seed();
|
|
100
|
+
const active = this.roots[0];
|
|
101
|
+
this.language.popups.setState({
|
|
102
|
+
callHierarchy: { direction, container: null, name: items[0].name, rows: rowsOf(this.nodes, this.roots), active, site: 0, sites: 0, preview: null }
|
|
103
|
+
});
|
|
104
|
+
this.select(active);
|
|
105
|
+
// The editor draws a row once per id, so a tree opened over another needs an id of its own to get a container.
|
|
106
|
+
editor.setWidgets([{ id: `call-hierarchy-${epoch}`, line: position.line, height: CALL_HIERARCHY_HEIGHT, render: (container) => this.mounted(container) }], OWNER);
|
|
107
|
+
editor.focus();
|
|
108
|
+
for (const root of this.roots) {
|
|
109
|
+
void this.load(root);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
close() {
|
|
113
|
+
this.reset();
|
|
114
|
+
if (this.language.popups.getState().callHierarchy == null) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.language.popups.setState({ callHierarchy: null });
|
|
118
|
+
this.language.editor.setWidgets([], OWNER);
|
|
119
|
+
this.language.editor.setInputPopup(null);
|
|
120
|
+
this.language.editor.focus();
|
|
121
|
+
}
|
|
122
|
+
/* Starts the tree over from the same symbols for the other direction. */
|
|
123
|
+
setDirection(direction) {
|
|
124
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
125
|
+
if (view === null || view.direction === direction) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.reset();
|
|
129
|
+
this.direction = direction;
|
|
130
|
+
this.seed();
|
|
131
|
+
const active = this.roots[0];
|
|
132
|
+
this.language.popups.setState({
|
|
133
|
+
callHierarchy: { ...view, direction, rows: rowsOf(this.nodes, this.roots), active, site: 0, sites: 0, preview: null }
|
|
134
|
+
});
|
|
135
|
+
this.select(active);
|
|
136
|
+
for (const root of this.roots) {
|
|
137
|
+
void this.load(root);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/* Opens a node, asking the server for its calls the first time, or folds it away. */
|
|
141
|
+
toggle(id) {
|
|
142
|
+
const node = this.nodes.get(id);
|
|
143
|
+
if (node === undefined) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (node.expanded) {
|
|
147
|
+
this.patch(id, { expanded: false });
|
|
148
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
149
|
+
if (view !== null && this.hasAncestor(view.active, id)) {
|
|
150
|
+
this.select(id);
|
|
151
|
+
}
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.patch(id, { expanded: true });
|
|
155
|
+
if (node.load === 'idle' || node.load === 'failed') {
|
|
156
|
+
void this.load(id);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/* Asks again for the calls of a node whose last try failed. */
|
|
160
|
+
retry(id) {
|
|
161
|
+
if (this.nodes.get(id)?.load === 'failed') {
|
|
162
|
+
void this.load(id);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/* Brings the next stretch of calls of a level. */
|
|
166
|
+
showMore(parent) {
|
|
167
|
+
const node = this.nodes.get(parent);
|
|
168
|
+
if (node === undefined) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.patch(parent, { shown: Math.min(node.children.length, node.shown + CALL_LEVEL_LIMIT) });
|
|
172
|
+
const first = node.children[node.shown];
|
|
173
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
174
|
+
// The row that offered more is gone once everything shows, so the selection moves on to what it brought.
|
|
175
|
+
if (first !== undefined && view?.active === moreIdOf(parent)) {
|
|
176
|
+
this.select(first);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/* Makes a row the active one and shows the code of its call. */
|
|
180
|
+
select(id, site = 0) {
|
|
181
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
182
|
+
const node = this.nodes.get(id);
|
|
183
|
+
if (view === null || !view.rows.some((row) => row.id === id)) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const sites = node?.sites.length ?? view.sites;
|
|
187
|
+
const next = node === undefined ? view.site : Math.max(0, Math.min(Math.max(0, sites - 1), site));
|
|
188
|
+
this.language.popups.setState({ callHierarchy: { ...view, active: id, site: next, sites, preview: node === undefined ? view.preview : null } });
|
|
189
|
+
if (node !== undefined) {
|
|
190
|
+
void this.preview(node, next);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/* Steps through the places a call is made from when the active one has more than one. */
|
|
194
|
+
stepSite(direction) {
|
|
195
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
196
|
+
if (view !== null && view.sites > 1) {
|
|
197
|
+
this.select(view.active, (view.site + direction + view.sites) % view.sites);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/* Goes to the call of a row, or to the symbol of a root, and closes the tree. */
|
|
201
|
+
go(id) {
|
|
202
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
203
|
+
const node = this.nodes.get(id);
|
|
204
|
+
if (view === null || node === undefined) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const { uri, range } = targetOf(node, id === view.active ? view.site : 0);
|
|
208
|
+
this.close();
|
|
209
|
+
this.language.goTo({ uri, range });
|
|
210
|
+
}
|
|
211
|
+
reset() {
|
|
212
|
+
this.epoch++;
|
|
213
|
+
this.previewEpoch++;
|
|
214
|
+
this.controller.abort();
|
|
215
|
+
this.controller = new AbortController();
|
|
216
|
+
this.nodes = new Map();
|
|
217
|
+
this.roots = [];
|
|
218
|
+
this.texts = new Map();
|
|
219
|
+
}
|
|
220
|
+
seed() {
|
|
221
|
+
const roots = rootsOf(this.prepared);
|
|
222
|
+
this.roots = roots.map((root) => root.id);
|
|
223
|
+
this.nodes = new Map(roots.map((root) => [root.id, root]));
|
|
224
|
+
}
|
|
225
|
+
mounted(container) {
|
|
226
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
227
|
+
if (view !== null && view.container !== container) {
|
|
228
|
+
this.language.popups.setState({ callHierarchy: { ...view, container } });
|
|
229
|
+
// The row opens under the caret line, which may sit at the bottom of the view with the tree below the fold. The element is in the page a turn later.
|
|
230
|
+
this.timers.set(() => container.scrollIntoView?.({ block: 'nearest' }), 0);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
patch(id, change) {
|
|
234
|
+
const node = this.nodes.get(id);
|
|
235
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
236
|
+
if (node === undefined || view === null) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
this.nodes.set(id, { ...node, ...change });
|
|
240
|
+
this.language.popups.setState({ callHierarchy: { ...view, rows: rowsOf(this.nodes, this.roots) } });
|
|
241
|
+
}
|
|
242
|
+
async load(id) {
|
|
243
|
+
const node = this.nodes.get(id);
|
|
244
|
+
if (node === undefined || node.load === 'loading') {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const { service, i18n } = this.language.project;
|
|
248
|
+
const { uri, editor } = this.language;
|
|
249
|
+
const { direction, epoch } = this;
|
|
250
|
+
const options = { signal: this.controller.signal, parallel: true };
|
|
251
|
+
this.patch(id, { load: 'loading', error: null });
|
|
252
|
+
try {
|
|
253
|
+
const calls = direction === 'incoming' ? await service.incomingCalls(uri, node.item, options) : await service.outgoingCalls(uri, node.item, options);
|
|
254
|
+
if (epoch !== this.epoch) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const children = childrenOf(direction, node, calls ?? []);
|
|
258
|
+
for (const child of children) {
|
|
259
|
+
this.nodes.set(child.id, child);
|
|
260
|
+
}
|
|
261
|
+
this.patch(id, { load: 'loaded', children: children.map((child) => child.id), shown: Math.min(children.length, CALL_LEVEL_LIMIT) });
|
|
262
|
+
editor.announce(i18n.t(`editor:language.callHierarchy.announce.${direction}`, { count: children.length, name: node.item.name }));
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
if (epoch !== this.epoch) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (error instanceof StaleResultError) {
|
|
269
|
+
this.close();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
this.patch(id, { load: 'failed', error: messageOf(error) });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async preview(node, site) {
|
|
276
|
+
const { project } = this.language;
|
|
277
|
+
const epoch = ++this.previewEpoch;
|
|
278
|
+
const { uri, range } = targetOf(node, site);
|
|
279
|
+
let text = this.texts.get(uri);
|
|
280
|
+
if (text === undefined) {
|
|
281
|
+
try {
|
|
282
|
+
text = (await project.readText(uri)) ?? undefined;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
// A file that cannot be read shows no code, as one that is no text file does.
|
|
286
|
+
text = undefined;
|
|
287
|
+
}
|
|
288
|
+
if (epoch !== this.previewEpoch) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (text !== undefined) {
|
|
292
|
+
this.texts.set(uri, text);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
296
|
+
if (view === null || view.active !== node.id) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const snippet = text === undefined ? null : snippetOf(text, range.start.line, range);
|
|
300
|
+
this.language.popups.setState({ callHierarchy: { ...view, preview: snippet === null ? null : { ...snippet, uri } } });
|
|
301
|
+
}
|
|
302
|
+
/* Whether a row stands under a node; the `more` and `status` rows belong to the node they are about. */
|
|
303
|
+
hasAncestor(id, ancestor) {
|
|
304
|
+
const owner = [moreIdOf(''), statusIdOf('')]
|
|
305
|
+
.map((suffix) => (id.endsWith(suffix) ? id.slice(0, -suffix.length) : null))
|
|
306
|
+
.find((found) => found !== null);
|
|
307
|
+
if (owner !== undefined && owner !== null) {
|
|
308
|
+
return owner === ancestor || this.hasAncestor(owner, ancestor);
|
|
309
|
+
}
|
|
310
|
+
for (let node = this.nodes.get(id); node !== undefined && node.parent !== null; node = this.nodes.get(node.parent)) {
|
|
311
|
+
if (node.parent === ancestor) {
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
key(event) {
|
|
318
|
+
const view = this.language.popups.getState().callHierarchy ?? null;
|
|
319
|
+
if (view === null || event.metaKey || event.ctrlKey || event.altKey || event.shiftKey || event.isComposing) {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
const steps = stepsOf(view.rows);
|
|
323
|
+
const index = steps.findIndex((row) => row.id === view.active);
|
|
324
|
+
const row = steps[index];
|
|
325
|
+
switch (event.key) {
|
|
326
|
+
case 'ArrowDown':
|
|
327
|
+
this.land(steps[Math.min(steps.length - 1, index + 1)]);
|
|
328
|
+
return true;
|
|
329
|
+
case 'ArrowUp':
|
|
330
|
+
this.land(steps[Math.max(0, index - 1)]);
|
|
331
|
+
return true;
|
|
332
|
+
case 'Home':
|
|
333
|
+
this.land(steps[0]);
|
|
334
|
+
return true;
|
|
335
|
+
case 'End':
|
|
336
|
+
this.land(steps.at(-1));
|
|
337
|
+
return true;
|
|
338
|
+
case 'ArrowRight':
|
|
339
|
+
this.expandOrEnter(row, steps[index + 1]);
|
|
340
|
+
return true;
|
|
341
|
+
case 'ArrowLeft':
|
|
342
|
+
this.collapseOrLeave(row);
|
|
343
|
+
return true;
|
|
344
|
+
case 'Enter':
|
|
345
|
+
if (row?.kind === 'more') {
|
|
346
|
+
this.showMore(row.parent);
|
|
347
|
+
}
|
|
348
|
+
else if (row !== undefined) {
|
|
349
|
+
this.go(row.id);
|
|
350
|
+
}
|
|
351
|
+
return true;
|
|
352
|
+
case 'Escape':
|
|
353
|
+
this.close();
|
|
354
|
+
return true;
|
|
355
|
+
default:
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
land(row) {
|
|
360
|
+
if (row !== undefined) {
|
|
361
|
+
this.select(row.id);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/* Right opens a closed node, tries a failed one again, and steps into an open one. */
|
|
365
|
+
expandOrEnter(row, next) {
|
|
366
|
+
if (row?.kind !== 'node') {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (!row.expanded) {
|
|
370
|
+
this.toggle(row.id);
|
|
371
|
+
}
|
|
372
|
+
else if (row.load === 'failed') {
|
|
373
|
+
this.retry(row.id);
|
|
374
|
+
}
|
|
375
|
+
else if (next !== undefined && next.level > row.level) {
|
|
376
|
+
this.land(next);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/* Left closes an open node and steps out of a closed one. */
|
|
380
|
+
collapseOrLeave(row) {
|
|
381
|
+
if (row === undefined) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (row.kind === 'node' && row.expanded && !row.leaf) {
|
|
385
|
+
this.toggle(row.id);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const parent = row.kind === 'node' ? (this.nodes.get(row.id)?.parent ?? null) : row.parent;
|
|
389
|
+
if (parent !== null) {
|
|
390
|
+
this.select(parent);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
tell(title) {
|
|
394
|
+
this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title });
|
|
395
|
+
}
|
|
396
|
+
say(key, options) {
|
|
397
|
+
return this.language.project.i18n.t(`editor:language.callHierarchy.${key}`, options);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
@@ -19,5 +19,7 @@ export interface EditPreview {
|
|
|
19
19
|
readonly hiddenLines: number;
|
|
20
20
|
readonly otherFiles: number;
|
|
21
21
|
readonly moves: number;
|
|
22
|
+
readonly creates?: number;
|
|
23
|
+
readonly deletes?: number;
|
|
22
24
|
}
|
|
23
25
|
export declare function previewOf(text: string, edit: WorkspaceEdit, uri: string): EditPreview | null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { applyTextEdits } from '@adecore/lsp';
|
|
2
2
|
import { comparePositions } from './diagnostics-model.js';
|
|
3
|
-
import {
|
|
3
|
+
import { resourceOperationsOf, textEntriesOf } from './workspace-edit.js';
|
|
4
4
|
/* The order of the list under the caret: the fixes and the rewrites that are one keystroke away first, the refactorings after them. */
|
|
5
5
|
export const ACTION_GROUPS = ['quickfix', 'rewrite', 'extract', 'inline', 'move', 'refactor', 'source', 'other'];
|
|
6
6
|
/* The order of Refactor This: what changes the structure of the code first, the rewrites after it. */
|
|
@@ -75,15 +75,17 @@ const PREVIEW_LINES = 3;
|
|
|
75
75
|
function linesOf(text) {
|
|
76
76
|
return text.split(/\r\n|\r|\n/);
|
|
77
77
|
}
|
|
78
|
-
/* Null when the edit changes nothing
|
|
78
|
+
/* Null when the edit changes nothing. A file it creates is counted under `creates` and not as another file; moves and deletes are counted apart. */
|
|
79
79
|
export function previewOf(text, edit, uri) {
|
|
80
|
-
const entries =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
const entries = textEntriesOf(edit);
|
|
81
|
+
const operations = resourceOperationsOf(edit);
|
|
82
|
+
const count = (kind) => operations.filter((operation) => operation.kind === kind).length;
|
|
83
|
+
const moves = count('rename');
|
|
84
|
+
const creates = count('create');
|
|
85
|
+
const deletes = count('delete');
|
|
84
86
|
const own = entries.get(uri);
|
|
85
|
-
const
|
|
86
|
-
const otherFiles = [...entries.keys()].filter((key) => key !== uri).length;
|
|
87
|
+
const made = new Set(operations.filter((operation) => operation.kind === 'create').map((operation) => operation.uri));
|
|
88
|
+
const otherFiles = [...entries.keys()].filter((key) => key !== uri && !made.has(key)).length;
|
|
87
89
|
let removed = [];
|
|
88
90
|
let added = [];
|
|
89
91
|
if (own !== undefined) {
|
|
@@ -107,9 +109,9 @@ export function previewOf(text, edit, uri) {
|
|
|
107
109
|
removed = before.slice(head, before.length - tail);
|
|
108
110
|
added = next.slice(head, next.length - tail);
|
|
109
111
|
}
|
|
110
|
-
if (removed.length === 0 && added.length === 0 && otherFiles === 0 && moves === 0) {
|
|
112
|
+
if (removed.length === 0 && added.length === 0 && otherFiles === 0 && moves === 0 && creates === 0 && deletes === 0) {
|
|
111
113
|
return null;
|
|
112
114
|
}
|
|
113
115
|
const hiddenLines = Math.max(0, removed.length - PREVIEW_LINES) + Math.max(0, added.length - PREVIEW_LINES);
|
|
114
|
-
return { removed: removed.slice(0, PREVIEW_LINES), added: added.slice(0, PREVIEW_LINES), hiddenLines, otherFiles, moves };
|
|
116
|
+
return { removed: removed.slice(0, PREVIEW_LINES), added: added.slice(0, PREVIEW_LINES), hiddenLines, otherFiles, moves, creates, deletes };
|
|
115
117
|
}
|
package/dist/code-actions.js
CHANGED
|
@@ -261,7 +261,9 @@ export class CodeActionsFeature {
|
|
|
261
261
|
const notes = [
|
|
262
262
|
...(found.hiddenLines > 0 ? [this.say('moreLines', { count: found.hiddenLines })] : []),
|
|
263
263
|
...(found.otherFiles > 0 ? [this.say('otherFiles', { count: found.otherFiles })] : []),
|
|
264
|
-
...(found.moves > 0 ? [this.say('moves', { count: found.moves })] : [])
|
|
264
|
+
...(found.moves > 0 ? [this.say('moves', { count: found.moves })] : []),
|
|
265
|
+
...((found.creates ?? 0) > 0 ? [this.say('creates', { count: found.creates })] : []),
|
|
266
|
+
...((found.deletes ?? 0) > 0 ? [this.say('deletes', { count: found.deletes })] : [])
|
|
265
267
|
];
|
|
266
268
|
return { removed: found.removed, added: found.added, note: notes.length === 0 ? null : notes.join(' · ') };
|
|
267
269
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { SnippetTransformJob } from './snippet-transform-protocol.ts';
|
|
1
2
|
import type { EditorContentChange, EditorPosition, EditorRange } from '@adecore/editor';
|
|
2
|
-
import type
|
|
3
|
+
import { type CompletionItem, type CompletionResult, type MarkupContent } from '@adecore/lsp';
|
|
3
4
|
import type { CompletionDocs } from './popups.ts';
|
|
4
|
-
import { type SnippetStop } from './snippet.ts';
|
|
5
|
+
import { type SnippetField, type SnippetStop, type SnippetTemplate, type SnippetVariableResolver } from './snippet.ts';
|
|
5
6
|
export declare function isIdentifierCharacter(character: string): boolean;
|
|
6
7
|
export declare function identifierPrefix(before: string): string;
|
|
7
8
|
export declare function opensCompletion(before: string, text: string): boolean;
|
|
@@ -34,12 +35,21 @@ export declare const CLASS_KIND = 7;
|
|
|
34
35
|
export declare const SNIPPET_FORMAT = 2;
|
|
35
36
|
export declare function isCallableKind(kind: number | undefined): boolean;
|
|
36
37
|
export interface Insertion {
|
|
38
|
+
readonly limited?: true;
|
|
37
39
|
readonly range: EditorRange;
|
|
38
40
|
readonly text: string;
|
|
39
41
|
readonly stops: readonly SnippetStop[];
|
|
42
|
+
readonly fields?: readonly SnippetField[];
|
|
43
|
+
readonly variableTransforms?: readonly SnippetTransformJob[];
|
|
40
44
|
}
|
|
41
|
-
export
|
|
42
|
-
|
|
45
|
+
export interface SnippetInsertionOptions {
|
|
46
|
+
readonly resolve?: SnippetVariableResolver;
|
|
47
|
+
readonly variableTransforms?: readonly string[];
|
|
48
|
+
readonly template?: SnippetTemplate;
|
|
49
|
+
readonly indentationBefore?: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function insertionOf(item: CompletionItem, caret: EditorPosition, lineBefore: string, replace: boolean, lineAfter?: string, options?: SnippetInsertionOptions): Insertion;
|
|
52
|
+
export declare function mirroredInsertions(carets: readonly EditorRange[], primary: EditorRange, caret: EditorPosition, text: (range: EditorRange) => string, insertion: Insertion, includeSelections?: boolean): EditorContentChange[];
|
|
43
53
|
export declare function documentationText(documentation: string | MarkupContent | undefined): string;
|
|
44
54
|
export declare const SOURCE_LINE: RegExp;
|
|
45
55
|
export declare function completionDocsOf(item: CompletionItem, highlightLanguage: string): CompletionDocs | null;
|
package/dist/completion-model.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { applyCompletionDefaults } from '@adecore/lsp';
|
|
1
2
|
import { comparePositions } from './diagnostics-model.js';
|
|
2
3
|
import { escapeMarkdown, isEmptyHover, splitSignatures } from './hover-content.js';
|
|
3
|
-
import { parseSnippet, tabOrder } from './snippet.js';
|
|
4
|
+
import { compileSnippet, parseSnippet, tabOrder } from './snippet.js';
|
|
5
|
+
import { indentSnippet } from './snippet-indent.js';
|
|
4
6
|
const IDENTIFIER_CHARACTER = /[\p{L}\p{N}\p{M}_$]/u;
|
|
5
7
|
export function isIdentifierCharacter(character) {
|
|
6
8
|
return IDENTIFIER_CHARACTER.test(character);
|
|
@@ -117,35 +119,23 @@ export function editRangeOf(item, replace) {
|
|
|
117
119
|
}
|
|
118
120
|
/* Items from a result, with the list's defaults filled in so each carries its own edit range and format. */
|
|
119
121
|
export function itemsOf(result) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
if (Array.isArray(result)) {
|
|
124
|
-
return { items: result, incomplete: false };
|
|
125
|
-
}
|
|
126
|
-
const list = result;
|
|
127
|
-
const defaults = list.itemDefaults;
|
|
128
|
-
if (defaults === undefined) {
|
|
129
|
-
return { items: list.items, incomplete: list.isIncomplete };
|
|
130
|
-
}
|
|
122
|
+
const list = result === null || Array.isArray(result) ? undefined : result;
|
|
123
|
+
const items = result === null ? [] : Array.isArray(result) ? result : result.items;
|
|
131
124
|
return {
|
|
132
|
-
incomplete: list
|
|
133
|
-
items:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
next.insertTextFormat = defaults.insertTextFormat;
|
|
125
|
+
incomplete: list?.isIncomplete ?? false,
|
|
126
|
+
items: items.flatMap((item) => {
|
|
127
|
+
if (typeof item?.label !== 'string') {
|
|
128
|
+
return [];
|
|
137
129
|
}
|
|
138
|
-
|
|
139
|
-
|
|
130
|
+
try {
|
|
131
|
+
return [applyCompletionDefaults(item, list?.itemDefaults, { onInvalidRange: () => undefined })];
|
|
140
132
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
: { insert: defaults.editRange.insert, replace: defaults.editRange.replace, newText: text };
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (error instanceof TypeError || error instanceof RangeError) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
147
138
|
}
|
|
148
|
-
return next;
|
|
149
139
|
})
|
|
150
140
|
};
|
|
151
141
|
}
|
|
@@ -236,7 +226,7 @@ function withoutCall(snippet) {
|
|
|
236
226
|
* What goes into the text for an item and the range it takes the place of, given the caret. `lineAfter` is
|
|
237
227
|
* the rest of the line after the caret: an item that would add a call does not when a parenthesis follows.
|
|
238
228
|
*/
|
|
239
|
-
export function insertionOf(item, caret, lineBefore, replace, lineAfter = '') {
|
|
229
|
+
export function insertionOf(item, caret, lineBefore, replace, lineAfter = '', options = {}) {
|
|
240
230
|
const raw = item.textEdit?.newText ?? item.insertText ?? item.label;
|
|
241
231
|
const edit = editRangeOf(item, replace);
|
|
242
232
|
const range = edit === null
|
|
@@ -244,17 +234,34 @@ export function insertionOf(item, caret, lineBefore, replace, lineAfter = '') {
|
|
|
244
234
|
: // The word grew since the answer came, so what was typed since belongs to what is replaced.
|
|
245
235
|
{ start: edit.start, end: comparePositions(edit.end, caret) < 0 && edit.end.line === caret.line ? caret : edit.end };
|
|
246
236
|
if (item.insertTextFormat !== SNIPPET_FORMAT) {
|
|
247
|
-
|
|
237
|
+
const adjusted = item.insertTextMode === 2
|
|
238
|
+
? indentSnippet({ text: raw, stops: [] }, options.indentationBefore ?? lineBefore.slice(0, range.start.character))
|
|
239
|
+
: { text: raw, stops: [] };
|
|
240
|
+
return { range, text: adjusted.text, stops: [], ...(adjusted.limited ? { limited: true } : {}) };
|
|
248
241
|
}
|
|
249
242
|
const followed = range.end.line === caret.line && lineAfter.slice(Math.max(0, range.end.character - caret.character)).startsWith('(');
|
|
250
|
-
const
|
|
251
|
-
|
|
243
|
+
const source = followed && isCallableKind(item.kind) ? withoutCall(raw) : raw;
|
|
244
|
+
const template = options.template?.source === source ? options.template : compileSnippet(source);
|
|
245
|
+
const rendered = template.render(options.resolve, options.variableTransforms);
|
|
246
|
+
const parsed = item.insertTextMode === 2 ? indentSnippet(rendered, options.indentationBefore ?? lineBefore.slice(0, range.start.character)) : rendered;
|
|
247
|
+
return {
|
|
248
|
+
range,
|
|
249
|
+
text: parsed.text,
|
|
250
|
+
stops: tabOrder(parsed),
|
|
251
|
+
fields: parsed.fields,
|
|
252
|
+
...(options.variableTransforms !== undefined || rendered.transforms === undefined
|
|
253
|
+
? {}
|
|
254
|
+
: {
|
|
255
|
+
variableTransforms: rendered.transforms.map((range) => ({ ...range.transform, value: rendered.text.slice(range.start, range.end) }))
|
|
256
|
+
}),
|
|
257
|
+
...(parsed.limited ? { limited: true } : {})
|
|
258
|
+
};
|
|
252
259
|
}
|
|
253
260
|
/*
|
|
254
261
|
* The same insertion at every other caret that has the word the primary caret has before it, so a name typed at
|
|
255
262
|
* several carets is completed at all of them. A caret that is inside a selection, or after other text, is left alone.
|
|
256
263
|
*/
|
|
257
|
-
export function mirroredInsertions(carets, primary, caret, text, insertion) {
|
|
264
|
+
export function mirroredInsertions(carets, primary, caret, text, insertion, includeSelections = false) {
|
|
258
265
|
const before = insertion.range.start.line === caret.line ? caret.character - insertion.range.start.character : -1;
|
|
259
266
|
const after = insertion.range.end.line === caret.line ? insertion.range.end.character - caret.character : -1;
|
|
260
267
|
if (before < 0 || after < 0) {
|
|
@@ -263,11 +270,11 @@ export function mirroredInsertions(carets, primary, caret, text, insertion) {
|
|
|
263
270
|
const typed = text({ start: insertion.range.start, end: caret });
|
|
264
271
|
const replaced = text({ start: caret, end: insertion.range.end });
|
|
265
272
|
return carets.flatMap((other) => {
|
|
266
|
-
const at = other.start;
|
|
273
|
+
const at = includeSelections ? other.end : other.start;
|
|
267
274
|
if (comparePositions(other.start, primary.start) === 0 && comparePositions(other.end, primary.end) === 0) {
|
|
268
275
|
return [];
|
|
269
276
|
}
|
|
270
|
-
if (comparePositions(other.start, other.end) !== 0 || at.character < before) {
|
|
277
|
+
if ((!includeSelections && comparePositions(other.start, other.end) !== 0) || at.character < before) {
|
|
271
278
|
return [];
|
|
272
279
|
}
|
|
273
280
|
const range = { start: { line: at.line, character: at.character - before }, end: { line: at.line, character: at.character + after } };
|