@adecore/editor-react 0.22.1 → 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,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
|
@@ -126,6 +126,7 @@ export class PeekFeature {
|
|
|
126
126
|
? files[0]!.places[0]!
|
|
127
127
|
: (files.flatMap((file) => file.places).find((place) => !(place.location.uri === uri && place.line === position.line)) ?? files[0]!.places[0]!);
|
|
128
128
|
this.line = position.line;
|
|
129
|
+
this.language.callHierarchy.close();
|
|
129
130
|
this.language.popups.setState({ peek: { kind, container: null, files, active: first.id, collapsed: [], count: unique.length, preview: null } });
|
|
130
131
|
this.select(first.id);
|
|
131
132
|
// The editor draws a row once per id, so a peek opened over another needs an id of its own to get a container.
|
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,5 +1,6 @@
|
|
|
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';
|
|
@@ -73,6 +74,17 @@ interface Holder {
|
|
|
73
74
|
languageId: string;
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
/* A file or folder for a file operation notification: a bare path is a file. */
|
|
78
|
+
export type FileOperationTarget = string | { readonly path: string; readonly directory?: boolean };
|
|
79
|
+
|
|
80
|
+
function filesOf(targets: readonly FileOperationTarget[]): Array<{ uri: string; directory: boolean }> {
|
|
81
|
+
return targets.map((target) => {
|
|
82
|
+
const { path, directory } =
|
|
83
|
+
typeof target === 'string' ? { path: target, directory: false } : { path: target.path, directory: target.directory === true };
|
|
84
|
+
return { uri: pathToFileUri(path), directory };
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
76
88
|
/*
|
|
77
89
|
* A shared language service and document ownership for the editors of a project. Two editors on the same
|
|
78
90
|
* file hold one document, and only the first one sends changes, since both would report the same edit.
|
|
@@ -109,7 +121,35 @@ export class ProjectLanguage {
|
|
|
109
121
|
|
|
110
122
|
/* 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
123
|
applyWorkspaceEdit(edit: WorkspaceEdit): Promise<ApplyWorkspaceEditResult> {
|
|
112
|
-
|
|
124
|
+
const { service } = this;
|
|
125
|
+
return applyWorkspaceEdit(edit, {
|
|
126
|
+
editorOf: (uri) => this.holders.get(uri)?.editors[0],
|
|
127
|
+
files: this.files,
|
|
128
|
+
...(service.versionOf === undefined ? {} : { versionOf: (uri: string) => service.versionOf!(uri) })
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/*
|
|
133
|
+
* What the servers want changed before files are made, for a host that makes them outside a language server's
|
|
134
|
+
* edit: apply the edit with `applyWorkspaceEdit`, then make the files. Null when no server has anything to say.
|
|
135
|
+
*/
|
|
136
|
+
willCreateFiles(targets: readonly FileOperationTarget[]): Promise<WorkspaceEdit | null> {
|
|
137
|
+
return this.service.willCreateFiles?.(filesOf(targets)) ?? Promise.resolve(null);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Tells the servers that files were made, once they are. */
|
|
141
|
+
didCreateFiles(targets: readonly FileOperationTarget[]): Promise<void> {
|
|
142
|
+
return this.service.didCreateFiles?.(filesOf(targets)) ?? Promise.resolve();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* What the servers want changed before files are deleted, such as the references to them; null when no server has anything to say. */
|
|
146
|
+
willDeleteFiles(targets: readonly FileOperationTarget[]): Promise<WorkspaceEdit | null> {
|
|
147
|
+
return this.service.willDeleteFiles?.(filesOf(targets)) ?? Promise.resolve(null);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* Tells the servers that files were deleted, once they are. */
|
|
151
|
+
didDeleteFiles(targets: readonly FileOperationTarget[]): Promise<void> {
|
|
152
|
+
return this.service.didDeleteFiles?.(filesOf(targets)) ?? Promise.resolve();
|
|
113
153
|
}
|
|
114
154
|
|
|
115
155
|
/* 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 +251,7 @@ export class ProjectLanguage {
|
|
|
211
251
|
return;
|
|
212
252
|
}
|
|
213
253
|
this.disposed = true;
|
|
254
|
+
disposeSnippetTransforms(this);
|
|
214
255
|
for (const [uri, holder] of this.holders) {
|
|
215
256
|
holder.sync.dispose();
|
|
216
257
|
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
|
}
|
package/src/rename.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { lineTextOf } from './line-text.ts';
|
|
|
5
5
|
import type { RenameView } from './popups.ts';
|
|
6
6
|
import { occurrencesOf, renamePreviewOf, renameTargetOf, wordRangeAt, type RenameTarget } from './rename-model.ts';
|
|
7
7
|
import { isShortcut } from './shortcut-keys.ts';
|
|
8
|
+
import { resourceOperationsOf } from './workspace-edit.ts';
|
|
8
9
|
import { messageOf } from './error-message.ts';
|
|
9
10
|
|
|
10
11
|
const METHOD = 'textDocument/rename';
|
|
@@ -86,7 +87,7 @@ export class RenameFeature {
|
|
|
86
87
|
}
|
|
87
88
|
const session: Session = { target, original: editor.textInRange(target.range), edit: null, names: new AbortController() };
|
|
88
89
|
this.session = session;
|
|
89
|
-
this.publish({ phase: 'input', busy: false, name: target.placeholder, files: [], occurrences: null, suggestions: [] });
|
|
90
|
+
this.publish({ phase: 'input', busy: false, name: target.placeholder, files: [], operations: [], occurrences: null, suggestions: [] });
|
|
90
91
|
void this.light(session).then((locations) => this.suggest(session, locations));
|
|
91
92
|
}
|
|
92
93
|
|
|
@@ -121,12 +122,7 @@ export class RenameFeature {
|
|
|
121
122
|
if (this.session !== session) {
|
|
122
123
|
return;
|
|
123
124
|
}
|
|
124
|
-
|
|
125
|
-
// An edit that renames files too has no list of lines to show, so it is applied or refused as a whole.
|
|
126
|
-
await this.apply(session);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
this.publish({ phase: 'preview', busy: false, name: next, files });
|
|
125
|
+
this.publish({ phase: 'preview', busy: false, name: next, files, operations: resourceOperationsOf(edit) });
|
|
130
126
|
} catch (error) {
|
|
131
127
|
if (this.session === session) {
|
|
132
128
|
this.fail(error instanceof StaleResultError ? this.say('stale') : messageOf(error));
|
|
@@ -172,7 +168,7 @@ export class RenameFeature {
|
|
|
172
168
|
private fail(message: string): void {
|
|
173
169
|
if (this.session !== null) {
|
|
174
170
|
this.session.edit = null;
|
|
175
|
-
this.publish({ phase: 'input', busy: false, files: [], error: this.say('failed', { message }) });
|
|
171
|
+
this.publish({ phase: 'input', busy: false, files: [], operations: [], error: this.say('failed', { message }) });
|
|
176
172
|
}
|
|
177
173
|
}
|
|
178
174
|
|
|
@@ -271,6 +267,7 @@ export class RenameFeature {
|
|
|
271
267
|
error: null,
|
|
272
268
|
name: session.target.placeholder,
|
|
273
269
|
files: [],
|
|
270
|
+
operations: [],
|
|
274
271
|
suggestions: [],
|
|
275
272
|
...current,
|
|
276
273
|
...patch
|