@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.
Files changed (137) hide show
  1. package/dist/AnchoredPopup.js +73 -19
  2. package/dist/AttributionCard.js +162 -4
  3. package/dist/BaselineStatus.js +62 -8
  4. package/dist/CallHierarchyPanel.js +977 -60
  5. package/dist/ChangeReview.js +76 -25
  6. package/dist/CodeAuthorsCard.js +314 -14
  7. package/dist/CompletionPopup.js +490 -83
  8. package/dist/DiffView.js +548 -181
  9. package/dist/EditorContextMenu.js +352 -34
  10. package/dist/EditorView.js +276 -99
  11. package/dist/FindReplace.js +525 -67
  12. package/dist/HoverCard.js +378 -38
  13. package/dist/HoverSections.js +367 -56
  14. package/dist/LanguagePopups.js +95 -34
  15. package/dist/Markdown.js +41 -6
  16. package/dist/PanelResizeHandle.js +199 -66
  17. package/dist/PathText.js +49 -7
  18. package/dist/PeekPanel.js +675 -72
  19. package/dist/PickPopup.js +304 -34
  20. package/dist/RenameCard.js +679 -85
  21. package/dist/RenderedDocs.js +329 -58
  22. package/dist/SignatureCard.js +91 -7
  23. package/dist/SplitDiffView.js +344 -254
  24. package/dist/SymbolPicker.js +440 -85
  25. package/dist/call-hierarchy-model.js +102 -68
  26. package/dist/call-hierarchy.js +458 -359
  27. package/dist/code-actions-model.js +88 -76
  28. package/dist/code-actions.js +372 -283
  29. package/dist/code-authors.js +64 -55
  30. package/dist/code-vision-model.js +134 -127
  31. package/dist/code-vision.js +224 -206
  32. package/dist/completion-call.js +94 -67
  33. package/dist/completion-model.js +334 -244
  34. package/dist/completion.js +840 -649
  35. package/dist/conflict-model.js +35 -29
  36. package/dist/context-menu-rows.js +42 -17
  37. package/dist/context-menu.js +64 -46
  38. package/dist/definition-link.js +88 -85
  39. package/dist/diagnostics-model.js +74 -55
  40. package/dist/diagnostics.js +121 -100
  41. package/dist/diff-expansion.js +38 -35
  42. package/dist/diff-overlays.js +144 -22
  43. package/dist/diff-syntax.js +65 -42
  44. package/dist/diff-wordmarks.js +26 -23
  45. package/dist/doc-comment-model.js +265 -216
  46. package/dist/editor-language.js +177 -152
  47. package/dist/error-message.js +2 -2
  48. package/dist/find-query.js +49 -36
  49. package/dist/folding.js +81 -62
  50. package/dist/format-on-type.js +86 -67
  51. package/dist/highlight-layers.js +19 -20
  52. package/dist/highlights.js +105 -83
  53. package/dist/history.js +44 -38
  54. package/dist/holder.js +18 -18
  55. package/dist/host-types.js +1 -1
  56. package/dist/hover-content.js +167 -129
  57. package/dist/hover.js +285 -245
  58. package/dist/index.js +10 -10
  59. package/dist/inlay-hints.js +369 -324
  60. package/dist/key-priority.js +34 -31
  61. package/dist/language-ids.js +45 -42
  62. package/dist/line-edits.js +74 -28
  63. package/dist/line-text.js +18 -3
  64. package/dist/locales/en.json +38 -38
  65. package/dist/locales/nl.json +41 -41
  66. package/dist/models.js +1 -1
  67. package/dist/navigation-history.js +40 -41
  68. package/dist/navigation.js +141 -123
  69. package/dist/panel-size.js +8 -8
  70. package/dist/patch-decorations.js +236 -160
  71. package/dist/patch-layout.js +331 -262
  72. package/dist/paths.js +3 -3
  73. package/dist/peek-model.js +86 -69
  74. package/dist/peek.js +254 -192
  75. package/dist/pick.js +164 -157
  76. package/dist/popup-focus.js +5 -5
  77. package/dist/popup-placement.js +36 -21
  78. package/dist/popups.js +13 -13
  79. package/dist/project-language.js +228 -211
  80. package/dist/project-problems.js +81 -69
  81. package/dist/proposal-model.js +163 -126
  82. package/dist/recent-choices.js +25 -25
  83. package/dist/refresher.js +44 -49
  84. package/dist/rename-model.js +113 -113
  85. package/dist/rename.js +321 -233
  86. package/dist/rendered-docs.d.ts +4 -2
  87. package/dist/rendered-docs.js +384 -318
  88. package/dist/rendering-context.js +5 -3
  89. package/dist/rendering.js +22 -4
  90. package/dist/review-group.js +42 -39
  91. package/dist/row-host.d.ts +4 -2
  92. package/dist/row-host.js +90 -78
  93. package/dist/selection-ranges.js +21 -17
  94. package/dist/semantic-delta.js +22 -19
  95. package/dist/semantic-model.js +86 -73
  96. package/dist/semantic-tokens.js +201 -162
  97. package/dist/shortcut-keys.js +16 -10
  98. package/dist/signature-model.js +34 -28
  99. package/dist/signature.js +177 -129
  100. package/dist/snippet-field-transforms.js +118 -106
  101. package/dist/snippet-indent.js +48 -43
  102. package/dist/snippet-range-order.js +11 -11
  103. package/dist/snippet-session.js +703 -667
  104. package/dist/snippet-transform-client.js +59 -59
  105. package/dist/snippet-transform-format.js +285 -263
  106. package/dist/snippet-transform-protocol.js +1 -1
  107. package/dist/snippet-transform-queue.js +163 -153
  108. package/dist/snippet-transform-worker.js +42 -35
  109. package/dist/snippet-variable-names.js +1 -36
  110. package/dist/snippet-variables.js +53 -63
  111. package/dist/snippet.js +477 -405
  112. package/dist/split-alignment.js +47 -35
  113. package/dist/split-scroll.js +42 -40
  114. package/dist/symbol-links.js +33 -25
  115. package/dist/symbol-picker-model.js +126 -116
  116. package/dist/symbol-picker.js +98 -59
  117. package/dist/symbols.js +117 -88
  118. package/dist/testing.js +248 -165
  119. package/dist/timers.js +35 -30
  120. package/dist/use-panel-body.js +49 -8
  121. package/dist/use-problem-counts.js +29 -15
  122. package/dist/view-state.js +54 -33
  123. package/dist/word-diff.js +18 -18
  124. package/dist/workspace-edit.js +300 -249
  125. package/package.json +6 -6
  126. package/src/CallHierarchyPanel.tsx +2 -2
  127. package/src/DiffView.tsx +3 -3
  128. package/src/FindReplace.tsx +2 -2
  129. package/src/LanguagePopups.tsx +2 -0
  130. package/src/PeekPanel.tsx +3 -3
  131. package/src/RenderedDocs.tsx +9 -10
  132. package/src/SymbolPicker.tsx +2 -2
  133. package/src/diff-overlays.tsx +6 -4
  134. package/src/locales/en.json +38 -38
  135. package/src/locales/nl.json +41 -41
  136. package/src/rendered-docs.ts +14 -3
  137. package/src/row-host.ts +14 -3
@@ -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
- return { applied: false, failureReason };
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
- const entries = new Map();
9
- const add = (uri, edits) => {
10
- entries.set(uri, [...(entries.get(uri) ?? []), edits]);
11
- };
12
- if (edit.documentChanges === undefined) {
13
- for (const [uri, edits] of Object.entries(edit.changes ?? {})) {
14
- add(uri, edits);
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
- return (edit.documentChanges ?? []).some((change) => 'kind' in change && change.kind === 'delete') ? null : textEntriesOf(edit);
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
- return (edit.documentChanges ?? []).filter((change) => 'kind' in change && change.kind === 'rename');
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
- 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;
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
- return { range: edit.range, text: edit.newText };
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
- 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;
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
- return uri.startsWith(`${folder}/`);
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
- 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));
72
- }
73
- else if (within(newUri, place)) {
74
- found.add(oldUri);
75
- }
76
- }
77
- }
78
- return [...found];
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
- 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
- });
88
- }
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)));
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
- return host.isReadOnly?.(uri) ?? editor?.isReadOnly?.() ?? false;
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
- 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;
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
- const renames = [];
115
- for (const step of steps) {
116
- if (step.kind === 'edits') {
117
- if (step.open && readOnlyAt(host, step.readUri, host.editorOf(step.readUri))) {
118
- return `${step.uri} is read only`;
119
- }
120
- continue;
121
- }
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`;
126
- }
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;
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
- 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;
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
- 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);
192
- if (disk === null) {
193
- return null;
194
- }
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);
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
- if (running.staged.length > 0) {
232
- files?.stage(running.staged);
233
- }
234
- return { applied: true };
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
- 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;
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
- 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);
257
- }
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`;
267
- }
268
- return files.delete(path, { recursive: step.recursive });
269
- }
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);
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
- 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;
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
- 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`;
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
- return ahead.some((step) => step.kind === 'delete' && (step.uri === uri || within(uri, step.uri.replace(/\/$/, ''))));
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
- 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));
312
- }
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;
322
- }
323
- return host.files.save([file]);
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.31.12",
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.31.12",
66
- "@adecore/lsp": "0.31.12",
67
- "@adecore/merge": "0.31.12",
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.31.12",
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.31.12",
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",