@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
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { parseHunkHeader } from '@adecore/merge';
|
|
2
|
+
|
|
3
|
+
export type PatchLineRole = 'file-header' | 'hunk-header' | 'context' | 'added' | 'removed' | 'no-newline' | 'binary' | 'other';
|
|
4
|
+
|
|
5
|
+
export type PatchFileStatus = 'added' | 'deleted' | 'renamed' | 'copied' | 'modified' | 'binary';
|
|
6
|
+
|
|
7
|
+
/* What a line of a patch is, and the one-based numbers it has in the files; null where the file has no such line. */
|
|
8
|
+
export interface PatchLayoutLine {
|
|
9
|
+
readonly role: PatchLineRole;
|
|
10
|
+
readonly old: number | null;
|
|
11
|
+
readonly new: number | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PatchHunkLayout {
|
|
15
|
+
/* Zero-based line of the `@@` header in the patch text. */
|
|
16
|
+
readonly line: number;
|
|
17
|
+
/* As the header says them, `oldStart` and `newStart` one-based. */
|
|
18
|
+
readonly oldStart: number;
|
|
19
|
+
readonly oldCount: number;
|
|
20
|
+
readonly newStart: number;
|
|
21
|
+
readonly newCount: number;
|
|
22
|
+
readonly section: string;
|
|
23
|
+
/* False when the lines under the header no longer add up to its counts, such as after an edit. */
|
|
24
|
+
readonly consistent: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface PatchFileLayout {
|
|
28
|
+
/* Null for `/dev/null`, or when the patch does not say. Quotes and prefixes such as `a/` are gone. */
|
|
29
|
+
readonly oldPath: string | null;
|
|
30
|
+
readonly newPath: string | null;
|
|
31
|
+
readonly status: PatchFileStatus;
|
|
32
|
+
/* Zero-based lines of the section in the patch text, `lastLine` included. */
|
|
33
|
+
readonly firstLine: number;
|
|
34
|
+
readonly lastLine: number;
|
|
35
|
+
readonly added: number;
|
|
36
|
+
readonly removed: number;
|
|
37
|
+
readonly hunks: readonly PatchHunkLayout[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PatchLayout {
|
|
41
|
+
/* One entry per line of the text, `split('\n')`, up to the limit. */
|
|
42
|
+
readonly lines: readonly PatchLayoutLine[];
|
|
43
|
+
readonly files: readonly PatchFileLayout[];
|
|
44
|
+
/* The text went past a limit: the lines after it have no role and no file. */
|
|
45
|
+
readonly truncated: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface PatchLimits {
|
|
49
|
+
/* Lines read before the layout stops; defaults to 50,000. */
|
|
50
|
+
readonly maxLines?: number;
|
|
51
|
+
/* Characters read before the layout stops; defaults to 4,000,000. */
|
|
52
|
+
readonly maxCharacters?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const DEFAULT_PATCH_LIMITS = { maxLines: 50_000, maxCharacters: 4_000_000 } as const;
|
|
56
|
+
|
|
57
|
+
const OTHER: PatchLayoutLine = { role: 'other', old: null, new: null };
|
|
58
|
+
const FILE_HEADER: PatchLayoutLine = { role: 'file-header', old: null, new: null };
|
|
59
|
+
const HUNK_HEADER: PatchLayoutLine = { role: 'hunk-header', old: null, new: null };
|
|
60
|
+
const NO_NEWLINE: PatchLayoutLine = { role: 'no-newline', old: null, new: null };
|
|
61
|
+
const BINARY: PatchLayoutLine = { role: 'binary', old: null, new: null };
|
|
62
|
+
|
|
63
|
+
const GIT_HEADERS =
|
|
64
|
+
/^(?:index |old mode |new mode |deleted file mode |new file mode |similarity index |dissimilarity index |rename from |rename to |copy from |copy to )/;
|
|
65
|
+
|
|
66
|
+
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
|
|
67
|
+
|
|
68
|
+
interface Section {
|
|
69
|
+
oldPath: string | null;
|
|
70
|
+
newPath: string | null;
|
|
71
|
+
git: boolean;
|
|
72
|
+
/* Whether the `diff --git` line showed `a/X b/Y`, which is the only time `a/` and `b/` are not part of the path. */
|
|
73
|
+
prefixed: boolean;
|
|
74
|
+
created: boolean;
|
|
75
|
+
deleted: boolean;
|
|
76
|
+
renamed: boolean;
|
|
77
|
+
copied: boolean;
|
|
78
|
+
binary: boolean;
|
|
79
|
+
/* Whether `---` and `+++` named the files, which then beat the `diff --git` line. */
|
|
80
|
+
named: boolean;
|
|
81
|
+
firstLine: number;
|
|
82
|
+
lastLine: number;
|
|
83
|
+
added: number;
|
|
84
|
+
removed: number;
|
|
85
|
+
hunks: Mutable<PatchHunkLayout>[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* The hunk lines are being read into, with what its header still promises. */
|
|
89
|
+
interface OpenHunk {
|
|
90
|
+
layout: Mutable<PatchHunkLayout>;
|
|
91
|
+
oldLeft: number;
|
|
92
|
+
newLeft: number;
|
|
93
|
+
oldNumber: number;
|
|
94
|
+
newNumber: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const ESCAPES: Readonly<Record<string, number>> = { a: 7, b: 8, f: 12, n: 10, r: 13, t: 9, v: 11, '"': 34, '\\': 92 };
|
|
98
|
+
|
|
99
|
+
/* Reads a path as git writes it: bare, or in quotes with C escapes and octal bytes. */
|
|
100
|
+
function unquote(raw: string): string {
|
|
101
|
+
if (raw.length < 2 || !raw.startsWith('"') || !raw.endsWith('"')) {
|
|
102
|
+
return raw;
|
|
103
|
+
}
|
|
104
|
+
const bytes: number[] = [];
|
|
105
|
+
const encoder = new TextEncoder();
|
|
106
|
+
const body = raw.slice(1, -1);
|
|
107
|
+
for (let i = 0; i < body.length; i++) {
|
|
108
|
+
if (body[i] !== '\\') {
|
|
109
|
+
const point = String.fromCodePoint(body.codePointAt(i)!);
|
|
110
|
+
bytes.push(...encoder.encode(point));
|
|
111
|
+
i += point.length - 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const octal = /^[0-3][0-7]{2}/.exec(body.slice(i + 1, i + 4));
|
|
115
|
+
const escaped = ESCAPES[body[i + 1] ?? ''];
|
|
116
|
+
if (octal !== null) {
|
|
117
|
+
bytes.push(Number.parseInt(octal[0], 8));
|
|
118
|
+
i += 3;
|
|
119
|
+
} else if (escaped !== undefined) {
|
|
120
|
+
bytes.push(escaped);
|
|
121
|
+
i++;
|
|
122
|
+
} else {
|
|
123
|
+
bytes.push(92);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return new TextDecoder().decode(new Uint8Array(bytes));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* The name on a `---`, `+++`, `rename` or `copy` line: a timestamp after a tab goes, and `/dev/null` is no file. */
|
|
130
|
+
function pathOf(raw: string): string | null {
|
|
131
|
+
const tab = raw.indexOf('\t');
|
|
132
|
+
const name = unquote(tab === -1 ? raw : raw.slice(0, tab));
|
|
133
|
+
return name === '/dev/null' ? null : name;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function stripPrefix(path: string | null, prefix: 'a/' | 'b/'): string | null {
|
|
137
|
+
return path !== null && path.startsWith(prefix) ? path.slice(2) : path;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* The two names of a `diff --git a/x b/y` line; with spaces in an unquoted name, the split that gives both halves the same name wins. */
|
|
141
|
+
function gitPaths(rest: string): { oldPath: string | null; newPath: string | null; prefixed: boolean } {
|
|
142
|
+
if (rest.startsWith('"')) {
|
|
143
|
+
const end = rest.indexOf('" ', 1);
|
|
144
|
+
if (end !== -1) {
|
|
145
|
+
const oldName = unquote(rest.slice(0, end + 1));
|
|
146
|
+
const newName = unquote(rest.slice(end + 2));
|
|
147
|
+
const prefixed = oldName.startsWith('a/') && newName.startsWith('b/');
|
|
148
|
+
return prefixed ? { oldPath: oldName.slice(2), newPath: newName.slice(2), prefixed } : { oldPath: oldName, newPath: newName, prefixed };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const middle = rest.indexOf(' b/');
|
|
152
|
+
if (rest.startsWith('a/') && middle !== -1) {
|
|
153
|
+
for (let at = middle; at !== -1; at = rest.indexOf(' b/', at + 1)) {
|
|
154
|
+
if (rest.slice(2, at) === rest.slice(at + 3)) {
|
|
155
|
+
return { oldPath: rest.slice(2, at), newPath: rest.slice(at + 3), prefixed: true };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return { oldPath: rest.slice(2, middle), newPath: unquote(rest.slice(middle + 3)), prefixed: true };
|
|
159
|
+
}
|
|
160
|
+
const split = rest.indexOf(' ');
|
|
161
|
+
return split === -1
|
|
162
|
+
? { oldPath: unquote(rest), newPath: null, prefixed: false }
|
|
163
|
+
: { oldPath: unquote(rest.slice(0, split)), newPath: unquote(rest.slice(split + 1)), prefixed: false };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function emptySection(firstLine: number, git: boolean): Section {
|
|
167
|
+
return {
|
|
168
|
+
oldPath: null,
|
|
169
|
+
newPath: null,
|
|
170
|
+
git,
|
|
171
|
+
created: false,
|
|
172
|
+
deleted: false,
|
|
173
|
+
renamed: false,
|
|
174
|
+
copied: false,
|
|
175
|
+
binary: false,
|
|
176
|
+
firstLine,
|
|
177
|
+
lastLine: firstLine,
|
|
178
|
+
added: 0,
|
|
179
|
+
removed: 0,
|
|
180
|
+
hunks: [],
|
|
181
|
+
named: false,
|
|
182
|
+
prefixed: false
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function statusOf(section: Section): PatchFileStatus {
|
|
187
|
+
if (section.binary) {
|
|
188
|
+
return 'binary';
|
|
189
|
+
}
|
|
190
|
+
if (section.created || (!section.git && section.oldPath === null && section.newPath !== null)) {
|
|
191
|
+
return 'added';
|
|
192
|
+
}
|
|
193
|
+
if (section.deleted || (!section.git && section.newPath === null && section.oldPath !== null)) {
|
|
194
|
+
return 'deleted';
|
|
195
|
+
}
|
|
196
|
+
if (section.renamed) {
|
|
197
|
+
return 'renamed';
|
|
198
|
+
}
|
|
199
|
+
return section.copied ? 'copied' : 'modified';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function linesOf(text: string, limits: Required<PatchLimits>): { lines: string[]; truncated: boolean } {
|
|
203
|
+
const lines: string[] = [];
|
|
204
|
+
let start = 0;
|
|
205
|
+
while (lines.length < limits.maxLines && start <= limits.maxCharacters) {
|
|
206
|
+
const end = text.indexOf('\n', start);
|
|
207
|
+
if (end === -1) {
|
|
208
|
+
lines.push(text.slice(start).replace(/\r$/, ''));
|
|
209
|
+
return { lines, truncated: false };
|
|
210
|
+
}
|
|
211
|
+
lines.push(text.slice(start, end).replace(/\r$/, ''));
|
|
212
|
+
start = end + 1;
|
|
213
|
+
}
|
|
214
|
+
// Stopping right after a final newline leaves nothing unread.
|
|
215
|
+
return { lines, truncated: start < text.length };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/*
|
|
219
|
+
* What every line of a patch is, with the file numbers of its lines and the files it touches. It reads
|
|
220
|
+
* git patches and plain `diff -u` output, tolerates a text that was edited so its hunks no longer add up
|
|
221
|
+
* (the hunk is marked inconsistent and the reading goes on) and never throws. Past the limits it stops
|
|
222
|
+
* and says so in `truncated`.
|
|
223
|
+
*/
|
|
224
|
+
export function patchLayoutOf(text: string, limits: PatchLimits = {}): PatchLayout {
|
|
225
|
+
const { lines: source, truncated } = linesOf(text, { ...DEFAULT_PATCH_LIMITS, ...limits });
|
|
226
|
+
const lines: PatchLayoutLine[] = [];
|
|
227
|
+
const sections: Section[] = [];
|
|
228
|
+
let section: Section | undefined;
|
|
229
|
+
let hunk: OpenHunk | undefined;
|
|
230
|
+
let binaryData = false;
|
|
231
|
+
const seal = (): void => {
|
|
232
|
+
if (hunk !== undefined && (hunk.oldLeft > 0 || hunk.newLeft > 0)) {
|
|
233
|
+
hunk.layout.consistent = false;
|
|
234
|
+
}
|
|
235
|
+
hunk = undefined;
|
|
236
|
+
};
|
|
237
|
+
const open = (line: number, git: boolean): Section => {
|
|
238
|
+
seal();
|
|
239
|
+
binaryData = false;
|
|
240
|
+
section = emptySection(line, git);
|
|
241
|
+
sections.push(section);
|
|
242
|
+
return section;
|
|
243
|
+
};
|
|
244
|
+
// A removed line can read `-- x`, so `---` starts a file only with `+++` under it.
|
|
245
|
+
const startsFile = (index: number): boolean => source[index]!.startsWith('--- ') && (source[index + 1]?.startsWith('+++ ') ?? false);
|
|
246
|
+
const push = (line: PatchLayoutLine, index: number): void => {
|
|
247
|
+
lines.push(line);
|
|
248
|
+
if (section !== undefined) {
|
|
249
|
+
section.lastLine = index;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
for (let index = 0; index < source.length; index++) {
|
|
253
|
+
const line = source[index]!;
|
|
254
|
+
const remaining = hunk !== undefined && (hunk.oldLeft > 0 || hunk.newLeft > 0);
|
|
255
|
+
const gitStart = line.startsWith('diff --git ');
|
|
256
|
+
const header = line.startsWith('@@') ? parseHunkHeader(line) : undefined;
|
|
257
|
+
const fileStart = startsFile(index) && (!remaining || source[index + 2]?.startsWith('@@') === true);
|
|
258
|
+
if (remaining && !gitStart && header === undefined && !fileStart) {
|
|
259
|
+
if (isContent(line) || line === '' || line.startsWith('\\')) {
|
|
260
|
+
push(contentLine(line, hunk!, section!), index);
|
|
261
|
+
} else {
|
|
262
|
+
hunk!.layout.consistent = false;
|
|
263
|
+
push(OTHER, index);
|
|
264
|
+
}
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (gitStart) {
|
|
268
|
+
const created = open(index, true);
|
|
269
|
+
({ oldPath: created.oldPath, newPath: created.newPath, prefixed: created.prefixed } = gitPaths(line.slice('diff --git '.length)));
|
|
270
|
+
push(FILE_HEADER, index);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (fileStart) {
|
|
274
|
+
const reuse = section !== undefined && section.git && !section.named && section.hunks.length === 0;
|
|
275
|
+
const target = reuse ? section! : open(index, false);
|
|
276
|
+
seal();
|
|
277
|
+
nameSides(target, line, source[index + 1]!);
|
|
278
|
+
push(FILE_HEADER, index);
|
|
279
|
+
push(FILE_HEADER, index + 1);
|
|
280
|
+
index++;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (header !== undefined) {
|
|
284
|
+
seal();
|
|
285
|
+
const target = section ?? open(index, false);
|
|
286
|
+
const layout: Mutable<PatchHunkLayout> = { line: index, ...header, consistent: true };
|
|
287
|
+
target.hunks.push(layout);
|
|
288
|
+
hunk = { layout, oldLeft: header.oldCount, newLeft: header.newCount, oldNumber: header.oldStart, newNumber: header.newStart };
|
|
289
|
+
push(HUNK_HEADER, index);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (section === undefined) {
|
|
293
|
+
push(OTHER, index);
|
|
294
|
+
} else if (hunk !== undefined && line.startsWith('\\')) {
|
|
295
|
+
push(NO_NEWLINE, index);
|
|
296
|
+
} else if (hunk !== undefined && isContent(line) && line !== '-- ') {
|
|
297
|
+
// The counts ran out, so a line a person typed below the hunk extends it. The `-- ` of a mail signature does not.
|
|
298
|
+
hunk.layout.consistent = false;
|
|
299
|
+
push(contentLine(line, hunk, section), index);
|
|
300
|
+
} else if (hunk === undefined && section.git && GIT_HEADERS.test(line)) {
|
|
301
|
+
readGitHeader(section, line);
|
|
302
|
+
push(FILE_HEADER, index);
|
|
303
|
+
} else if (/^Binary files .* differ$/.test(line) || line === 'GIT binary patch') {
|
|
304
|
+
section.binary = true;
|
|
305
|
+
binaryData = line === 'GIT binary patch';
|
|
306
|
+
push(BINARY, index);
|
|
307
|
+
} else if (binaryData && line !== '') {
|
|
308
|
+
push(BINARY, index);
|
|
309
|
+
} else if (line === '') {
|
|
310
|
+
lines.push(OTHER);
|
|
311
|
+
} else {
|
|
312
|
+
push(OTHER, index);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
seal();
|
|
316
|
+
return {
|
|
317
|
+
lines,
|
|
318
|
+
truncated,
|
|
319
|
+
files: sections.map((entry) => ({
|
|
320
|
+
oldPath: entry.oldPath,
|
|
321
|
+
newPath: entry.newPath,
|
|
322
|
+
status: statusOf(entry),
|
|
323
|
+
firstLine: entry.firstLine,
|
|
324
|
+
lastLine: entry.lastLine,
|
|
325
|
+
added: entry.added,
|
|
326
|
+
removed: entry.removed,
|
|
327
|
+
hunks: entry.hunks
|
|
328
|
+
}))
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function isContent(line: string): boolean {
|
|
333
|
+
return line.startsWith('+') || line.startsWith('-') || line.startsWith(' ');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/* A line under a hunk header; an empty line is a context line whose space an editor trimmed. */
|
|
337
|
+
function contentLine(line: string, hunk: OpenHunk, section: Section): PatchLayoutLine {
|
|
338
|
+
if (line.startsWith('\\')) {
|
|
339
|
+
return NO_NEWLINE;
|
|
340
|
+
}
|
|
341
|
+
const kind = line.startsWith('+') ? 'added' : line.startsWith('-') ? 'removed' : 'context';
|
|
342
|
+
const takesOld = kind !== 'added';
|
|
343
|
+
const takesNew = kind !== 'removed';
|
|
344
|
+
if ((takesOld && hunk.oldLeft === 0) || (takesNew && hunk.newLeft === 0)) {
|
|
345
|
+
hunk.layout.consistent = false;
|
|
346
|
+
}
|
|
347
|
+
if (kind === 'added') {
|
|
348
|
+
section.added++;
|
|
349
|
+
} else if (kind === 'removed') {
|
|
350
|
+
section.removed++;
|
|
351
|
+
}
|
|
352
|
+
const oldNumber = takesOld ? hunk.oldNumber++ : null;
|
|
353
|
+
const newNumber = takesNew ? hunk.newNumber++ : null;
|
|
354
|
+
hunk.oldLeft = takesOld ? Math.max(0, hunk.oldLeft - 1) : hunk.oldLeft;
|
|
355
|
+
hunk.newLeft = takesNew ? Math.max(0, hunk.newLeft - 1) : hunk.newLeft;
|
|
356
|
+
return { role: kind, old: oldNumber, new: newNumber };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function nameSides(section: Section, oldLine: string, newLine: string): void {
|
|
360
|
+
const oldName = pathOf(oldLine.slice(4));
|
|
361
|
+
const newName = pathOf(newLine.slice(4));
|
|
362
|
+
const prefixed = section.git ? section.prefixed : (oldName?.startsWith('a/') ?? false) && (newName?.startsWith('b/') ?? false);
|
|
363
|
+
section.oldPath = prefixed ? stripPrefix(oldName, 'a/') : oldName;
|
|
364
|
+
section.newPath = prefixed ? stripPrefix(newName, 'b/') : newName;
|
|
365
|
+
section.created ||= oldName === null;
|
|
366
|
+
section.deleted ||= newName === null;
|
|
367
|
+
section.named = true;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function readGitHeader(section: Section, line: string): void {
|
|
371
|
+
if (line.startsWith('new file mode ')) {
|
|
372
|
+
section.created = true;
|
|
373
|
+
} else if (line.startsWith('deleted file mode ')) {
|
|
374
|
+
section.deleted = true;
|
|
375
|
+
} else if (line.startsWith('rename from ')) {
|
|
376
|
+
section.renamed = true;
|
|
377
|
+
section.oldPath = pathOf(line.slice('rename from '.length));
|
|
378
|
+
} else if (line.startsWith('rename to ')) {
|
|
379
|
+
section.renamed = true;
|
|
380
|
+
section.newPath = pathOf(line.slice('rename to '.length));
|
|
381
|
+
} else if (line.startsWith('copy from ')) {
|
|
382
|
+
section.copied = true;
|
|
383
|
+
section.oldPath = pathOf(line.slice('copy from '.length));
|
|
384
|
+
} else if (line.startsWith('copy to ')) {
|
|
385
|
+
section.copied = true;
|
|
386
|
+
section.newPath = pathOf(line.slice('copy to '.length));
|
|
387
|
+
}
|
|
388
|
+
}
|
package/src/peek.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { PEEK_READ_FILES, definitionSnippetOf, peekFilesOf, snippetOf, type Peek
|
|
|
6
6
|
import type { PeekView } from './popups.ts';
|
|
7
7
|
import { isShortcut } from './shortcut-keys.ts';
|
|
8
8
|
import { messageOf } from './error-message.ts';
|
|
9
|
+
import { realTimers, revealRow, type Timers } from './timers.ts';
|
|
9
10
|
|
|
10
11
|
const TOAST_ID = 'language-peek';
|
|
11
12
|
/* The block as the panel draws it: a hatched band, the header, eight lines of code and a hatched band, until the editor has measured it. */
|
|
@@ -29,6 +30,7 @@ function placeKey(location: Location): string {
|
|
|
29
30
|
*/
|
|
30
31
|
export class PeekFeature {
|
|
31
32
|
private readonly language: EditorLanguage;
|
|
33
|
+
private readonly timers: Timers;
|
|
32
34
|
private kind: PeekKind = 'references';
|
|
33
35
|
/* The whole declaration of each place of a definition peek, where the server gave it. */
|
|
34
36
|
private targets = new Map<string, Range>();
|
|
@@ -38,8 +40,9 @@ export class PeekFeature {
|
|
|
38
40
|
private line = 0;
|
|
39
41
|
private token = 0;
|
|
40
42
|
|
|
41
|
-
constructor(language: EditorLanguage) {
|
|
43
|
+
constructor(language: EditorLanguage, timers: Timers = realTimers) {
|
|
42
44
|
this.language = language;
|
|
45
|
+
this.timers = timers;
|
|
43
46
|
const { editor } = language;
|
|
44
47
|
const offs = [
|
|
45
48
|
editor.onKeyDown((event) => {
|
|
@@ -126,6 +129,7 @@ export class PeekFeature {
|
|
|
126
129
|
? files[0]!.places[0]!
|
|
127
130
|
: (files.flatMap((file) => file.places).find((place) => !(place.location.uri === uri && place.line === position.line)) ?? files[0]!.places[0]!);
|
|
128
131
|
this.line = position.line;
|
|
132
|
+
this.language.callHierarchy.close();
|
|
129
133
|
this.language.popups.setState({ peek: { kind, container: null, files, active: first.id, collapsed: [], count: unique.length, preview: null } });
|
|
130
134
|
this.select(first.id);
|
|
131
135
|
// The editor draws a row once per id, so a peek opened over another needs an id of its own to get a container.
|
|
@@ -200,6 +204,7 @@ export class PeekFeature {
|
|
|
200
204
|
const view = this.language.popups.getState().peek;
|
|
201
205
|
if (view !== null && view.container !== container) {
|
|
202
206
|
this.language.popups.setState({ peek: { ...view, container } });
|
|
207
|
+
revealRow(this.timers, container);
|
|
203
208
|
}
|
|
204
209
|
}
|
|
205
210
|
|
package/src/pick.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface PickSpec {
|
|
|
12
12
|
/* The row the list opens on; the first one without it. */
|
|
13
13
|
readonly active?: string;
|
|
14
14
|
accept(id: string): void;
|
|
15
|
+
/* A list can hand Tab back to its surrounding workflow after it closes. */
|
|
16
|
+
onTab?(backwards: boolean): void;
|
|
15
17
|
/*
|
|
16
18
|
* Asked before the list closes on a row. A reason it cannot be taken keeps the list open with the reason
|
|
17
19
|
* under it, the way a rename keeps its input; null lets the list close and `accept` run.
|
|
@@ -154,9 +156,18 @@ export class PickFeature {
|
|
|
154
156
|
this.activate(this.ids[this.ids.length - 1]!);
|
|
155
157
|
return true;
|
|
156
158
|
case 'Enter':
|
|
157
|
-
case 'Tab':
|
|
158
159
|
this.choose(this.active);
|
|
159
160
|
return true;
|
|
161
|
+
case 'Tab': {
|
|
162
|
+
const spec = this.spec;
|
|
163
|
+
if (spec.onTab !== undefined) {
|
|
164
|
+
this.close();
|
|
165
|
+
spec.onTab(event.shiftKey);
|
|
166
|
+
} else {
|
|
167
|
+
this.choose(this.active);
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
160
171
|
case 'Escape':
|
|
161
172
|
this.close();
|
|
162
173
|
return true;
|
package/src/popups.ts
CHANGED
|
@@ -4,10 +4,12 @@ import type { Location } from '@adecore/lsp';
|
|
|
4
4
|
import type { CodeAuthorship } from './code-authors.ts';
|
|
5
5
|
import type { Problem } from './diagnostics-model.ts';
|
|
6
6
|
import type { PeekFile, PeekSnippet } from './peek-model.ts';
|
|
7
|
+
import type { CallDirection, CallRow } from './call-hierarchy-model.ts';
|
|
7
8
|
import type { ActionEntry } from './code-actions-model.ts';
|
|
8
9
|
import type { SymbolEntry } from './symbol-picker-model.ts';
|
|
9
10
|
import type { HoverText } from './hover-content.ts';
|
|
10
11
|
import type { SignatureViewModel } from './signature-model.ts';
|
|
12
|
+
import type { ResourceOperationView } from './workspace-edit.ts';
|
|
11
13
|
|
|
12
14
|
/* What a hover card shows for the character under the pointer. */
|
|
13
15
|
export interface HoverView {
|
|
@@ -29,6 +31,8 @@ export interface HoverInfo {
|
|
|
29
31
|
readonly word: string;
|
|
30
32
|
/* How many other places use the symbol, once a server has counted them. */
|
|
31
33
|
readonly references: number | null;
|
|
34
|
+
/* Only the text: no footer for the definition and the references, which a card made of someone else's words has none of. */
|
|
35
|
+
readonly plain?: boolean;
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
/* One line of the suggestions list. */
|
|
@@ -132,6 +136,8 @@ export interface RenameView {
|
|
|
132
136
|
readonly error: string | null;
|
|
133
137
|
readonly name: string;
|
|
134
138
|
readonly files: readonly RenameFileView[];
|
|
139
|
+
/* The files the rename makes, moves or deletes, listed in the preview next to the lines. */
|
|
140
|
+
readonly operations?: readonly ResourceOperationView[];
|
|
135
141
|
/* Names the host suggests from how the symbol is used. They fill the input when chosen and never apply by themselves. */
|
|
136
142
|
readonly suggestions: readonly string[];
|
|
137
143
|
}
|
|
@@ -150,6 +156,21 @@ export interface PeekView {
|
|
|
150
156
|
readonly preview: (PeekSnippet & { readonly uri: string }) | null;
|
|
151
157
|
}
|
|
152
158
|
|
|
159
|
+
/* The callers of a symbol, or the symbols it calls, as a tree between the lines of the file with the code of the active call. */
|
|
160
|
+
export interface CallHierarchyView {
|
|
161
|
+
readonly direction: CallDirection;
|
|
162
|
+
/* The row the editor draws it in, which the editor makes again when it scrolls back into view. */
|
|
163
|
+
readonly container: HTMLElement | null;
|
|
164
|
+
/* The symbol the hierarchy was opened on. */
|
|
165
|
+
readonly name: string;
|
|
166
|
+
readonly rows: readonly CallRow[];
|
|
167
|
+
readonly active: string;
|
|
168
|
+
/* The call site of the active row that is shown, from zero, among `sites`. */
|
|
169
|
+
readonly site: number;
|
|
170
|
+
readonly sites: number;
|
|
171
|
+
readonly preview: (PeekSnippet & { readonly uri: string }) | null;
|
|
172
|
+
}
|
|
173
|
+
|
|
153
174
|
/* The picker that jumps to a symbol of the file, over the symbols the servers know in it. */
|
|
154
175
|
export interface SymbolsView {
|
|
155
176
|
/* The file's name, for the placeholder. */
|
|
@@ -182,6 +203,8 @@ export interface PopupState {
|
|
|
182
203
|
readonly pick: PickView | null;
|
|
183
204
|
readonly rename: RenameView | null;
|
|
184
205
|
readonly peek: PeekView | null;
|
|
206
|
+
/* Optional so a host that builds a `PopupState` literal from before the call hierarchy still fits; absent reads as closed. */
|
|
207
|
+
readonly callHierarchy?: CallHierarchyView | null;
|
|
185
208
|
readonly symbols: SymbolsView | null;
|
|
186
209
|
readonly menu: MenuView | null;
|
|
187
210
|
readonly authors: AuthorsView | null;
|
|
@@ -197,6 +220,7 @@ export function createPopupStore(): PopupStore {
|
|
|
197
220
|
pick: null,
|
|
198
221
|
rename: null,
|
|
199
222
|
peek: null,
|
|
223
|
+
callHierarchy: null,
|
|
200
224
|
symbols: null,
|
|
201
225
|
menu: null,
|
|
202
226
|
authors: null
|
package/src/project-language.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { disposeSnippetTransforms } from './snippet-transform-queue.ts';
|
|
1
2
|
import i18next, { type i18n } from 'i18next';
|
|
2
|
-
import { fileUriToPath, type ApplyWorkspaceEditResult, type ContentChange, type LanguageService, type WorkspaceEdit } from '@adecore/lsp';
|
|
3
|
+
import { fileUriToPath, pathToFileUri, type ApplyWorkspaceEditResult, type ContentChange, type LanguageService, type WorkspaceEdit } from '@adecore/lsp';
|
|
3
4
|
import type { Editor, EditorPosition } from '@adecore/editor';
|
|
4
5
|
import { NavigationHistory, type Place } from './navigation-history.ts';
|
|
5
6
|
import { ProjectProblems } from './project-problems.ts';
|
|
6
7
|
import { applyWorkspaceEdit, type ProjectFiles } from './workspace-edit.ts';
|
|
7
8
|
import type { LanguageHost } from './host-types.ts';
|
|
8
9
|
import { relativeTo } from './paths.ts';
|
|
10
|
+
import { PANEL_BODY_HEIGHT } from './panel-size.ts';
|
|
9
11
|
|
|
10
12
|
export interface LanguageDocumentHandle {
|
|
11
13
|
readonly uri: string;
|
|
@@ -73,6 +75,17 @@ interface Holder {
|
|
|
73
75
|
languageId: string;
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
/* A file or folder for a file operation notification: a bare path is a file. */
|
|
79
|
+
export type FileOperationTarget = string | { readonly path: string; readonly directory?: boolean };
|
|
80
|
+
|
|
81
|
+
function filesOf(targets: readonly FileOperationTarget[]): Array<{ uri: string; directory: boolean }> {
|
|
82
|
+
return targets.map((target) => {
|
|
83
|
+
const { path, directory } =
|
|
84
|
+
typeof target === 'string' ? { path: target, directory: false } : { path: target.path, directory: target.directory === true };
|
|
85
|
+
return { uri: pathToFileUri(path), directory };
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
76
89
|
/*
|
|
77
90
|
* A shared language service and document ownership for the editors of a project. Two editors on the same
|
|
78
91
|
* file hold one document, and only the first one sends changes, since both would report the same edit.
|
|
@@ -86,6 +99,9 @@ export class ProjectLanguage {
|
|
|
86
99
|
readonly history = new NavigationHistory();
|
|
87
100
|
private readonly holders = new Map<string, Holder>();
|
|
88
101
|
private readonly files: ProjectFiles | null;
|
|
102
|
+
/* The body height of the peek and call hierarchy panels, shared by the editors of the project. */
|
|
103
|
+
panelBodyHeight: number;
|
|
104
|
+
private readonly panelListeners = new Set<(height: number) => void>();
|
|
89
105
|
private pendingCaret: Place | null = null;
|
|
90
106
|
private lastPlace: Place | null = null;
|
|
91
107
|
private disposed = false;
|
|
@@ -96,9 +112,25 @@ export class ProjectLanguage {
|
|
|
96
112
|
this.i18n = host.i18n ?? i18next;
|
|
97
113
|
this.folder = host.folder ?? '';
|
|
98
114
|
this.files = host.files ?? null;
|
|
115
|
+
this.panelBodyHeight = host.panelBodyHeight ?? PANEL_BODY_HEIGHT;
|
|
99
116
|
this.problems = new ProjectProblems(service, (uri) => this.pathOfLocation(uri) ?? uri);
|
|
100
117
|
}
|
|
101
118
|
|
|
119
|
+
/* Open panels of other editors of the project follow a resize. */
|
|
120
|
+
onPanelBodyChange(listener: (height: number) => void): () => void {
|
|
121
|
+
this.panelListeners.add(listener);
|
|
122
|
+
return () => this.panelListeners.delete(listener);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
resizePanelBody(height: number): void {
|
|
126
|
+
if (height === this.panelBodyHeight) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.panelBodyHeight = height;
|
|
130
|
+
this.panelListeners.forEach((listener) => listener(height));
|
|
131
|
+
this.host.onPanelResize?.(height);
|
|
132
|
+
}
|
|
133
|
+
|
|
102
134
|
pathOfLocation(uri: string): string | null {
|
|
103
135
|
if (this.host.pathOfUri !== undefined) {
|
|
104
136
|
return this.host.pathOfUri(uri);
|
|
@@ -109,7 +141,35 @@ export class ProjectLanguage {
|
|
|
109
141
|
|
|
110
142
|
/* Makes an edit of a language server: an open document takes it as one undo step and any other file gets an unsaved draft. */
|
|
111
143
|
applyWorkspaceEdit(edit: WorkspaceEdit): Promise<ApplyWorkspaceEditResult> {
|
|
112
|
-
|
|
144
|
+
const { service } = this;
|
|
145
|
+
return applyWorkspaceEdit(edit, {
|
|
146
|
+
editorOf: (uri) => this.holders.get(uri)?.editors[0],
|
|
147
|
+
files: this.files,
|
|
148
|
+
...(service.versionOf === undefined ? {} : { versionOf: (uri: string) => service.versionOf!(uri) })
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/*
|
|
153
|
+
* What the servers want changed before files are made, for a host that makes them outside a language server's
|
|
154
|
+
* edit: apply the edit with `applyWorkspaceEdit`, then make the files. Null when no server has anything to say.
|
|
155
|
+
*/
|
|
156
|
+
willCreateFiles(targets: readonly FileOperationTarget[]): Promise<WorkspaceEdit | null> {
|
|
157
|
+
return this.service.willCreateFiles?.(filesOf(targets)) ?? Promise.resolve(null);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Tells the servers that files were made, once they are. */
|
|
161
|
+
didCreateFiles(targets: readonly FileOperationTarget[]): Promise<void> {
|
|
162
|
+
return this.service.didCreateFiles?.(filesOf(targets)) ?? Promise.resolve();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* What the servers want changed before files are deleted, such as the references to them; null when no server has anything to say. */
|
|
166
|
+
willDeleteFiles(targets: readonly FileOperationTarget[]): Promise<WorkspaceEdit | null> {
|
|
167
|
+
return this.service.willDeleteFiles?.(filesOf(targets)) ?? Promise.resolve(null);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* Tells the servers that files were deleted, once they are. */
|
|
171
|
+
didDeleteFiles(targets: readonly FileOperationTarget[]): Promise<void> {
|
|
172
|
+
return this.service.didDeleteFiles?.(filesOf(targets)) ?? Promise.resolve();
|
|
113
173
|
}
|
|
114
174
|
|
|
115
175
|
/* The text of a file as it stands now: an open document's, else a draft's or the machine's; null when it is no text file. */
|
|
@@ -211,6 +271,7 @@ export class ProjectLanguage {
|
|
|
211
271
|
return;
|
|
212
272
|
}
|
|
213
273
|
this.disposed = true;
|
|
274
|
+
disposeSnippetTransforms(this);
|
|
214
275
|
for (const [uri, holder] of this.holders) {
|
|
215
276
|
holder.sync.dispose();
|
|
216
277
|
void holder.sync.ready
|
package/src/refresher.ts
CHANGED
|
@@ -41,6 +41,12 @@ export class Refresher {
|
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/* Drops the wait and the ask in flight, and keeps the refresher usable. */
|
|
45
|
+
cancel(): void {
|
|
46
|
+
this.timers.clear(this.timer);
|
|
47
|
+
this.controller?.abort();
|
|
48
|
+
}
|
|
49
|
+
|
|
44
50
|
dispose(): void {
|
|
45
51
|
this.disposed = true;
|
|
46
52
|
this.timers.clear(this.timer);
|
package/src/rename-model.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { EditorPosition, EditorRange } from '@adecore/editor';
|
|
2
2
|
import type { Location, PrepareRenameResult, TextEdit, WorkspaceEdit } from '@adecore/lsp';
|
|
3
3
|
import { isIdentifierCharacter } from './completion-model.ts';
|
|
4
|
-
import {
|
|
4
|
+
import { textEntriesOf } from './workspace-edit.ts';
|
|
5
5
|
|
|
6
6
|
/* The identifier the caret is in or touches; null where there is none. */
|
|
7
7
|
export function wordRangeAt(lineText: string, position: EditorPosition): EditorRange | null {
|
|
@@ -115,14 +115,10 @@ export function renameRowsOf(text: string, edits: readonly TextEdit[]): RenameRo
|
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
/*
|
|
119
|
-
export async function renamePreviewOf(edit: WorkspaceEdit, textOf: (uri: string) => Promise<string | null>): Promise<RenameFile[]
|
|
120
|
-
const entries = entriesOf(edit);
|
|
121
|
-
if (entries === null) {
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
118
|
+
/* The lines of the files an edit edits; the files it makes, moves or deletes are listed by `resourceOperationsOf`. */
|
|
119
|
+
export async function renamePreviewOf(edit: WorkspaceEdit, textOf: (uri: string) => Promise<string | null>): Promise<RenameFile[]> {
|
|
124
120
|
const files: RenameFile[] = [];
|
|
125
|
-
for (const [uri, list] of
|
|
121
|
+
for (const [uri, list] of textEntriesOf(edit)) {
|
|
126
122
|
const text = await textOf(uri);
|
|
127
123
|
files.push({ uri, rows: text === null ? [] : renameRowsOf(text, list.flat()) });
|
|
128
124
|
}
|