@adecore/editor-react 0.22.1 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CallHierarchyPanel.d.ts +6 -0
- package/dist/CallHierarchyPanel.js +92 -0
- package/dist/CompletionPopup.js +11 -3
- package/dist/DiffView.d.ts +22 -0
- package/dist/DiffView.js +203 -0
- package/dist/EditorContextMenu.js +1 -0
- package/dist/EditorView.js +18 -1
- package/dist/FindReplace.d.ts +1 -1
- package/dist/FindReplace.js +41 -11
- package/dist/HoverCard.js +2 -2
- package/dist/LanguagePopups.js +4 -1
- package/dist/Markdown.d.ts +2 -1
- package/dist/Markdown.js +8 -4
- package/dist/PanelResizeHandle.d.ts +8 -0
- package/dist/PanelResizeHandle.js +71 -0
- package/dist/PeekPanel.d.ts +3 -0
- package/dist/PeekPanel.js +7 -4
- package/dist/PickPopup.js +15 -2
- package/dist/RenameCard.js +10 -5
- package/dist/RenderedDocs.d.ts +9 -0
- package/dist/RenderedDocs.js +84 -0
- package/dist/SplitDiffView.d.ts +9 -0
- package/dist/SplitDiffView.js +276 -0
- package/dist/SymbolPicker.d.ts +3 -0
- package/dist/SymbolPicker.js +4 -3
- package/dist/call-hierarchy-model.d.ts +52 -0
- package/dist/call-hierarchy-model.js +93 -0
- package/dist/call-hierarchy.d.ts +42 -0
- package/dist/call-hierarchy.js +398 -0
- package/dist/code-actions-model.d.ts +2 -0
- package/dist/code-actions-model.js +12 -10
- package/dist/code-actions.js +3 -1
- package/dist/completion-model.d.ts +14 -4
- package/dist/completion-model.js +40 -33
- package/dist/completion.d.ts +4 -0
- package/dist/completion.js +257 -42
- package/dist/diagnostics.js +8 -6
- package/dist/diff-expansion.d.ts +9 -0
- package/dist/diff-expansion.js +41 -0
- package/dist/diff-overlays.d.ts +15 -0
- package/dist/diff-overlays.js +28 -0
- package/dist/diff-syntax.d.ts +16 -0
- package/dist/diff-syntax.js +49 -0
- package/dist/diff-wordmarks.d.ts +5 -0
- package/dist/diff-wordmarks.js +26 -0
- package/dist/doc-comment-model.d.ts +23 -0
- package/dist/doc-comment-model.js +291 -0
- package/dist/editor-language.d.ts +13 -1
- package/dist/editor-language.js +15 -5
- package/dist/editor-react.css +79 -0
- package/dist/format-on-type.d.ts +14 -0
- package/dist/format-on-type.js +96 -0
- package/dist/host-types.d.ts +19 -0
- package/dist/hover.d.ts +4 -0
- package/dist/hover.js +15 -2
- package/dist/index.d.ts +19 -5
- package/dist/index.js +9 -1
- package/dist/inlay-hints.d.ts +47 -2
- package/dist/inlay-hints.js +353 -16
- package/dist/language-ids.js +4 -2
- package/dist/locales/en.json +117 -3
- package/dist/locales/nl.json +117 -3
- package/dist/models.d.ts +6 -2
- package/dist/models.js +5 -1
- package/dist/panel-size.d.ts +6 -0
- package/dist/panel-size.js +25 -0
- package/dist/patch-decorations.d.ts +27 -0
- package/dist/patch-decorations.js +182 -0
- package/dist/patch-layout.d.ts +40 -0
- package/dist/patch-layout.js +304 -0
- package/dist/peek.d.ts +3 -1
- package/dist/peek.js +6 -1
- package/dist/pick.d.ts +1 -0
- package/dist/pick.js +11 -1
- package/dist/popups.d.ts +17 -0
- package/dist/popups.js +1 -0
- package/dist/project-language.d.ts +12 -0
- package/dist/project-language.js +52 -2
- package/dist/refresher.d.ts +1 -0
- package/dist/refresher.js +5 -0
- package/dist/rename-model.d.ts +1 -1
- package/dist/rename-model.js +3 -7
- package/dist/rename.js +5 -8
- package/dist/rendered-docs.d.ts +64 -0
- package/dist/rendered-docs.js +374 -0
- package/dist/row-host.d.ts +2 -0
- package/dist/row-host.js +11 -3
- package/dist/semantic-delta.d.ts +2 -0
- package/dist/semantic-delta.js +27 -0
- package/dist/semantic-model.js +16 -0
- package/dist/semantic-tokens.d.ts +26 -1
- package/dist/semantic-tokens.js +174 -14
- package/dist/shortcut-keys.d.ts +2 -0
- package/dist/shortcut-keys.js +6 -2
- package/dist/signature.js +1 -1
- package/dist/snippet-field-transforms.d.ts +30 -0
- package/dist/snippet-field-transforms.js +116 -0
- package/dist/snippet-indent.d.ts +2 -0
- package/dist/snippet-indent.js +49 -0
- package/dist/snippet-range-order.d.ts +3 -0
- package/dist/snippet-range-order.js +19 -0
- package/dist/snippet-session.d.ts +42 -6
- package/dist/snippet-session.js +650 -53
- package/dist/snippet-transform-client.d.ts +19 -0
- package/dist/snippet-transform-client.js +66 -0
- package/dist/snippet-transform-format.d.ts +15 -0
- package/dist/snippet-transform-format.js +302 -0
- package/dist/snippet-transform-protocol.d.ts +45 -0
- package/dist/snippet-transform-protocol.js +8 -0
- package/dist/snippet-transform-queue.d.ts +32 -0
- package/dist/snippet-transform-queue.js +173 -0
- package/dist/snippet-transform-worker.d.ts +10 -0
- package/dist/snippet-transform-worker.js +41 -0
- package/dist/snippet-variable-names.d.ts +1 -0
- package/dist/snippet-variable-names.js +36 -0
- package/dist/snippet-variables.d.ts +7 -0
- package/dist/snippet-variables.js +66 -0
- package/dist/snippet.d.ts +30 -3
- package/dist/snippet.js +402 -87
- package/dist/split-alignment.d.ts +6 -0
- package/dist/split-alignment.js +38 -0
- package/dist/split-scroll.d.ts +14 -0
- package/dist/split-scroll.js +45 -0
- package/dist/symbols.d.ts +2 -1
- package/dist/symbols.js +39 -7
- package/dist/testing.d.ts +5 -1
- package/dist/testing.js +12 -0
- package/dist/timers.d.ts +1 -0
- package/dist/timers.js +4 -0
- package/dist/use-panel-body.d.ts +6 -0
- package/dist/use-panel-body.js +13 -0
- package/dist/workspace-edit.d.ts +25 -3
- package/dist/workspace-edit.js +282 -143
- package/package.json +10 -5
- package/src/CallHierarchyPanel.tsx +244 -0
- package/src/CompletionPopup.tsx +12 -1
- package/src/DiffView.tsx +262 -0
- package/src/EditorContextMenu.tsx +1 -0
- package/src/EditorView.tsx +21 -2
- package/src/FindReplace.tsx +51 -13
- package/src/HoverCard.tsx +23 -21
- package/src/LanguagePopups.tsx +5 -0
- package/src/Markdown.tsx +10 -3
- package/src/PanelResizeHandle.tsx +104 -0
- package/src/PeekPanel.tsx +11 -6
- package/src/PickPopup.tsx +18 -2
- package/src/RenameCard.tsx +18 -0
- package/src/RenderedDocs.tsx +155 -0
- package/src/SplitDiffView.tsx +337 -0
- package/src/SymbolPicker.tsx +4 -3
- package/src/call-hierarchy-model.ts +151 -0
- package/src/call-hierarchy.ts +440 -0
- package/src/code-actions-model.ts +17 -11
- package/src/code-actions.ts +3 -1
- package/src/completion-model.ts +61 -35
- package/src/completion.ts +302 -42
- package/src/diagnostics.ts +8 -6
- package/src/diff-expansion.ts +49 -0
- package/src/diff-overlays.tsx +69 -0
- package/src/diff-syntax.ts +67 -0
- package/src/diff-wordmarks.ts +31 -0
- package/src/doc-comment-model.ts +336 -0
- package/src/editor-language.ts +22 -6
- package/src/editor-react.css +79 -0
- package/src/format-on-type.ts +111 -0
- package/src/host-types.ts +23 -0
- package/src/hover.ts +18 -3
- package/src/index.ts +27 -4
- package/src/inlay-hints.ts +394 -18
- package/src/language-ids.ts +4 -2
- package/src/locales/en.json +117 -3
- package/src/locales/nl.json +117 -3
- package/src/models.ts +40 -1
- package/src/panel-size.ts +28 -0
- package/src/patch-decorations.ts +224 -0
- package/src/patch-layout.ts +388 -0
- package/src/peek.ts +6 -1
- package/src/pick.ts +12 -1
- package/src/popups.ts +24 -0
- package/src/project-language.ts +63 -2
- package/src/refresher.ts +6 -0
- package/src/rename-model.ts +4 -8
- package/src/rename.ts +5 -8
- package/src/rendered-docs.ts +430 -0
- package/src/row-host.ts +12 -3
- package/src/semantic-delta.ts +29 -0
- package/src/semantic-model.ts +17 -0
- package/src/semantic-tokens.ts +202 -19
- package/src/shortcut-keys.ts +6 -2
- package/src/signature.ts +1 -1
- package/src/snippet-field-transforms.ts +144 -0
- package/src/snippet-indent.ts +53 -0
- package/src/snippet-range-order.ts +19 -0
- package/src/snippet-session.ts +797 -56
- package/src/snippet-transform-client.ts +84 -0
- package/src/snippet-transform-format.ts +325 -0
- package/src/snippet-transform-protocol.ts +24 -0
- package/src/snippet-transform-queue.ts +199 -0
- package/src/snippet-transform-worker.ts +58 -0
- package/src/snippet-variable-names.ts +36 -0
- package/src/snippet-variables.ts +74 -0
- package/src/snippet.ts +452 -89
- package/src/split-alignment.ts +45 -0
- package/src/split-scroll.ts +54 -0
- package/src/symbols.ts +43 -7
- package/src/testing.ts +25 -0
- package/src/timers.ts +5 -0
- package/src/use-panel-body.ts +17 -0
- package/src/workspace-edit.ts +347 -169
package/dist/workspace-edit.js
CHANGED
|
@@ -1,185 +1,324 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fileUriToPath, planWorkspaceOperations } from '@adecore/lsp';
|
|
2
2
|
import { messageOf } from './error-message.js';
|
|
3
3
|
function refused(failureReason) {
|
|
4
4
|
return { applied: false, failureReason };
|
|
5
5
|
}
|
|
6
|
-
/* The
|
|
7
|
-
function
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
else if (change.kind === 'create') {
|
|
14
|
-
steps.push({ kind: 'create', file: change });
|
|
15
|
-
}
|
|
16
|
-
else if (change.kind === 'rename') {
|
|
17
|
-
steps.push({ kind: 'rename', oldUri: change.oldUri, newUri: change.newUri });
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
6
|
+
/* The text edits of an edit per document, as the entries a document has in it, whatever else the edit does. */
|
|
7
|
+
export function textEntriesOf(edit) {
|
|
8
|
+
const entries = new Map();
|
|
9
|
+
const add = (uri, edits) => {
|
|
10
|
+
entries.set(uri, [...(entries.get(uri) ?? []), edits]);
|
|
11
|
+
};
|
|
23
12
|
if (edit.documentChanges === undefined) {
|
|
24
13
|
for (const [uri, edits] of Object.entries(edit.changes ?? {})) {
|
|
25
|
-
|
|
14
|
+
add(uri, edits);
|
|
26
15
|
}
|
|
16
|
+
return entries;
|
|
27
17
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export function entriesOf(edit) {
|
|
32
|
-
const steps = stepsOf(edit);
|
|
33
|
-
if (steps === null) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
const entries = new Map();
|
|
37
|
-
for (const step of steps) {
|
|
38
|
-
if (step.kind === 'edits') {
|
|
39
|
-
entries.set(step.uri, [...(entries.get(step.uri) ?? []), step.edits]);
|
|
18
|
+
for (const change of edit.documentChanges) {
|
|
19
|
+
if ('textDocument' in change) {
|
|
20
|
+
add(change.textDocument.uri, change.edits);
|
|
40
21
|
}
|
|
41
22
|
}
|
|
42
23
|
return entries;
|
|
43
24
|
}
|
|
25
|
+
/* The text edits of an edit per document, as the entries a document has in it; null when it deletes a file. */
|
|
26
|
+
export function entriesOf(edit) {
|
|
27
|
+
return (edit.documentChanges ?? []).some((change) => 'kind' in change && change.kind === 'delete') ? null : textEntriesOf(edit);
|
|
28
|
+
}
|
|
44
29
|
/* The files an edit moves, which a preview of lines cannot show. */
|
|
45
30
|
export function renamesOf(edit) {
|
|
46
31
|
return (edit.documentChanges ?? []).filter((change) => 'kind' in change && change.kind === 'rename');
|
|
47
32
|
}
|
|
33
|
+
/* Every file an edit makes, moves or deletes, in the order it says. */
|
|
34
|
+
export function resourceOperationsOf(edit) {
|
|
35
|
+
const operations = [];
|
|
36
|
+
for (const change of edit.documentChanges ?? []) {
|
|
37
|
+
if ('textDocument' in change) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
operations.push(change.kind === 'rename' ? { kind: 'rename', uri: change.oldUri, newUri: change.newUri } : { kind: change.kind, uri: change.uri });
|
|
41
|
+
}
|
|
42
|
+
return operations;
|
|
43
|
+
}
|
|
48
44
|
function contentChangeOf(edit) {
|
|
49
45
|
return { range: edit.range, text: edit.newText };
|
|
50
46
|
}
|
|
51
|
-
/*
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
47
|
+
/* 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
|
+
export function supportedResourceOperations(files) {
|
|
49
|
+
const operations = [];
|
|
50
|
+
if (files?.create !== undefined) {
|
|
51
|
+
operations.push('create');
|
|
52
|
+
}
|
|
53
|
+
if (files?.rename !== undefined) {
|
|
54
|
+
operations.push('rename');
|
|
55
|
+
}
|
|
56
|
+
if (files?.delete !== undefined) {
|
|
57
|
+
operations.push('delete');
|
|
58
|
+
}
|
|
59
|
+
return operations;
|
|
60
|
+
}
|
|
61
|
+
function within(uri, folder) {
|
|
62
|
+
return uri.startsWith(`${folder}/`);
|
|
63
|
+
}
|
|
64
|
+
/* 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
|
+
function placesOf(uri, renames) {
|
|
66
|
+
const found = new Set([uri]);
|
|
67
|
+
for (let i = renames.length - 1; i >= 0; i--) {
|
|
68
|
+
const { oldUri, newUri } = renames[i];
|
|
69
|
+
for (const place of [...found]) {
|
|
70
|
+
if (place === newUri || within(place, newUri)) {
|
|
71
|
+
found.add(oldUri + place.slice(newUri.length));
|
|
73
72
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (item.after === item.before) {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
const editor = host.editorOf(item.uri);
|
|
80
|
-
if (editor) {
|
|
81
|
-
// The edits stay apart, so the caret and what is drawn over the text stay where they were between them.
|
|
82
|
-
if (!editor.applyEdits(item.edits.map(contentChangeOf))) {
|
|
83
|
-
return refused(`${item.uri} is read only`);
|
|
73
|
+
else if (within(newUri, place)) {
|
|
74
|
+
found.add(oldUri);
|
|
84
75
|
}
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
const path = fileUriToPath(item.uri);
|
|
88
|
-
if (path === null || item.disk === null) {
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
const file = { path, disk: item.disk, text: item.after };
|
|
92
|
-
if (!moving) {
|
|
93
|
-
staged.push(file);
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
const reason = await host.files.save([file]);
|
|
97
|
-
if (reason !== null) {
|
|
98
|
-
return refused(reason);
|
|
99
76
|
}
|
|
100
77
|
}
|
|
101
|
-
|
|
102
|
-
|
|
78
|
+
return [...found];
|
|
79
|
+
}
|
|
80
|
+
/* 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
|
+
function losesWork(recursive, places, host) {
|
|
82
|
+
const files = host.files;
|
|
83
|
+
if (files.isUnsaved !== undefined) {
|
|
84
|
+
return places.some((place) => {
|
|
85
|
+
const path = fileUriToPath(place);
|
|
86
|
+
return path !== null && files.isUnsaved(path);
|
|
87
|
+
});
|
|
103
88
|
}
|
|
104
|
-
|
|
89
|
+
if (host.openUris === undefined && recursive) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
const open = host.openUris?.() ?? [];
|
|
93
|
+
return places.some((place) => host.editorOf(place) !== undefined || open.some((uri) => uri === place || within(uri, place)));
|
|
105
94
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
95
|
+
function readOnlyAt(host, uri, editor) {
|
|
96
|
+
return host.isReadOnly?.(uri) ?? editor?.isReadOnly?.() ?? false;
|
|
97
|
+
}
|
|
98
|
+
/* Why a create or move over a file that is there is refused; null when the host can replace it without losing work. */
|
|
99
|
+
function overwriteProblem(step, target, places, host, reads) {
|
|
100
|
+
const path = fileUriToPath(target);
|
|
101
|
+
if (host.files?.supportsOverwrite !== true) {
|
|
102
|
+
return `${path} already exists`;
|
|
103
|
+
}
|
|
104
|
+
// A target no text was read from may be a folder, with unsaved work anywhere under it.
|
|
105
|
+
const isText = step.kind === 'create' || reads.open.has(target) || reads.disks.has(target);
|
|
106
|
+
if (losesWork(!isText, places, host)) {
|
|
107
|
+
return `${path} has changes that are not saved`;
|
|
108
|
+
}
|
|
109
|
+
const editor = host.editorOf(target);
|
|
110
|
+
return editor !== undefined && step.kind === 'create' && readOnlyAt(host, target, editor) ? `${path} is read only` : null;
|
|
111
|
+
}
|
|
112
|
+
/* Checks a plan against what only the host can see: paths, overwrites, unsaved work and the editors it touches. Null when it fits. */
|
|
113
|
+
function problemWith(steps, host, reads) {
|
|
114
|
+
const renames = [];
|
|
114
115
|
for (const step of steps) {
|
|
115
|
-
if (step.kind === '
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return made;
|
|
119
|
-
}
|
|
120
|
-
if (made !== null) {
|
|
121
|
-
creates.set(step.file.uri, made);
|
|
122
|
-
planned.push(made);
|
|
116
|
+
if (step.kind === 'edits') {
|
|
117
|
+
if (step.open && readOnlyAt(host, step.readUri, host.editorOf(step.readUri))) {
|
|
118
|
+
return `${step.uri} is read only`;
|
|
123
119
|
}
|
|
124
120
|
continue;
|
|
125
121
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return `${step.oldUri} cannot be moved from here`;
|
|
131
|
-
}
|
|
132
|
-
origins.set(step.newUri, origins.get(step.oldUri) ?? step.oldUri);
|
|
133
|
-
planned.push({ kind: 'rename', oldPath, newPath });
|
|
134
|
-
continue;
|
|
122
|
+
const uris = step.kind === 'rename' ? [step.oldUri, step.newUri] : [step.uri];
|
|
123
|
+
const paths = uris.map((uri) => fileUriToPath(uri));
|
|
124
|
+
if (paths.includes(null)) {
|
|
125
|
+
return `${uris[0]} cannot be changed from here`;
|
|
135
126
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
return messageOf(error);
|
|
127
|
+
if ((step.kind === 'create' || step.kind === 'rename') && step.overwrite) {
|
|
128
|
+
const target = step.kind === 'create' ? step.uri : step.newUri;
|
|
129
|
+
const refusal = overwriteProblem(step, target, placesOf(target, renames), host, reads);
|
|
130
|
+
if (refusal !== null) {
|
|
131
|
+
return refusal;
|
|
144
132
|
}
|
|
145
|
-
continue;
|
|
146
133
|
}
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (const uri of reads.disks.keys()) {
|
|
152
|
+
if (host.editorOf(uri) !== undefined) {
|
|
153
|
+
return `${uri} was opened while the edit was being prepared`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
/*
|
|
159
|
+
* Applies an LSP workspace edit step by step, after planning it against every text (`planWorkspaceOperations`)
|
|
160
|
+
* so one that does not fit changes nothing. What the host supports follows `host.files`: moves need `rename`,
|
|
161
|
+
* makes need `create` and deletes need `delete`. Just before the first step runs, every open editor must still
|
|
162
|
+
* hold the text and version the plan was made from, and a delete must not lose unsaved work. Each text step
|
|
163
|
+
* and each delete checks that again right before it runs, since the steps before it are awaited.
|
|
164
|
+
*
|
|
165
|
+
* A file an editor holds takes its edits as one undo step per step of the edit; any other file gets an unsaved
|
|
166
|
+
* draft, since nothing is written to disk on a server's word alone, unless the edit also moves files: then it
|
|
167
|
+
* is one change, saved and moved through the machine. A create or a move over a file that is there is refused,
|
|
168
|
+
* unless it says `overwrite` and `files.supportsOverwrite` is set. Then it is refused only when the target, or
|
|
169
|
+
* anything under a folder target, has unsaved work, checked again right before the step runs. `overwrite` wins
|
|
170
|
+
* over `ignoreIfExists`, as the protocol says. A created file that an editor still holds shows its new text.
|
|
171
|
+
* The steps run in order and a step that no longer fits stops the edit there, with the steps before it applied
|
|
172
|
+
* and nothing rolled back. A folder that only exists because an earlier move in the same edit made it is
|
|
173
|
+
* looked for where it was and refused.
|
|
174
|
+
*/
|
|
175
|
+
export async function applyWorkspaceEdit(edit, host) {
|
|
176
|
+
const reads = { open: new Map(), disks: new Map() };
|
|
177
|
+
const files = host.files;
|
|
178
|
+
let steps;
|
|
179
|
+
try {
|
|
180
|
+
({ steps } = await planWorkspaceOperations(edit, {
|
|
181
|
+
operations: supportedResourceOperations(files),
|
|
182
|
+
read: async (uri) => {
|
|
183
|
+
const editor = host.editorOf(uri);
|
|
184
|
+
if (editor !== undefined) {
|
|
185
|
+
const text = editor.getText();
|
|
186
|
+
const version = host.versionOf?.(uri) ?? null;
|
|
187
|
+
reads.open.set(uri, { text, version });
|
|
188
|
+
return { text, version, open: true };
|
|
189
|
+
}
|
|
190
|
+
const path = fileUriToPath(uri);
|
|
191
|
+
const disk = path === null || files === null ? null : await files.read(path);
|
|
155
192
|
if (disk === null) {
|
|
156
|
-
return
|
|
193
|
+
return null;
|
|
157
194
|
}
|
|
158
|
-
|
|
159
|
-
|
|
195
|
+
reads.disks.set(uri, disk);
|
|
196
|
+
return { text: disk.text, version: null, open: false };
|
|
197
|
+
},
|
|
198
|
+
...(files?.exists === undefined
|
|
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);
|
|
160
229
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
230
|
+
}
|
|
231
|
+
if (running.staged.length > 0) {
|
|
232
|
+
files?.stage(running.staged);
|
|
233
|
+
}
|
|
234
|
+
return { applied: true };
|
|
235
|
+
}
|
|
236
|
+
/* Why a text step no longer fits its editor, now that the steps before it ran; null when it does. */
|
|
237
|
+
function driftOf(step, host, uri, editor, touched) {
|
|
238
|
+
if (editor.getText() !== step.before) {
|
|
239
|
+
return `${step.uri} changed while the edit was being applied`;
|
|
240
|
+
}
|
|
241
|
+
const version = uri === step.readUri && !touched.has(editor) ? host.versionOf?.(uri) : undefined;
|
|
242
|
+
if (version !== undefined && step.version !== null && version !== step.version) {
|
|
243
|
+
return `${step.uri} changed while the edit was being applied`;
|
|
244
|
+
}
|
|
245
|
+
return readOnlyAt(host, uri, editor) ? `${step.uri} is read only` : null;
|
|
246
|
+
}
|
|
247
|
+
/* Does one step; the reason it could not, or null. */
|
|
248
|
+
async function run(step, host, running) {
|
|
249
|
+
const files = host.files;
|
|
250
|
+
if (step.kind === 'create') {
|
|
251
|
+
return step.overwrite ? overwriteFile(step, host) : files.create(fileUriToPath(step.uri), step.text);
|
|
252
|
+
}
|
|
253
|
+
if (step.kind === 'rename') {
|
|
254
|
+
const [from, to] = [fileUriToPath(step.oldUri), fileUriToPath(step.newUri)];
|
|
255
|
+
if (!step.overwrite) {
|
|
256
|
+
return files.rename(from, to);
|
|
165
257
|
}
|
|
166
|
-
|
|
167
|
-
|
|
258
|
+
// The steps before this one ran, so the unsaved work at the target is looked for once more.
|
|
259
|
+
return losesWork(!running.reads.disks.has(step.newUri) && !running.reads.open.has(step.newUri), [step.newUri], host)
|
|
260
|
+
? `${to} has changes that are not saved`
|
|
261
|
+
: files.rename(from, to, { overwrite: true });
|
|
262
|
+
}
|
|
263
|
+
if (step.kind === 'delete') {
|
|
264
|
+
const path = fileUriToPath(step.uri);
|
|
265
|
+
if (losesWork(step.recursive, [step.uri], host)) {
|
|
266
|
+
return `${path} has changes that are not saved`;
|
|
168
267
|
}
|
|
169
|
-
|
|
170
|
-
texts.set(source, { text: after, disk: held.disk });
|
|
268
|
+
return files.delete(path, { recursive: step.recursive });
|
|
171
269
|
}
|
|
172
|
-
|
|
270
|
+
if (step.after === step.before) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
if (step.open) {
|
|
274
|
+
const uri = host.editorOf(step.uri) === undefined ? step.readUri : step.uri;
|
|
275
|
+
return editOpen(step, host, running, uri, host.editorOf(uri));
|
|
276
|
+
}
|
|
277
|
+
return runDraft(step, host, running);
|
|
278
|
+
}
|
|
279
|
+
/* Empties a file that is there and gives it its text; the editor that still holds it is set to the same text. */
|
|
280
|
+
async function overwriteFile(step, host) {
|
|
281
|
+
const path = fileUriToPath(step.uri);
|
|
282
|
+
if (losesWork(false, [step.uri], host)) {
|
|
283
|
+
return `${path} has changes that are not saved`;
|
|
284
|
+
}
|
|
285
|
+
const reason = await host.files.create(path, step.text, { overwrite: true });
|
|
286
|
+
if (reason === null) {
|
|
287
|
+
host.editorOf(step.uri)?.setText(step.text);
|
|
288
|
+
}
|
|
289
|
+
return reason;
|
|
290
|
+
}
|
|
291
|
+
function editOpen(step, host, running, uri, editor) {
|
|
292
|
+
if (editor === undefined) {
|
|
293
|
+
return `${step.uri} is not open any more`;
|
|
294
|
+
}
|
|
295
|
+
const drift = driftOf(step, host, uri, editor, running.touched);
|
|
296
|
+
if (drift !== null) {
|
|
297
|
+
return drift;
|
|
298
|
+
}
|
|
299
|
+
running.touched.add(editor);
|
|
300
|
+
// The edits stay apart, so the caret and what is drawn over the text stay where they were between them.
|
|
301
|
+
return editor.applyEdits(step.edits.map(contentChangeOf)) ? null : `${step.uri} is read only`;
|
|
302
|
+
}
|
|
303
|
+
/* 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
|
+
function deletedAhead(uri, ahead) {
|
|
305
|
+
return ahead.some((step) => step.kind === 'delete' && (step.uri === uri || within(uri, step.uri.replace(/\/$/, ''))));
|
|
173
306
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
if (
|
|
178
|
-
return
|
|
307
|
+
async function runDraft(step, host, running) {
|
|
308
|
+
const { reads, saving, staged, ahead } = running;
|
|
309
|
+
const uri = [step.uri, step.readUri].find((candidate) => host.editorOf(candidate) !== undefined);
|
|
310
|
+
if (uri !== undefined) {
|
|
311
|
+
return editOpen(step, host, running, uri, host.editorOf(uri));
|
|
179
312
|
}
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
313
|
+
const path = fileUriToPath(step.uri);
|
|
314
|
+
const disk = reads.disks.get(step.readUri);
|
|
315
|
+
if (path === null || disk === undefined || deletedAhead(step.uri, ahead)) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
const file = { path, disk, text: step.after };
|
|
319
|
+
if (!saving) {
|
|
320
|
+
staged.push(file);
|
|
321
|
+
return null;
|
|
183
322
|
}
|
|
184
|
-
return
|
|
323
|
+
return host.files.save([file]);
|
|
185
324
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/editor-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.1",
|
|
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",
|
|
@@ -49,6 +49,11 @@
|
|
|
49
49
|
"./locales/*.json": {
|
|
50
50
|
"source": "./src/locales/*.json",
|
|
51
51
|
"default": "./dist/locales/*.json"
|
|
52
|
+
},
|
|
53
|
+
"./snippet-transform-worker": {
|
|
54
|
+
"source": "./src/snippet-transform-worker.ts",
|
|
55
|
+
"types": "./dist/snippet-transform-worker.d.ts",
|
|
56
|
+
"default": "./dist/snippet-transform-worker.js"
|
|
52
57
|
}
|
|
53
58
|
},
|
|
54
59
|
"scripts": {
|
|
@@ -57,9 +62,9 @@
|
|
|
57
62
|
"test": "bun test --conditions=source"
|
|
58
63
|
},
|
|
59
64
|
"dependencies": {
|
|
60
|
-
"@adecore/editor": "0.
|
|
61
|
-
"@adecore/lsp": "0.
|
|
62
|
-
"@adecore/merge": "0.
|
|
65
|
+
"@adecore/editor": "0.23.1",
|
|
66
|
+
"@adecore/lsp": "0.23.1",
|
|
67
|
+
"@adecore/merge": "0.23.1",
|
|
63
68
|
"lucide-react": "^1.47.0",
|
|
64
69
|
"react-markdown": "^10.1.0",
|
|
65
70
|
"remark-breaks": "^4.0.0",
|
|
@@ -67,7 +72,7 @@
|
|
|
67
72
|
"zustand": "^5.0.15"
|
|
68
73
|
},
|
|
69
74
|
"peerDependencies": {
|
|
70
|
-
"@adecore/ui": "^0.
|
|
75
|
+
"@adecore/ui": "^0.23.1",
|
|
71
76
|
"i18next": "^26.4.2",
|
|
72
77
|
"react": "^19.3.0",
|
|
73
78
|
"react-dom": "^19.3.0",
|