@adecore/editor-react 0.31.12 → 0.33.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/AnchoredPopup.js +73 -19
- package/dist/AttributionCard.js +162 -4
- package/dist/BaselineStatus.js +62 -8
- package/dist/CallHierarchyPanel.js +977 -60
- package/dist/ChangeReview.js +76 -25
- package/dist/CodeAuthorsCard.js +314 -14
- package/dist/CompletionPopup.js +490 -83
- package/dist/DiffView.js +548 -181
- package/dist/EditorContextMenu.js +352 -34
- package/dist/EditorView.js +276 -99
- package/dist/FindReplace.js +525 -67
- package/dist/HoverCard.js +378 -38
- package/dist/HoverSections.js +367 -56
- package/dist/LanguagePopups.js +95 -34
- package/dist/Markdown.js +41 -6
- package/dist/PanelResizeHandle.js +199 -66
- package/dist/PathText.js +49 -7
- package/dist/PeekPanel.js +675 -72
- package/dist/PickPopup.js +304 -34
- package/dist/RenameCard.js +679 -85
- package/dist/RenderedDocs.js +329 -58
- package/dist/SignatureCard.js +91 -7
- package/dist/SplitDiffView.js +344 -254
- package/dist/SymbolPicker.js +440 -85
- package/dist/call-hierarchy-model.js +102 -68
- package/dist/call-hierarchy.js +458 -359
- package/dist/code-actions-model.js +88 -76
- package/dist/code-actions.js +372 -283
- package/dist/code-authors.js +64 -55
- package/dist/code-vision-model.js +134 -127
- package/dist/code-vision.js +224 -206
- package/dist/completion-call.js +94 -67
- package/dist/completion-model.js +334 -244
- package/dist/completion.js +840 -649
- package/dist/conflict-model.js +35 -29
- package/dist/context-menu-rows.js +42 -17
- package/dist/context-menu.js +64 -46
- package/dist/definition-link.js +88 -85
- package/dist/diagnostics-model.js +74 -55
- package/dist/diagnostics.js +121 -100
- package/dist/diff-expansion.js +38 -35
- package/dist/diff-overlays.js +144 -22
- package/dist/diff-syntax.js +65 -42
- package/dist/diff-wordmarks.js +26 -23
- package/dist/doc-comment-model.js +265 -216
- package/dist/editor-language.js +177 -152
- package/dist/error-message.js +2 -2
- package/dist/find-query.js +49 -36
- package/dist/folding.js +81 -62
- package/dist/format-on-type.js +86 -67
- package/dist/highlight-layers.js +19 -20
- package/dist/highlights.js +105 -83
- package/dist/history.js +44 -38
- package/dist/holder.js +18 -18
- package/dist/host-types.js +1 -1
- package/dist/hover-content.js +167 -129
- package/dist/hover.js +285 -245
- package/dist/index.js +10 -10
- package/dist/inlay-hints.js +369 -324
- package/dist/key-priority.js +34 -31
- package/dist/language-ids.js +45 -42
- package/dist/line-edits.js +74 -28
- package/dist/line-text.js +18 -3
- package/dist/locales/en.json +38 -38
- package/dist/locales/nl.json +41 -41
- package/dist/models.js +1 -1
- package/dist/navigation-history.js +40 -41
- package/dist/navigation.js +141 -123
- package/dist/panel-size.js +8 -8
- package/dist/patch-decorations.js +236 -160
- package/dist/patch-layout.js +331 -262
- package/dist/paths.js +3 -3
- package/dist/peek-model.js +86 -69
- package/dist/peek.js +254 -192
- package/dist/pick.js +164 -157
- package/dist/popup-focus.js +5 -5
- package/dist/popup-placement.js +36 -21
- package/dist/popups.js +13 -13
- package/dist/project-language.js +228 -211
- package/dist/project-problems.js +81 -69
- package/dist/proposal-model.js +163 -126
- package/dist/recent-choices.js +25 -25
- package/dist/refresher.js +44 -49
- package/dist/rename-model.js +113 -113
- package/dist/rename.js +321 -233
- package/dist/rendered-docs.d.ts +4 -2
- package/dist/rendered-docs.js +384 -318
- package/dist/rendering-context.js +5 -3
- package/dist/rendering.js +22 -4
- package/dist/review-group.js +42 -39
- package/dist/row-host.d.ts +4 -2
- package/dist/row-host.js +90 -78
- package/dist/selection-ranges.js +21 -17
- package/dist/semantic-delta.js +22 -19
- package/dist/semantic-model.js +86 -73
- package/dist/semantic-tokens.js +201 -162
- package/dist/shortcut-keys.js +16 -10
- package/dist/signature-model.js +34 -28
- package/dist/signature.js +177 -129
- package/dist/snippet-field-transforms.js +118 -106
- package/dist/snippet-indent.js +48 -43
- package/dist/snippet-range-order.js +11 -11
- package/dist/snippet-session.js +703 -667
- package/dist/snippet-transform-client.js +59 -59
- package/dist/snippet-transform-format.js +285 -263
- package/dist/snippet-transform-protocol.js +1 -1
- package/dist/snippet-transform-queue.js +163 -153
- package/dist/snippet-transform-worker.js +42 -35
- package/dist/snippet-variable-names.js +1 -36
- package/dist/snippet-variables.js +53 -63
- package/dist/snippet.js +477 -405
- package/dist/split-alignment.js +47 -35
- package/dist/split-scroll.js +42 -40
- package/dist/symbol-links.js +33 -25
- package/dist/symbol-picker-model.js +126 -116
- package/dist/symbol-picker.js +98 -59
- package/dist/symbols.js +117 -88
- package/dist/testing.js +248 -165
- package/dist/timers.js +35 -30
- package/dist/use-panel-body.js +49 -8
- package/dist/use-problem-counts.js +29 -15
- package/dist/view-state.js +54 -33
- package/dist/word-diff.js +18 -18
- package/dist/workspace-edit.js +300 -249
- package/package.json +6 -6
- package/src/CallHierarchyPanel.tsx +2 -2
- package/src/DiffView.tsx +3 -3
- package/src/FindReplace.tsx +2 -2
- package/src/LanguagePopups.tsx +2 -0
- package/src/PeekPanel.tsx +3 -3
- package/src/RenderedDocs.tsx +9 -10
- package/src/SymbolPicker.tsx +2 -2
- package/src/diff-overlays.tsx +6 -4
- package/src/locales/en.json +38 -38
- package/src/locales/nl.json +41 -41
- package/src/rendered-docs.ts +14 -3
- package/src/row-host.ts +14 -3
package/dist/workspace-edit.js
CHANGED
|
@@ -1,159 +1,178 @@
|
|
|
1
1
|
import { fileUriToPath, planWorkspaceOperations } from '@adecore/lsp';
|
|
2
2
|
import { messageOf } from './error-message.js';
|
|
3
3
|
function refused(failureReason) {
|
|
4
|
-
|
|
4
|
+
return {
|
|
5
|
+
applied: false,
|
|
6
|
+
failureReason
|
|
7
|
+
};
|
|
5
8
|
}
|
|
6
9
|
/* The text edits of an edit per document, as the entries a document has in it, whatever else the edit does. */
|
|
7
10
|
export function textEntriesOf(edit) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
return entries;
|
|
17
|
-
}
|
|
18
|
-
for (const change of edit.documentChanges) {
|
|
19
|
-
if ('textDocument' in change) {
|
|
20
|
-
add(change.textDocument.uri, change.edits);
|
|
21
|
-
}
|
|
11
|
+
const entries = new Map();
|
|
12
|
+
const add = (uri, edits) => {
|
|
13
|
+
entries.set(uri, [...(entries.get(uri) ?? []), edits]);
|
|
14
|
+
};
|
|
15
|
+
if (edit.documentChanges === undefined) {
|
|
16
|
+
for (const [uri, edits] of Object.entries(edit.changes ?? {})) {
|
|
17
|
+
add(uri, edits);
|
|
22
18
|
}
|
|
23
19
|
return entries;
|
|
20
|
+
}
|
|
21
|
+
for (const change of edit.documentChanges) {
|
|
22
|
+
if ('textDocument' in change) {
|
|
23
|
+
add(change.textDocument.uri, change.edits);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return entries;
|
|
24
27
|
}
|
|
25
28
|
/* The text edits of an edit per document, as the entries a document has in it; null when it deletes a file. */
|
|
26
29
|
export function entriesOf(edit) {
|
|
27
|
-
|
|
30
|
+
return (edit.documentChanges ?? []).some(change => 'kind' in change && change.kind === 'delete') ? null : textEntriesOf(edit);
|
|
28
31
|
}
|
|
29
32
|
/* The files an edit moves, which a preview of lines cannot show. */
|
|
30
33
|
export function renamesOf(edit) {
|
|
31
|
-
|
|
34
|
+
return (edit.documentChanges ?? []).filter(change => 'kind' in change && change.kind === 'rename');
|
|
32
35
|
}
|
|
33
36
|
/* Every file an edit makes, moves or deletes, in the order it says. */
|
|
34
37
|
export function resourceOperationsOf(edit) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
const operations = [];
|
|
39
|
+
for (const change of edit.documentChanges ?? []) {
|
|
40
|
+
if ('textDocument' in change) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
operations.push(change.kind === 'rename' ? {
|
|
44
|
+
kind: 'rename',
|
|
45
|
+
uri: change.oldUri,
|
|
46
|
+
newUri: change.newUri
|
|
47
|
+
} : {
|
|
48
|
+
kind: change.kind,
|
|
49
|
+
uri: change.uri
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return operations;
|
|
43
53
|
}
|
|
44
54
|
function contentChangeOf(edit) {
|
|
45
|
-
|
|
55
|
+
return {
|
|
56
|
+
range: edit.range,
|
|
57
|
+
text: edit.newText
|
|
58
|
+
};
|
|
46
59
|
}
|
|
47
60
|
/* The resource operations a project has a way to carry out; pass it as the session's `resourceOperations` so servers only ask for what can be done. */
|
|
48
61
|
export function supportedResourceOperations(files) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
const operations = [];
|
|
63
|
+
if (files?.create !== undefined) {
|
|
64
|
+
operations.push('create');
|
|
65
|
+
}
|
|
66
|
+
if (files?.rename !== undefined) {
|
|
67
|
+
operations.push('rename');
|
|
68
|
+
}
|
|
69
|
+
if (files?.delete !== undefined) {
|
|
70
|
+
operations.push('delete');
|
|
71
|
+
}
|
|
72
|
+
return operations;
|
|
60
73
|
}
|
|
61
74
|
function within(uri, folder) {
|
|
62
|
-
|
|
75
|
+
return uri.startsWith(`${folder}/`);
|
|
63
76
|
}
|
|
64
77
|
/* Where a deleted URI stood before the renames that ran ahead of it, and what was moved into it: the places unsaved work can be. */
|
|
65
78
|
function placesOf(uri, renames) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
const found = new Set([uri]);
|
|
80
|
+
for (let i = renames.length - 1; i >= 0; i--) {
|
|
81
|
+
const {
|
|
82
|
+
oldUri,
|
|
83
|
+
newUri
|
|
84
|
+
} = renames[i];
|
|
85
|
+
for (const place of [...found]) {
|
|
86
|
+
if (place === newUri || within(place, newUri)) {
|
|
87
|
+
found.add(oldUri + place.slice(newUri.length));
|
|
88
|
+
} else if (within(newUri, place)) {
|
|
89
|
+
found.add(oldUri);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return [...found];
|
|
79
94
|
}
|
|
80
95
|
/* Whether deleting or replacing loses work nobody saved at any of the places. Without `isUnsaved` that is an editor on, or under, a place. A `recursive` one may be a folder. */
|
|
81
96
|
function losesWork(recursive, places, host) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
97
|
+
const files = host.files;
|
|
98
|
+
if (files.isUnsaved !== undefined) {
|
|
99
|
+
return places.some(place => {
|
|
100
|
+
const path = fileUriToPath(place);
|
|
101
|
+
return path !== null && files.isUnsaved(path);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
if (host.openUris === undefined && recursive) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
const open = host.openUris?.() ?? [];
|
|
108
|
+
return places.some(place => host.editorOf(place) !== undefined || open.some(uri => uri === place || within(uri, place)));
|
|
94
109
|
}
|
|
95
110
|
function readOnlyAt(host, uri, editor) {
|
|
96
|
-
|
|
111
|
+
return host.isReadOnly?.(uri) ?? editor?.isReadOnly?.() ?? false;
|
|
97
112
|
}
|
|
98
113
|
/* Why a create or move over a file that is there is refused; null when the host can replace it without losing work. */
|
|
99
114
|
function overwriteProblem(step, target, places, host, reads) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
const path = fileUriToPath(target);
|
|
116
|
+
if (host.files?.supportsOverwrite !== true) {
|
|
117
|
+
return `${path} already exists`;
|
|
118
|
+
}
|
|
119
|
+
// A target no text was read from may be a folder, with unsaved work anywhere under it.
|
|
120
|
+
const isText = step.kind === 'create' || reads.open.has(target) || reads.disks.has(target);
|
|
121
|
+
if (losesWork(!isText, places, host)) {
|
|
122
|
+
return `${path} has changes that are not saved`;
|
|
123
|
+
}
|
|
124
|
+
const editor = host.editorOf(target);
|
|
125
|
+
return editor !== undefined && step.kind === 'create' && readOnlyAt(host, target, editor) ? `${path} is read only` : null;
|
|
111
126
|
}
|
|
112
127
|
/* Checks a plan against what only the host can see: paths, overwrites, unsaved work and the editors it touches. Null when it fits. */
|
|
113
128
|
function problemWith(steps, host, reads) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
if (step.kind === 'rename') {
|
|
135
|
-
renames.push({ kind: 'rename', oldUri: step.oldUri, newUri: step.newUri });
|
|
136
|
-
}
|
|
137
|
-
if (step.kind === 'delete' && losesWork(step.recursive, placesOf(step.uri, renames), host)) {
|
|
138
|
-
return `${paths[0]} has changes that are not saved`;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
for (const [uri, planned] of reads.open) {
|
|
142
|
-
const editor = host.editorOf(uri);
|
|
143
|
-
if (editor === undefined || editor.getText() !== planned.text) {
|
|
144
|
-
return `${uri} changed while the edit was being prepared`;
|
|
145
|
-
}
|
|
146
|
-
const version = host.versionOf?.(uri) ?? null;
|
|
147
|
-
if (planned.version !== null && version !== planned.version) {
|
|
148
|
-
return `${uri} changed while the edit was being prepared`;
|
|
149
|
-
}
|
|
129
|
+
const renames = [];
|
|
130
|
+
for (const step of steps) {
|
|
131
|
+
if (step.kind === 'edits') {
|
|
132
|
+
if (step.open && readOnlyAt(host, step.readUri, host.editorOf(step.readUri))) {
|
|
133
|
+
return `${step.uri} is read only`;
|
|
134
|
+
}
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const uris = step.kind === 'rename' ? [step.oldUri, step.newUri] : [step.uri];
|
|
138
|
+
const paths = uris.map(uri => fileUriToPath(uri));
|
|
139
|
+
if (paths.includes(null)) {
|
|
140
|
+
return `${uris[0]} cannot be changed from here`;
|
|
141
|
+
}
|
|
142
|
+
if ((step.kind === 'create' || step.kind === 'rename') && step.overwrite) {
|
|
143
|
+
const target = step.kind === 'create' ? step.uri : step.newUri;
|
|
144
|
+
const refusal = overwriteProblem(step, target, placesOf(target, renames), host, reads);
|
|
145
|
+
if (refusal !== null) {
|
|
146
|
+
return refusal;
|
|
147
|
+
}
|
|
150
148
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
149
|
+
if (step.kind === 'rename') {
|
|
150
|
+
renames.push({
|
|
151
|
+
kind: 'rename',
|
|
152
|
+
oldUri: step.oldUri,
|
|
153
|
+
newUri: step.newUri
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (step.kind === 'delete' && losesWork(step.recursive, placesOf(step.uri, renames), host)) {
|
|
157
|
+
return `${paths[0]} has changes that are not saved`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const [uri, planned] of reads.open) {
|
|
161
|
+
const editor = host.editorOf(uri);
|
|
162
|
+
if (editor === undefined || editor.getText() !== planned.text) {
|
|
163
|
+
return `${uri} changed while the edit was being prepared`;
|
|
164
|
+
}
|
|
165
|
+
const version = host.versionOf?.(uri) ?? null;
|
|
166
|
+
if (planned.version !== null && version !== planned.version) {
|
|
167
|
+
return `${uri} changed while the edit was being prepared`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const uri of reads.disks.keys()) {
|
|
171
|
+
if (host.editorOf(uri) !== undefined) {
|
|
172
|
+
return `${uri} was opened while the edit was being prepared`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
157
176
|
}
|
|
158
177
|
/*
|
|
159
178
|
* Applies an LSP workspace edit step by step, after planning it against every text (`planWorkspaceOperations`)
|
|
@@ -173,152 +192,184 @@ function problemWith(steps, host, reads) {
|
|
|
173
192
|
* looked for where it was and refused.
|
|
174
193
|
*/
|
|
175
194
|
export async function applyWorkspaceEdit(edit, host) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
exists: async (uri) => {
|
|
202
|
-
const path = fileUriToPath(uri);
|
|
203
|
-
return path !== null && (await files.exists(path));
|
|
204
|
-
}
|
|
205
|
-
}),
|
|
206
|
-
...(files?.isFolder === undefined
|
|
207
|
-
? {}
|
|
208
|
-
: {
|
|
209
|
-
isFolder: async (uri) => {
|
|
210
|
-
const path = fileUriToPath(uri);
|
|
211
|
-
return path === null || (await files.isFolder(path));
|
|
212
|
-
}
|
|
213
|
-
})
|
|
214
|
-
}));
|
|
215
|
-
}
|
|
216
|
-
catch (error) {
|
|
217
|
-
return refused(messageOf(error));
|
|
218
|
-
}
|
|
219
|
-
const problem = problemWith(steps, host, reads);
|
|
220
|
-
if (problem !== null) {
|
|
221
|
-
return refused(problem);
|
|
222
|
-
}
|
|
223
|
-
const running = { ahead: [], reads, saving: steps.some((step) => step.kind === 'rename'), staged: [], touched: new Set() };
|
|
224
|
-
for (const [index, step] of steps.entries()) {
|
|
225
|
-
running.ahead = steps.slice(index + 1);
|
|
226
|
-
const reason = await run(step, host, running);
|
|
227
|
-
if (reason !== null) {
|
|
228
|
-
return refused(reason);
|
|
195
|
+
const reads = {
|
|
196
|
+
open: new Map(),
|
|
197
|
+
disks: new Map()
|
|
198
|
+
};
|
|
199
|
+
const files = host.files;
|
|
200
|
+
let steps;
|
|
201
|
+
try {
|
|
202
|
+
({
|
|
203
|
+
steps
|
|
204
|
+
} = await planWorkspaceOperations(edit, {
|
|
205
|
+
operations: supportedResourceOperations(files),
|
|
206
|
+
read: async uri => {
|
|
207
|
+
const editor = host.editorOf(uri);
|
|
208
|
+
if (editor !== undefined) {
|
|
209
|
+
const text = editor.getText();
|
|
210
|
+
const version = host.versionOf?.(uri) ?? null;
|
|
211
|
+
reads.open.set(uri, {
|
|
212
|
+
text,
|
|
213
|
+
version
|
|
214
|
+
});
|
|
215
|
+
return {
|
|
216
|
+
text,
|
|
217
|
+
version,
|
|
218
|
+
open: true
|
|
219
|
+
};
|
|
229
220
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
221
|
+
const path = fileUriToPath(uri);
|
|
222
|
+
const disk = path === null || files === null ? null : await files.read(path);
|
|
223
|
+
if (disk === null) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
reads.disks.set(uri, disk);
|
|
227
|
+
return {
|
|
228
|
+
text: disk.text,
|
|
229
|
+
version: null,
|
|
230
|
+
open: false
|
|
231
|
+
};
|
|
232
|
+
},
|
|
233
|
+
...(files?.exists === undefined ? {} : {
|
|
234
|
+
exists: async uri => {
|
|
235
|
+
const path = fileUriToPath(uri);
|
|
236
|
+
return path !== null && (await files.exists(path));
|
|
237
|
+
}
|
|
238
|
+
}),
|
|
239
|
+
...(files?.isFolder === undefined ? {} : {
|
|
240
|
+
isFolder: async uri => {
|
|
241
|
+
const path = fileUriToPath(uri);
|
|
242
|
+
return path === null || (await files.isFolder(path));
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
}));
|
|
246
|
+
} catch (error) {
|
|
247
|
+
return refused(messageOf(error));
|
|
248
|
+
}
|
|
249
|
+
const problem = problemWith(steps, host, reads);
|
|
250
|
+
if (problem !== null) {
|
|
251
|
+
return refused(problem);
|
|
252
|
+
}
|
|
253
|
+
const running = {
|
|
254
|
+
ahead: [],
|
|
255
|
+
reads,
|
|
256
|
+
saving: steps.some(step => step.kind === 'rename'),
|
|
257
|
+
staged: [],
|
|
258
|
+
touched: new Set()
|
|
259
|
+
};
|
|
260
|
+
for (const [index, step] of steps.entries()) {
|
|
261
|
+
running.ahead = steps.slice(index + 1);
|
|
262
|
+
const reason = await run(step, host, running);
|
|
263
|
+
if (reason !== null) {
|
|
264
|
+
return refused(reason);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (running.staged.length > 0) {
|
|
268
|
+
files?.stage(running.staged);
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
applied: true
|
|
272
|
+
};
|
|
235
273
|
}
|
|
236
274
|
/* Why a text step no longer fits its editor, now that the steps before it ran; null when it does. */
|
|
237
275
|
function driftOf(step, host, uri, editor, touched) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
276
|
+
if (editor.getText() !== step.before) {
|
|
277
|
+
return `${step.uri} changed while the edit was being applied`;
|
|
278
|
+
}
|
|
279
|
+
const version = uri === step.readUri && !touched.has(editor) ? host.versionOf?.(uri) : undefined;
|
|
280
|
+
if (version !== undefined && step.version !== null && version !== step.version) {
|
|
281
|
+
return `${step.uri} changed while the edit was being applied`;
|
|
282
|
+
}
|
|
283
|
+
return readOnlyAt(host, uri, editor) ? `${step.uri} is read only` : null;
|
|
246
284
|
}
|
|
247
285
|
/* Does one step; the reason it could not, or null. */
|
|
248
286
|
async function run(step, host, running) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return
|
|
287
|
+
const files = host.files;
|
|
288
|
+
if (step.kind === 'create') {
|
|
289
|
+
return step.overwrite ? overwriteFile(step, host) : files.create(fileUriToPath(step.uri), step.text);
|
|
290
|
+
}
|
|
291
|
+
if (step.kind === 'rename') {
|
|
292
|
+
const [from, to] = [fileUriToPath(step.oldUri), fileUriToPath(step.newUri)];
|
|
293
|
+
if (!step.overwrite) {
|
|
294
|
+
return files.rename(from, to);
|
|
295
|
+
}
|
|
296
|
+
// The steps before this one ran, so the unsaved work at the target is looked for once more.
|
|
297
|
+
return losesWork(!running.reads.disks.has(step.newUri) && !running.reads.open.has(step.newUri), [step.newUri], host) ? `${to} has changes that are not saved` : files.rename(from, to, {
|
|
298
|
+
overwrite: true
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
if (step.kind === 'delete') {
|
|
302
|
+
const path = fileUriToPath(step.uri);
|
|
303
|
+
if (losesWork(step.recursive, [step.uri], host)) {
|
|
304
|
+
return `${path} has changes that are not saved`;
|
|
305
|
+
}
|
|
306
|
+
return files.delete(path, {
|
|
307
|
+
recursive: step.recursive
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if (step.after === step.before) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
if (step.open) {
|
|
314
|
+
const uri = host.editorOf(step.uri) === undefined ? step.readUri : step.uri;
|
|
315
|
+
return editOpen(step, host, running, uri, host.editorOf(uri));
|
|
316
|
+
}
|
|
317
|
+
return runDraft(step, host, running);
|
|
278
318
|
}
|
|
279
319
|
/* Empties a file that is there and gives it its text; the editor that still holds it is set to the same text. */
|
|
280
320
|
async function overwriteFile(step, host) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
321
|
+
const path = fileUriToPath(step.uri);
|
|
322
|
+
if (losesWork(false, [step.uri], host)) {
|
|
323
|
+
return `${path} has changes that are not saved`;
|
|
324
|
+
}
|
|
325
|
+
const reason = await host.files.create(path, step.text, {
|
|
326
|
+
overwrite: true
|
|
327
|
+
});
|
|
328
|
+
if (reason === null) {
|
|
329
|
+
host.editorOf(step.uri)?.setText(step.text);
|
|
330
|
+
}
|
|
331
|
+
return reason;
|
|
290
332
|
}
|
|
291
333
|
function editOpen(step, host, running, uri, editor) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
334
|
+
if (editor === undefined) {
|
|
335
|
+
return `${step.uri} is not open any more`;
|
|
336
|
+
}
|
|
337
|
+
const drift = driftOf(step, host, uri, editor, running.touched);
|
|
338
|
+
if (drift !== null) {
|
|
339
|
+
return drift;
|
|
340
|
+
}
|
|
341
|
+
running.touched.add(editor);
|
|
342
|
+
// The edits stay apart, so the caret and what is drawn over the text stay where they were between them.
|
|
343
|
+
return editor.applyEdits(step.edits.map(contentChangeOf)) ? null : `${step.uri} is read only`;
|
|
302
344
|
}
|
|
303
345
|
/* Whether a later step deletes the file or a folder around it, so a draft or a save of it would only bring it back. */
|
|
304
346
|
function deletedAhead(uri, ahead) {
|
|
305
|
-
|
|
347
|
+
return ahead.some(step => step.kind === 'delete' && (step.uri === uri || within(uri, step.uri.replace(/\/$/, ''))));
|
|
306
348
|
}
|
|
307
349
|
async function runDraft(step, host, running) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
350
|
+
const {
|
|
351
|
+
reads,
|
|
352
|
+
saving,
|
|
353
|
+
staged,
|
|
354
|
+
ahead
|
|
355
|
+
} = running;
|
|
356
|
+
const uri = [step.uri, step.readUri].find(candidate => host.editorOf(candidate) !== undefined);
|
|
357
|
+
if (uri !== undefined) {
|
|
358
|
+
return editOpen(step, host, running, uri, host.editorOf(uri));
|
|
359
|
+
}
|
|
360
|
+
const path = fileUriToPath(step.uri);
|
|
361
|
+
const disk = reads.disks.get(step.readUri);
|
|
362
|
+
if (path === null || disk === undefined || deletedAhead(step.uri, ahead)) {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
const file = {
|
|
366
|
+
path,
|
|
367
|
+
disk,
|
|
368
|
+
text: step.after
|
|
369
|
+
};
|
|
370
|
+
if (!saving) {
|
|
371
|
+
staged.push(file);
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
return host.files.save([file]);
|
|
375
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/editor-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Language-enabled editors, feature coordination and React popups over injected language services.",
|
|
6
6
|
"license": "FSL-1.1-MIT",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"test": "bun test --conditions=source"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@adecore/editor": "0.
|
|
66
|
-
"@adecore/lsp": "0.
|
|
67
|
-
"@adecore/merge": "0.
|
|
65
|
+
"@adecore/editor": "0.33.0",
|
|
66
|
+
"@adecore/lsp": "0.33.0",
|
|
67
|
+
"@adecore/merge": "0.33.0",
|
|
68
68
|
"lucide-react": "^1.47.0",
|
|
69
69
|
"react-markdown": "^10.1.0",
|
|
70
70
|
"remark-breaks": "^4.0.0",
|
|
@@ -72,14 +72,14 @@
|
|
|
72
72
|
"zustand": "^5.0.15"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
|
-
"@adecore/ui": "^0.
|
|
75
|
+
"@adecore/ui": "^0.33.0",
|
|
76
76
|
"i18next": "^26.4.2",
|
|
77
77
|
"react": "^19.3.0",
|
|
78
78
|
"react-dom": "^19.3.0",
|
|
79
79
|
"react-i18next": "^17.0.15"
|
|
80
80
|
},
|
|
81
81
|
"devDependencies": {
|
|
82
|
-
"@adecore/ui": "0.
|
|
82
|
+
"@adecore/ui": "0.33.0",
|
|
83
83
|
"@types/bun": "^1.4.2",
|
|
84
84
|
"@types/react": "^19.3.0",
|
|
85
85
|
"@types/react-dom": "^19.3.0",
|