@adecore/editor-react 0.17.0-beta.1 → 0.17.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/LICENSE +1 -1
- package/README.md +35 -30
- package/dist/EditorView.d.ts +3 -2
- package/dist/EditorView.js +2 -2
- package/dist/HoverCard.d.ts +6 -0
- package/dist/HoverCard.js +20 -5
- package/dist/LanguagePopups.d.ts +3 -1
- package/dist/LanguagePopups.js +3 -3
- package/dist/code-actions-model.js +1 -1
- package/dist/code-actions.d.ts +1 -0
- package/dist/code-actions.js +19 -22
- package/dist/code-authors.d.ts +2 -1
- package/dist/code-authors.js +2 -3
- package/dist/code-vision-model.d.ts +2 -1
- package/dist/code-vision-model.js +3 -4
- package/dist/code-vision.js +12 -10
- package/dist/completion.js +1 -2
- package/dist/editor-language.d.ts +1 -0
- package/dist/editor-language.js +22 -0
- package/dist/folding.d.ts +2 -4
- package/dist/folding.js +11 -7
- package/dist/history.js +1 -2
- package/dist/host-types.d.ts +2 -0
- package/dist/hover.js +1 -2
- package/dist/inlay-hints.js +3 -5
- package/dist/language-ids.js +4 -2
- package/dist/locales/en.json +1 -0
- package/dist/locales/nl.json +1 -0
- package/dist/navigation.d.ts +1 -0
- package/dist/navigation.js +8 -9
- package/dist/peek.d.ts +1 -0
- package/dist/peek.js +6 -7
- package/dist/project-language.d.ts +2 -0
- package/dist/project-language.js +3 -0
- package/dist/rename.d.ts +1 -0
- package/dist/rename.js +10 -11
- package/dist/semantic-tokens.js +3 -5
- package/dist/symbol-picker.js +2 -3
- package/dist/symbols.js +3 -5
- package/dist/workspace-edit.d.ts +1 -0
- package/dist/workspace-edit.js +46 -7
- package/package.json +5 -5
- package/src/EditorView.tsx +5 -3
- package/src/HoverCard.tsx +44 -25
- package/src/LanguagePopups.tsx +8 -4
- package/src/code-actions-model.ts +1 -1
- package/src/code-actions.ts +20 -23
- package/src/code-authors.ts +3 -3
- package/src/code-vision-model.ts +4 -4
- package/src/code-vision.ts +12 -10
- package/src/completion.ts +1 -2
- package/src/editor-language.ts +26 -0
- package/src/folding.ts +14 -10
- package/src/history.ts +1 -2
- package/src/host-types.ts +3 -0
- package/src/hover.ts +1 -2
- package/src/inlay-hints.ts +3 -5
- package/src/language-ids.ts +4 -2
- package/src/locales/en.json +1 -0
- package/src/locales/nl.json +1 -0
- package/src/navigation.ts +9 -10
- package/src/peek.ts +7 -8
- package/src/project-language.ts +3 -0
- package/src/rename.ts +11 -12
- package/src/semantic-tokens.ts +3 -5
- package/src/symbol-picker.ts +2 -3
- package/src/symbols.ts +3 -5
- package/src/workspace-edit.ts +70 -10
package/src/peek.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import i18next from 'i18next';
|
|
2
1
|
import { StaleResultError, type Location, type Range } from '@adecore/lsp';
|
|
3
2
|
import type { EditorPosition } from '@adecore/editor';
|
|
4
3
|
import type { EditorLanguage } from './editor-language.ts';
|
|
@@ -22,10 +21,6 @@ function placeKey(location: Location): string {
|
|
|
22
21
|
return `${location.uri}\0${location.range.start.line}\0${location.range.start.character}`;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
function say(key: string, options?: Record<string, unknown>): string {
|
|
26
|
-
return i18next.t(`editor:language.peek.${key}`, options);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
24
|
/*
|
|
30
25
|
* The references of the name at the caret, between the lines of the file under the line it is on. The
|
|
31
26
|
* editor keeps the keyboard: arrows pick a reference, Enter goes to it and Escape closes the peek; typing
|
|
@@ -75,7 +70,7 @@ export class PeekFeature {
|
|
|
75
70
|
const { editor, project, uri } = this.language;
|
|
76
71
|
const method = KINDS[kind].method;
|
|
77
72
|
if (!project.service.supports(method, uri)) {
|
|
78
|
-
this.tell(say(KINDS[kind].unavailable));
|
|
73
|
+
this.tell(this.say(KINDS[kind].unavailable));
|
|
79
74
|
return;
|
|
80
75
|
}
|
|
81
76
|
const token = ++this.token;
|
|
@@ -84,7 +79,7 @@ export class PeekFeature {
|
|
|
84
79
|
found = await this.fetch(kind, position);
|
|
85
80
|
} catch (error) {
|
|
86
81
|
if (!(error instanceof StaleResultError)) {
|
|
87
|
-
this.tell(say('failed', { message: error instanceof Error ? error.message : String(error) }));
|
|
82
|
+
this.tell(this.say('failed', { message: error instanceof Error ? error.message : String(error) }));
|
|
88
83
|
}
|
|
89
84
|
return;
|
|
90
85
|
}
|
|
@@ -102,7 +97,7 @@ export class PeekFeature {
|
|
|
102
97
|
) === index
|
|
103
98
|
);
|
|
104
99
|
if (unique.length === 0) {
|
|
105
|
-
this.tell(say(KINDS[kind].none));
|
|
100
|
+
this.tell(this.say(KINDS[kind].none));
|
|
106
101
|
return;
|
|
107
102
|
}
|
|
108
103
|
const uris = [...new Set(unique.map((location) => location.uri))].slice(0, PEEK_READ_FILES);
|
|
@@ -245,4 +240,8 @@ export class PeekFeature {
|
|
|
245
240
|
private tell(title: string): void {
|
|
246
241
|
this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title });
|
|
247
242
|
}
|
|
243
|
+
|
|
244
|
+
private say(key: string, options?: Record<string, unknown>): string {
|
|
245
|
+
return this.language.project.i18n.t(`editor:language.peek.${key}`, options);
|
|
246
|
+
}
|
|
248
247
|
}
|
package/src/project-language.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import i18next, { type i18n } from 'i18next';
|
|
1
2
|
import { fileUriToPath, type ApplyWorkspaceEditResult, type ContentChange, type LanguageService, type WorkspaceEdit } from '@adecore/lsp';
|
|
2
3
|
import type { Editor, EditorPosition } from '@adecore/editor';
|
|
3
4
|
import { NavigationHistory, type Place } from './navigation-history.ts';
|
|
@@ -78,6 +79,7 @@ interface Holder {
|
|
|
78
79
|
export class ProjectLanguage {
|
|
79
80
|
readonly service: LanguageService;
|
|
80
81
|
readonly host: LanguageHost;
|
|
82
|
+
readonly i18n: i18n;
|
|
81
83
|
readonly folder: string;
|
|
82
84
|
readonly problems: ProjectProblems;
|
|
83
85
|
readonly history = new NavigationHistory();
|
|
@@ -90,6 +92,7 @@ export class ProjectLanguage {
|
|
|
90
92
|
constructor(service: LanguageService, host: LanguageHost = {}) {
|
|
91
93
|
this.service = service;
|
|
92
94
|
this.host = host;
|
|
95
|
+
this.i18n = host.i18n ?? i18next;
|
|
93
96
|
this.folder = host.folder ?? '';
|
|
94
97
|
this.files = host.files ?? null;
|
|
95
98
|
this.problems = new ProjectProblems(service, (uri) => this.pathOfLocation(uri) ?? uri);
|
package/src/rename.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import i18next from 'i18next';
|
|
2
1
|
import { StaleResultError, type Location, type PrepareRenameResult, type WorkspaceEdit } from '@adecore/lsp';
|
|
3
2
|
import type { EditorRange } from '@adecore/editor';
|
|
4
3
|
import type { EditorLanguage } from './editor-language.ts';
|
|
@@ -13,10 +12,6 @@ const SAMPLE_LOCATIONS = 8;
|
|
|
13
12
|
const SAMPLE_OTHER_FILES = 3;
|
|
14
13
|
const CONTEXT_LINES = 3;
|
|
15
14
|
|
|
16
|
-
function say(key: string, options?: Record<string, unknown>): string {
|
|
17
|
-
return i18next.t(`editor:language.rename.${key}`, options);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
15
|
interface Session {
|
|
21
16
|
readonly target: RenameTarget;
|
|
22
17
|
readonly original: string;
|
|
@@ -67,7 +62,7 @@ export class RenameFeature {
|
|
|
67
62
|
async start(): Promise<void> {
|
|
68
63
|
const { editor, project, uri } = this.language;
|
|
69
64
|
if (!project.service.supports(METHOD, uri)) {
|
|
70
|
-
this.tell('error', say('unavailable'));
|
|
65
|
+
this.tell('error', this.say('unavailable'));
|
|
71
66
|
return;
|
|
72
67
|
}
|
|
73
68
|
this.cancel();
|
|
@@ -78,7 +73,7 @@ export class RenameFeature {
|
|
|
78
73
|
answer = prepare ? await project.service.prepareRename(uri, caret) : { defaultBehavior: true };
|
|
79
74
|
} catch (error) {
|
|
80
75
|
if (!(error instanceof StaleResultError)) {
|
|
81
|
-
this.tell('error', say('nothing'));
|
|
76
|
+
this.tell('error', this.say('nothing'));
|
|
82
77
|
}
|
|
83
78
|
return;
|
|
84
79
|
}
|
|
@@ -92,7 +87,7 @@ export class RenameFeature {
|
|
|
92
87
|
)
|
|
93
88
|
);
|
|
94
89
|
if (target === null) {
|
|
95
|
-
this.tell('error', say('nothing'));
|
|
90
|
+
this.tell('error', this.say('nothing'));
|
|
96
91
|
return;
|
|
97
92
|
}
|
|
98
93
|
const session: Session = { target, original: editor.textInRange(target.range), edit: null, names: new AbortController() };
|
|
@@ -120,7 +115,7 @@ export class RenameFeature {
|
|
|
120
115
|
return;
|
|
121
116
|
}
|
|
122
117
|
if (edit === null) {
|
|
123
|
-
this.fail(say('refused'));
|
|
118
|
+
this.fail(this.say('refused'));
|
|
124
119
|
return;
|
|
125
120
|
}
|
|
126
121
|
session.edit = edit;
|
|
@@ -140,7 +135,7 @@ export class RenameFeature {
|
|
|
140
135
|
this.publish({ phase: 'preview', busy: false, name: next, files });
|
|
141
136
|
} catch (error) {
|
|
142
137
|
if (this.session === session) {
|
|
143
|
-
this.fail(error instanceof StaleResultError ? say('stale') : error instanceof Error ? error.message : String(error));
|
|
138
|
+
this.fail(error instanceof StaleResultError ? this.say('stale') : error instanceof Error ? error.message : String(error));
|
|
144
139
|
}
|
|
145
140
|
}
|
|
146
141
|
}
|
|
@@ -168,7 +163,7 @@ export class RenameFeature {
|
|
|
168
163
|
if (result.applied) {
|
|
169
164
|
this.finish();
|
|
170
165
|
} else {
|
|
171
|
-
this.fail(result.failureReason ?? say('refused'));
|
|
166
|
+
this.fail(result.failureReason ?? this.say('refused'));
|
|
172
167
|
}
|
|
173
168
|
}
|
|
174
169
|
|
|
@@ -176,7 +171,7 @@ export class RenameFeature {
|
|
|
176
171
|
private fail(message: string): void {
|
|
177
172
|
if (this.session !== null) {
|
|
178
173
|
this.session.edit = null;
|
|
179
|
-
this.publish({ phase: 'input', busy: false, files: [], error: say('failed', { message }) });
|
|
174
|
+
this.publish({ phase: 'input', busy: false, files: [], error: this.say('failed', { message }) });
|
|
180
175
|
}
|
|
181
176
|
}
|
|
182
177
|
|
|
@@ -288,4 +283,8 @@ export class RenameFeature {
|
|
|
288
283
|
}
|
|
289
284
|
});
|
|
290
285
|
}
|
|
286
|
+
|
|
287
|
+
private say(key: string, options?: Record<string, unknown>): string {
|
|
288
|
+
return this.language.project.i18n.t(`editor:language.rename.${key}`, options);
|
|
289
|
+
}
|
|
291
290
|
}
|
package/src/semantic-tokens.ts
CHANGED
|
@@ -33,14 +33,12 @@ export class SemanticTokensFeature {
|
|
|
33
33
|
timers
|
|
34
34
|
);
|
|
35
35
|
const edits = editor.onTextChange(() => this.refresher.later());
|
|
36
|
-
const providers =
|
|
37
|
-
|
|
38
|
-
this.refresher.now();
|
|
39
|
-
}
|
|
36
|
+
const providers = language.onProvidersChanged(() => {
|
|
37
|
+
this.refresher.now();
|
|
40
38
|
});
|
|
41
39
|
language.onDispose(() => {
|
|
42
40
|
edits();
|
|
43
|
-
providers
|
|
41
|
+
providers();
|
|
44
42
|
this.refresher.dispose();
|
|
45
43
|
editor.setSemanticTokens(null);
|
|
46
44
|
});
|
package/src/symbol-picker.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import i18next from 'i18next';
|
|
2
1
|
import { StaleResultError, type Location } from '@adecore/lsp';
|
|
3
2
|
import { basenameOf } from './paths.ts';
|
|
4
3
|
import type { EditorLanguage } from './editor-language.ts';
|
|
@@ -33,7 +32,7 @@ export class SymbolPickerFeature {
|
|
|
33
32
|
async open(): Promise<void> {
|
|
34
33
|
const { project, uri, popups } = this.language;
|
|
35
34
|
if (!project.service.supports('textDocument/documentSymbol', uri)) {
|
|
36
|
-
this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title:
|
|
35
|
+
this.language.project.host.notify?.({ id: TOAST_ID, kind: 'error', title: this.language.project.i18n.t('editor:language.symbols.unavailable') });
|
|
37
36
|
return;
|
|
38
37
|
}
|
|
39
38
|
try {
|
|
@@ -44,7 +43,7 @@ export class SymbolPickerFeature {
|
|
|
44
43
|
this.language.project.host.notify?.({
|
|
45
44
|
id: TOAST_ID,
|
|
46
45
|
kind: 'error',
|
|
47
|
-
title:
|
|
46
|
+
title: this.language.project.i18n.t('editor:language.symbols.failed', { message: error instanceof Error ? error.message : String(error) })
|
|
48
47
|
});
|
|
49
48
|
}
|
|
50
49
|
}
|
package/src/symbols.ts
CHANGED
|
@@ -89,14 +89,12 @@ export class SymbolsFeature {
|
|
|
89
89
|
timers
|
|
90
90
|
);
|
|
91
91
|
const edits = editor.onTextChange(() => this.refresher.later());
|
|
92
|
-
const providers =
|
|
93
|
-
|
|
94
|
-
this.refresher.now();
|
|
95
|
-
}
|
|
92
|
+
const providers = language.onProvidersChanged(() => {
|
|
93
|
+
this.refresher.now();
|
|
96
94
|
});
|
|
97
95
|
language.onDispose(() => {
|
|
98
96
|
edits();
|
|
99
|
-
providers
|
|
97
|
+
providers();
|
|
100
98
|
this.refresher.dispose();
|
|
101
99
|
editor.setBlocks(null);
|
|
102
100
|
});
|
package/src/workspace-edit.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { Editor, EditorContentChange } from '@adecore/editor';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
applyTextEdits,
|
|
4
|
+
fileUriToPath,
|
|
5
|
+
type ApplyWorkspaceEditResult,
|
|
6
|
+
type CreateFile,
|
|
7
|
+
type RenameFile,
|
|
8
|
+
type TextEdit,
|
|
9
|
+
type WorkspaceEdit
|
|
10
|
+
} from '@adecore/lsp';
|
|
3
11
|
import type { DiskText } from './host-types.ts';
|
|
4
12
|
|
|
5
13
|
/* A file that changed without an editor of this project holding it: its new text waits as an unsaved draft. */
|
|
@@ -18,6 +26,8 @@ export interface ProjectFiles {
|
|
|
18
26
|
save(files: readonly StagedFile[]): Promise<string | null>;
|
|
19
27
|
/* Moves a file or folder on the machine, with what is open on it, and tells the servers it moved; the reason it could not, or null. */
|
|
20
28
|
rename(from: string, to: string): Promise<string | null>;
|
|
29
|
+
/* Makes a file that is not there with its text, through the machine; the reason it could not, or null. Without it no edit creates a file. */
|
|
30
|
+
create?(path: string, text: string): Promise<string | null>;
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
export interface WorkspaceEditHost {
|
|
@@ -30,17 +40,20 @@ function refused(failureReason: string): ApplyWorkspaceEditResult {
|
|
|
30
40
|
return { applied: false, failureReason };
|
|
31
41
|
}
|
|
32
42
|
|
|
33
|
-
/* One thing an edit does, in the order it says: text edits of a document, or a file that moves. */
|
|
43
|
+
/* One thing an edit does, in the order it says: text edits of a document, a file that is made, or a file that moves. */
|
|
34
44
|
type Step =
|
|
35
45
|
| { readonly kind: 'edits'; readonly uri: string; readonly edits: TextEdit[] }
|
|
46
|
+
| { readonly kind: 'create'; readonly file: CreateFile }
|
|
36
47
|
| { readonly kind: 'rename'; readonly oldUri: string; readonly newUri: string };
|
|
37
48
|
|
|
38
|
-
/* The steps of an edit; null when it
|
|
49
|
+
/* The steps of an edit; null when it deletes a file, which an edit is not applied with. */
|
|
39
50
|
function stepsOf(edit: WorkspaceEdit): Step[] | null {
|
|
40
51
|
const steps: Step[] = [];
|
|
41
52
|
for (const change of edit.documentChanges ?? []) {
|
|
42
53
|
if ('textDocument' in change) {
|
|
43
54
|
steps.push({ kind: 'edits', uri: change.textDocument.uri, edits: change.edits });
|
|
55
|
+
} else if (change.kind === 'create') {
|
|
56
|
+
steps.push({ kind: 'create', file: change });
|
|
44
57
|
} else if (change.kind === 'rename') {
|
|
45
58
|
steps.push({ kind: 'rename', oldUri: change.oldUri, newUri: change.newUri });
|
|
46
59
|
} else {
|
|
@@ -55,7 +68,7 @@ function stepsOf(edit: WorkspaceEdit): Step[] | null {
|
|
|
55
68
|
return steps;
|
|
56
69
|
}
|
|
57
70
|
|
|
58
|
-
/* The text edits of an edit per document, as the entries a document has in it; null when it
|
|
71
|
+
/* The text edits of an edit per document, as the entries a document has in it; null when it deletes a file. */
|
|
59
72
|
export function entriesOf(edit: WorkspaceEdit): Map<string, TextEdit[][]> | null {
|
|
60
73
|
const steps = stepsOf(edit);
|
|
61
74
|
if (steps === null) {
|
|
@@ -90,19 +103,27 @@ interface PlannedEdits {
|
|
|
90
103
|
readonly disk: DiskText | null;
|
|
91
104
|
}
|
|
92
105
|
|
|
93
|
-
|
|
106
|
+
/* A file to make, with the text every step of the edit leaves in it. */
|
|
107
|
+
interface PlannedCreate {
|
|
108
|
+
readonly kind: 'create';
|
|
109
|
+
readonly path: string;
|
|
110
|
+
text: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
type Planned = PlannedEdits | PlannedCreate | { readonly kind: 'rename'; readonly oldPath: string; readonly newPath: string };
|
|
94
114
|
|
|
95
115
|
/*
|
|
96
116
|
* Applies an LSP workspace edit on the client, step by step in the order it says. A file an editor holds takes
|
|
97
117
|
* its edits as one undo step. Any other file gets its new text as an unsaved draft that a person saves, and
|
|
98
118
|
* nothing is written to disk on a server's word alone, unless the edit also moves a file: the edits and the
|
|
99
|
-
* move are then one change, so every file is saved through the machine and the move is made there.
|
|
100
|
-
* edit is
|
|
119
|
+
* move are then one change, so every file is saved through the machine and the move is made there. A file
|
|
120
|
+
* the edit creates is made through the machine with the text the edit writes into it. The edit is checked
|
|
121
|
+
* against every text first, so one that does not fit changes nothing.
|
|
101
122
|
*/
|
|
102
123
|
export async function applyWorkspaceEdit(edit: WorkspaceEdit, host: WorkspaceEditHost): Promise<ApplyWorkspaceEditResult> {
|
|
103
124
|
const steps = stepsOf(edit);
|
|
104
125
|
if (steps === null) {
|
|
105
|
-
return refused('
|
|
126
|
+
return refused('Deleting files is not supported yet');
|
|
106
127
|
}
|
|
107
128
|
const planned = await plan(steps, host);
|
|
108
129
|
if (typeof planned === 'string') {
|
|
@@ -111,8 +132,8 @@ export async function applyWorkspaceEdit(edit: WorkspaceEdit, host: WorkspaceEdi
|
|
|
111
132
|
const moving = planned.some((item) => item.kind === 'rename');
|
|
112
133
|
const staged: StagedFile[] = [];
|
|
113
134
|
for (const item of planned) {
|
|
114
|
-
if (item.kind === 'rename') {
|
|
115
|
-
const reason = await host.files!.rename(item.oldPath, item.newPath);
|
|
135
|
+
if (item.kind === 'rename' || item.kind === 'create') {
|
|
136
|
+
const reason = item.kind === 'rename' ? await host.files!.rename(item.oldPath, item.newPath) : await host.files!.create!(item.path, item.text);
|
|
116
137
|
if (reason !== null) {
|
|
117
138
|
return refused(reason);
|
|
118
139
|
}
|
|
@@ -154,8 +175,21 @@ async function plan(steps: readonly Step[], host: WorkspaceEditHost): Promise<Pl
|
|
|
154
175
|
const texts = new Map<string, { text: string; disk: DiskText | null }>();
|
|
155
176
|
// A file that has moved is read where it still stands: nothing moves until the plan is made.
|
|
156
177
|
const origins = new Map<string, string>();
|
|
178
|
+
// A file that is made is written once, with what every step leaves in it.
|
|
179
|
+
const creates = new Map<string, PlannedCreate>();
|
|
157
180
|
const planned: Planned[] = [];
|
|
158
181
|
for (const step of steps) {
|
|
182
|
+
if (step.kind === 'create') {
|
|
183
|
+
const made = await planCreate(step.file, host, texts);
|
|
184
|
+
if (typeof made === 'string') {
|
|
185
|
+
return made;
|
|
186
|
+
}
|
|
187
|
+
if (made !== null) {
|
|
188
|
+
creates.set(step.file.uri, made);
|
|
189
|
+
planned.push(made);
|
|
190
|
+
}
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
159
193
|
if (step.kind === 'rename') {
|
|
160
194
|
const oldPath = fileUriToPath(step.oldUri);
|
|
161
195
|
const newPath = fileUriToPath(step.newUri);
|
|
@@ -167,6 +201,15 @@ async function plan(steps: readonly Step[], host: WorkspaceEditHost): Promise<Pl
|
|
|
167
201
|
continue;
|
|
168
202
|
}
|
|
169
203
|
const source = origins.get(step.uri) ?? step.uri;
|
|
204
|
+
const created = creates.get(source);
|
|
205
|
+
if (created !== undefined) {
|
|
206
|
+
try {
|
|
207
|
+
created.text = applyTextEdits(created.text, step.edits);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
return error instanceof Error ? error.message : String(error);
|
|
210
|
+
}
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
170
213
|
if (!texts.has(source)) {
|
|
171
214
|
const editor = host.editorOf(source);
|
|
172
215
|
if (editor) {
|
|
@@ -192,3 +235,20 @@ async function plan(steps: readonly Step[], host: WorkspaceEditHost): Promise<Pl
|
|
|
192
235
|
}
|
|
193
236
|
return planned;
|
|
194
237
|
}
|
|
238
|
+
|
|
239
|
+
/* A file that is not there yet, or null when it is and the create says to leave it. Making one over a file that is there is not supported. */
|
|
240
|
+
async function planCreate(
|
|
241
|
+
file: CreateFile,
|
|
242
|
+
host: WorkspaceEditHost,
|
|
243
|
+
texts: ReadonlyMap<string, { text: string; disk: DiskText | null }>
|
|
244
|
+
): Promise<PlannedCreate | null | string> {
|
|
245
|
+
const path = fileUriToPath(file.uri);
|
|
246
|
+
if (path === null || host.files?.create === undefined) {
|
|
247
|
+
return `Creating ${file.uri} is not supported here`;
|
|
248
|
+
}
|
|
249
|
+
const there = texts.has(file.uri) || host.editorOf(file.uri) !== undefined || (await host.files.read(path)) !== null;
|
|
250
|
+
if (!there) {
|
|
251
|
+
return { kind: 'create', path, text: '' };
|
|
252
|
+
}
|
|
253
|
+
return file.options?.ignoreIfExists === true && file.options.overwrite !== true ? null : `${path} already exists`;
|
|
254
|
+
}
|