@lapis-notes/api 0.1.3 → 0.2.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/CHANGELOG.md +304 -13
- package/README.md +19 -0
- package/dist/__tests__/fixtures/indexed-mutations.json +6862 -0
- package/dist/__tests__/fixtures/memory-storage.json +309 -0
- package/dist/__tests__/fixtures/native-facade-contract.json +3224 -0
- package/dist/__tests__/fixtures/projection-query.json +195 -0
- package/dist/__tests__/fixtures/projection-values.json +60 -0
- package/dist/__tests__/fixtures/search-preparation.json +1513 -0
- package/dist/__tests__/fixtures/search-retrieval.json +15409 -0
- package/dist/__tests__/fixtures/search-semantic-model.json +3691 -0
- package/dist/__tests__/fixtures/search-semantic-retrieval.json +6603 -0
- package/dist/__tests__/fixtures/search-storage.json +409 -0
- package/dist/__tests__/fixtures/task-projection.json +428 -0
- package/dist/__tests__/indexed-mutation-support.js +262 -0
- package/dist/__tests__/projection-query-support.js +40 -0
- package/dist/__tests__/search-retrieval-support.js +47 -0
- package/dist/app-database-host.d.ts +1 -0
- package/dist/app-shell-plugin-persistence.d.ts +3 -2
- package/dist/app-shell-plugin-persistence.js +37 -3
- package/dist/cache.svelte.d.ts +8 -1
- package/dist/cache.svelte.js +103 -5
- package/dist/command.svelte.js +29 -2
- package/dist/components/editor/editor.css +4 -0
- package/dist/components/editor/editor.svelte +24 -11
- package/dist/components/editor/embedded-editor-surface.js +3 -1
- package/dist/components/editor/inline-title-alignment.d.ts +3 -0
- package/dist/components/editor/inline-title-alignment.js +57 -0
- package/dist/configuration.svelte.d.ts +270 -206
- package/dist/configuration.svelte.js +161 -86
- package/dist/context.svelte.d.ts +23 -3
- package/dist/context.svelte.js +68 -30
- package/dist/diagnostics.d.ts +8 -0
- package/dist/diagnostics.js +6 -0
- package/dist/editor-save-context.d.ts +35 -0
- package/dist/editor-save-context.js +132 -0
- package/dist/editor-view-registry.d.ts +10 -0
- package/dist/editor-view-registry.js +44 -4
- package/dist/editor.svelte.d.ts +19 -1
- package/dist/editor.svelte.js +528 -55
- package/dist/embed-registry.d.ts +10 -0
- package/dist/embed-registry.js +15 -0
- package/dist/file-editing-failures.d.ts +2 -0
- package/dist/file-editing-failures.js +29 -0
- package/dist/file-editing-native-issues.d.ts +19 -0
- package/dist/file-editing-native-issues.js +65 -0
- package/dist/file-editing-sessions.d.ts +76 -0
- package/dist/file-editing-sessions.js +367 -0
- package/dist/file-manager.d.ts +9 -1
- package/dist/file-manager.js +45 -3
- package/dist/file-state-store.d.ts +5 -0
- package/dist/file-state-store.js +34 -0
- package/dist/file-text-conflicts.d.ts +25 -0
- package/dist/file-text-conflicts.js +35 -0
- package/dist/file-text-mutation.d.ts +13 -0
- package/dist/file-text-mutation.js +100 -0
- package/dist/file-text-persistence.d.ts +36 -0
- package/dist/file-text-persistence.js +234 -0
- package/dist/file-text-state-writer.d.ts +34 -0
- package/dist/file-text-state-writer.js +183 -0
- package/dist/files-configuration.d.ts +20 -0
- package/dist/files-configuration.js +47 -0
- package/dist/generated/plugin-host-modules.schema.generated.d.ts +114 -34
- package/dist/generated/plugin-host-modules.schema.generated.js +132 -34
- package/dist/glob.d.ts +3 -0
- package/dist/glob.js +2 -1
- package/dist/icons/changes.d.ts +3 -0
- package/dist/icons/changes.js +16 -0
- package/dist/icons/index.d.ts +5 -1
- package/dist/icons/index.js +44 -3
- package/dist/icons/registry.d.ts +35 -0
- package/dist/icons/registry.js +154 -0
- package/dist/icons/safe-svg.d.ts +2 -0
- package/dist/icons/safe-svg.js +44 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +4 -1
- package/dist/lapis-extension.d.ts +6 -2
- package/dist/lapis-extension.js +2 -0
- package/dist/metadata-store.d.ts +23 -0
- package/dist/metadata-store.js +1 -0
- package/dist/metadata.svelte.js +45 -2
- package/dist/native-background-indexing.d.ts +48 -0
- package/dist/native-background-indexing.js +208 -0
- package/dist/native-database-protocol.d.ts +29 -0
- package/dist/native-database-protocol.js +1 -0
- package/dist/native-editing-intent-lease.d.ts +17 -0
- package/dist/native-editing-intent-lease.js +188 -0
- package/dist/native-editing-intents-protocol.d.ts +70 -0
- package/dist/native-editing-intents-protocol.js +1 -0
- package/dist/native-editing-intents.d.ts +8 -0
- package/dist/native-editing-intents.js +1 -0
- package/dist/native-editing-problems.d.ts +3 -0
- package/dist/native-editing-problems.js +180 -0
- package/dist/native-editing-recovery.d.ts +24 -0
- package/dist/native-editing-recovery.js +198 -0
- package/dist/native-editing-session.d.ts +51 -0
- package/dist/native-editing-session.js +251 -0
- package/dist/native-history-index-protocol.d.ts +80 -0
- package/dist/native-history-index-protocol.js +1 -0
- package/dist/native-history-index.d.ts +6 -0
- package/dist/native-history-index.js +1 -0
- package/dist/native-indexing-protocol.d.ts +63 -0
- package/dist/native-indexing-protocol.js +1 -0
- package/dist/native-participant-protocol.d.ts +67 -0
- package/dist/native-participant-protocol.js +3 -0
- package/dist/native-queue-protocol.d.ts +73 -0
- package/dist/native-queue-protocol.js +3 -0
- package/dist/native-tasks-protocol.d.ts +264 -0
- package/dist/native-tasks-protocol.js +1 -0
- package/dist/native-tasks.d.ts +35 -0
- package/dist/native-tasks.js +79 -0
- package/dist/native-turn-protocol.d.ts +81 -0
- package/dist/native-turn-protocol.js +1 -0
- package/dist/native-turns.d.ts +29 -0
- package/dist/native-turns.js +2 -0
- package/dist/plugin-asset-server.js +3 -2
- package/dist/plugin-dependency-resolver.d.ts +1 -1
- package/dist/plugin-distribution/compatibility.d.ts +1 -1
- package/dist/plugin-distribution/compatibility.js +10 -5
- package/dist/plugin-distribution/installed-plugin-state.js +2 -1
- package/dist/plugin-distribution/installer.d.ts +6 -0
- package/dist/plugin-distribution/installer.js +90 -37
- package/dist/plugin-distribution/manager.d.ts +4 -1
- package/dist/plugin-distribution/manager.js +12 -5
- package/dist/plugin-distribution/registry-cache.js +2 -1
- package/dist/plugin-distribution/registry-source-state.js +2 -1
- package/dist/plugin-distribution/schemas.d.ts +26 -8
- package/dist/plugin-distribution/schemas.js +2 -1
- package/dist/plugin-distribution/types.d.ts +4 -0
- package/dist/plugin-manager.d.ts +21 -5
- package/dist/plugin-manager.js +228 -64
- package/dist/plugin-runtime-entry.d.ts +1 -1
- package/dist/plugin-runtime-entry.js +16 -0
- package/dist/plugin.d.ts +19 -3
- package/dist/plugin.js +110 -44
- package/dist/registry-schema/catalog-v1.d.ts +60 -9
- package/dist/registry-schema/catalog-v1.js +2 -1
- package/dist/registry-schema/catalog-v2.d.ts +60 -9
- package/dist/registry-schema/catalog-v2.js +1 -1
- package/dist/search-result-provider.d.ts +95 -0
- package/dist/search-result-provider.js +42 -0
- package/dist/status-bar.svelte.d.ts +26 -20
- package/dist/status-bar.svelte.js +1 -0
- package/dist/storage/app-database.d.ts +9 -23
- package/dist/storage/app-database.js +41 -34
- package/dist/storage/desktop-native.d.ts +5 -3
- package/dist/storage/desktop-native.js +59 -6
- package/dist/storage/directory-watcher.d.ts +27 -0
- package/dist/storage/directory-watcher.js +201 -0
- package/dist/storage/fs.d.ts +14 -27
- package/dist/storage/fs.js +2 -168
- package/dist/storage/index-projection.js +33 -15
- package/dist/storage/memory-vault-adapter.d.ts +2 -0
- package/dist/storage/memory-vault-adapter.js +14 -1
- package/dist/storage/path.d.ts +6 -2
- package/dist/storage/path.js +8 -2
- package/dist/storage/projection-query-candidates.d.ts +3 -0
- package/dist/storage/projection-query-candidates.js +69 -0
- package/dist/storage/remote-app-database.d.ts +2 -2
- package/dist/storage/remote-app-database.js +1 -1
- package/dist/storage/runtime.d.ts +3 -0
- package/dist/storage/runtime.js +3 -0
- package/dist/storage/search-embedding-record.d.ts +3 -0
- package/dist/storage/search-embedding-record.js +18 -0
- package/dist/storage/turso-app-database.d.ts +2 -0
- package/dist/storage/turso-app-database.js +67 -30
- package/dist/storage/vault-listing.d.ts +3 -0
- package/dist/storage/vault-listing.js +36 -0
- package/dist/storage/vault-reconciliation.d.ts +19 -0
- package/dist/storage/vault-reconciliation.js +137 -0
- package/dist/storage/vault-session.d.ts +8 -0
- package/dist/storage/vault-session.js +10 -2
- package/dist/storage/vault.svelte.d.ts +53 -4
- package/dist/storage/vault.svelte.js +223 -87
- package/dist/sync-editor-flush.d.ts +6 -0
- package/dist/sync-editor-flush.js +22 -0
- package/dist/sync-host-local.d.ts +15 -0
- package/dist/sync-host-local.js +228 -0
- package/dist/sync-host-security.d.ts +24 -9
- package/dist/sync-host-security.js +47 -14
- package/dist/sync-host.d.ts +33 -4
- package/dist/sync-host.js +160 -10
- package/dist/sync-repository.d.ts +106 -0
- package/dist/sync-repository.js +18 -0
- package/dist/vault-change-views.d.ts +4 -0
- package/dist/vault-change-views.js +43 -0
- package/dist/vault-changes.d.ts +37 -0
- package/dist/vault-changes.js +228 -0
- package/dist/view.svelte.d.ts +182 -149
- package/dist/view.svelte.js +39 -21
- package/dist/workspace-close-guard.d.ts +3 -0
- package/dist/workspace-close-guard.js +35 -0
- package/dist/workspace-host-internal.js +10 -2
- package/dist/workspace-layout-normalizer.d.ts +4 -2
- package/dist/workspace-layout-normalizer.js +5 -5
- package/dist/workspace-view-document.d.ts +3 -0
- package/dist/workspace-view-document.js +12 -0
- package/dist/workspace.svelte.d.ts +13 -2
- package/dist/workspace.svelte.js +371 -82
- package/package.json +61 -8
package/dist/editor.svelte.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Annotation, EditorState, Transaction, StateField, EditorSelection as CodeMirrorSelection, } from "@codemirror/state";
|
|
1
|
+
import { Annotation, Compartment, EditorState, StateEffect, Transaction, StateField, EditorSelection as CodeMirrorSelection, } from "@codemirror/state";
|
|
2
2
|
import { EditorView } from "@codemirror/view";
|
|
3
3
|
import { redo as redoCommand, undo as undoCommand } from "@codemirror/commands";
|
|
4
4
|
import { EventDispatcher } from "./events";
|
|
5
|
-
import
|
|
5
|
+
import { tick } from "svelte";
|
|
6
|
+
import { EditorSaveContext, } from "./editor-save-context";
|
|
6
7
|
import { resolveApplication } from "./application-compatibility";
|
|
8
|
+
import { FileTextPersistence } from "./file-text-persistence";
|
|
7
9
|
export const editorViewField = StateField.define({
|
|
8
10
|
create() {
|
|
9
11
|
return {
|
|
@@ -49,26 +51,35 @@ export function createEditorState(editor, doc, extensions = [], selection) {
|
|
|
49
51
|
return EditorState.create({
|
|
50
52
|
doc,
|
|
51
53
|
selection,
|
|
52
|
-
extensions:
|
|
53
|
-
editorViewField.init(() => ({
|
|
54
|
-
get app() {
|
|
55
|
-
return editor.app;
|
|
56
|
-
},
|
|
57
|
-
editor,
|
|
58
|
-
get file() {
|
|
59
|
-
return editor.file;
|
|
60
|
-
},
|
|
61
|
-
})),
|
|
62
|
-
EditorView.updateListener.of((v) => {
|
|
63
|
-
if (v.docChanged &&
|
|
64
|
-
!v.transactions.some((it) => it.annotation(syncAnnotation))) {
|
|
65
|
-
editor.queueChange(v.state.doc.toString());
|
|
66
|
-
}
|
|
67
|
-
}),
|
|
68
|
-
...extensions,
|
|
69
|
-
],
|
|
54
|
+
extensions: editorStateExtensions(editor, extensions),
|
|
70
55
|
});
|
|
71
56
|
}
|
|
57
|
+
function editorStateExtensions(editor, extensions) {
|
|
58
|
+
return [
|
|
59
|
+
editorViewField.init(() => ({
|
|
60
|
+
get app() {
|
|
61
|
+
return editor.app;
|
|
62
|
+
},
|
|
63
|
+
editor,
|
|
64
|
+
get file() {
|
|
65
|
+
return editor.file;
|
|
66
|
+
},
|
|
67
|
+
})),
|
|
68
|
+
editor.protectionExtensions(),
|
|
69
|
+
EditorState.transactionFilter.of((transaction) => transaction.docChanged &&
|
|
70
|
+
editor.editingProtected &&
|
|
71
|
+
!transaction.annotation(syncAnnotation)
|
|
72
|
+
? []
|
|
73
|
+
: transaction),
|
|
74
|
+
EditorView.updateListener.of((v) => {
|
|
75
|
+
if (v.docChanged &&
|
|
76
|
+
!v.transactions.some((it) => it.annotation(syncAnnotation))) {
|
|
77
|
+
editor.queueChange(v.state.doc.toString());
|
|
78
|
+
}
|
|
79
|
+
}),
|
|
80
|
+
...extensions,
|
|
81
|
+
];
|
|
82
|
+
}
|
|
72
83
|
export function createEditor(editor, doc, ...extensions) {
|
|
73
84
|
return new EditorView({
|
|
74
85
|
doc,
|
|
@@ -97,21 +108,380 @@ export class Editor extends EventDispatcher {
|
|
|
97
108
|
data;
|
|
98
109
|
extensions;
|
|
99
110
|
view;
|
|
100
|
-
file = $state(null);
|
|
101
|
-
persistence = "vault";
|
|
111
|
+
#file = $state.raw(null);
|
|
112
|
+
#persistence = "vault";
|
|
113
|
+
#externalWriter;
|
|
114
|
+
#editingProtected = $state(0);
|
|
115
|
+
#protection = new Compartment();
|
|
116
|
+
#documents = new Set();
|
|
117
|
+
#document;
|
|
102
118
|
id = crypto.randomUUID();
|
|
103
119
|
destroyed = false;
|
|
104
120
|
application;
|
|
105
|
-
|
|
106
|
-
|
|
121
|
+
#writeTail = Promise.resolve();
|
|
122
|
+
#sequence = 0;
|
|
123
|
+
#completedSequence = 0;
|
|
124
|
+
#documentGeneration = 0;
|
|
125
|
+
#fileReadGeneration = 0;
|
|
126
|
+
#refreshError;
|
|
127
|
+
#loading;
|
|
128
|
+
#controlledLoad = false;
|
|
129
|
+
get file() {
|
|
130
|
+
return this.#file;
|
|
131
|
+
}
|
|
132
|
+
set file(file) {
|
|
133
|
+
if (file === this.#file && file?.path === this.#document?.path)
|
|
134
|
+
return;
|
|
135
|
+
this.#assertWritable();
|
|
136
|
+
const next = this.#createDocument(this.app, file, this.#persistence, this.#externalWriter);
|
|
137
|
+
this.#retireDocument();
|
|
138
|
+
this.#file = file;
|
|
139
|
+
this.#document = next;
|
|
140
|
+
this.#documentGeneration++;
|
|
141
|
+
}
|
|
142
|
+
get persistence() {
|
|
143
|
+
return this.#persistence;
|
|
144
|
+
}
|
|
145
|
+
set persistence(value) {
|
|
146
|
+
if (value === this.#persistence)
|
|
107
147
|
return;
|
|
148
|
+
this.#assertWritable();
|
|
149
|
+
const next = this.#createDocument(this.app, this.#file, value, this.#externalWriter);
|
|
150
|
+
this.#retireDocument();
|
|
151
|
+
this.#persistence = value;
|
|
152
|
+
this.#document = next;
|
|
153
|
+
this.#documentGeneration++;
|
|
154
|
+
}
|
|
155
|
+
/** Capture an explicit asynchronous owner for external-mode persistence. */
|
|
156
|
+
setExternalPersistenceWriter(writer) {
|
|
157
|
+
if (writer === this.#externalWriter)
|
|
158
|
+
return this;
|
|
159
|
+
this.#assertWritable();
|
|
160
|
+
const next = this.#createDocument(this.app, this.#file, this.#persistence, writer);
|
|
161
|
+
this.#retireDocument();
|
|
162
|
+
this.#externalWriter = writer;
|
|
163
|
+
this.#document = next;
|
|
164
|
+
this.#documentGeneration++;
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
#assertWritable() {
|
|
168
|
+
if (this.destroyed)
|
|
169
|
+
throw new Error("Editor is destroyed");
|
|
170
|
+
if (this.editingProtected)
|
|
171
|
+
throw new Error("Editor editing is protected");
|
|
172
|
+
}
|
|
173
|
+
#retireDocument() {
|
|
174
|
+
this.#document?.saves.retire();
|
|
175
|
+
}
|
|
176
|
+
#serializeWrite(run) {
|
|
177
|
+
const result = this.#writeTail.then(run);
|
|
178
|
+
this.#writeTail = result.catch(() => undefined);
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
#createDocument(application, file, persistence, writer) {
|
|
182
|
+
const initialText = this.data;
|
|
183
|
+
const document = {
|
|
184
|
+
application,
|
|
185
|
+
file,
|
|
186
|
+
path: file?.path,
|
|
187
|
+
persistence,
|
|
188
|
+
writer,
|
|
189
|
+
};
|
|
190
|
+
document.saves = new EditorSaveContext({
|
|
191
|
+
unownedExternal: persistence === "external" && !writer,
|
|
192
|
+
serialize: (run) => this.#serializeWrite(run),
|
|
193
|
+
persist: async (snapshot) => {
|
|
194
|
+
if (document.unregister?.native)
|
|
195
|
+
await document.unregister.ready();
|
|
196
|
+
if (file?.path !== document.path)
|
|
197
|
+
throw new Error("Editor persistence target changed");
|
|
198
|
+
if (document.text)
|
|
199
|
+
await document.text.persist(snapshot.content);
|
|
200
|
+
else if (persistence === "vault" && file)
|
|
201
|
+
await application.vault.modify(file, snapshot.content);
|
|
202
|
+
else if (persistence === "external" && writer)
|
|
203
|
+
await writer({ application, file, content: snapshot.content });
|
|
204
|
+
},
|
|
205
|
+
saved: (snapshot) => {
|
|
206
|
+
this.#completedSequence = Math.max(this.#completedSequence, snapshot.sequence);
|
|
207
|
+
if (!this.destroyed &&
|
|
208
|
+
this.#document === document &&
|
|
209
|
+
this.app === application &&
|
|
210
|
+
this.file === file &&
|
|
211
|
+
snapshot.documentGeneration === this.#documentGeneration) {
|
|
212
|
+
this.data = snapshot.content;
|
|
213
|
+
if (snapshot.content === this.getValue())
|
|
214
|
+
this.onChange(snapshot.content);
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
released: () => {
|
|
218
|
+
document.unregister?.();
|
|
219
|
+
this.#documents.delete(document);
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
if (persistence === "vault" &&
|
|
223
|
+
file &&
|
|
224
|
+
application.vault.supportsConditionalTextWrites) {
|
|
225
|
+
document.text = new FileTextPersistence({
|
|
226
|
+
vault: application.vault,
|
|
227
|
+
file,
|
|
228
|
+
baseline: this.data,
|
|
229
|
+
conflicts: application.fileEditingSessions.textConflicts,
|
|
230
|
+
local: () => document.saves.latest?.content ?? this.data,
|
|
231
|
+
protect: () => ({
|
|
232
|
+
release: !this.destroyed && this.#document === document
|
|
233
|
+
? this.#holdEditing()
|
|
234
|
+
: () => undefined,
|
|
235
|
+
saved: () => {
|
|
236
|
+
const snapshot = document.saves.latest;
|
|
237
|
+
if (!snapshot)
|
|
238
|
+
throw new Error("No retained editor snapshot");
|
|
239
|
+
document.saves.accept(snapshot);
|
|
240
|
+
},
|
|
241
|
+
reset: (contents) => {
|
|
242
|
+
if (!this.destroyed && this.#document === document) {
|
|
243
|
+
this.#replaceContent(contents);
|
|
244
|
+
this.onChange(contents);
|
|
245
|
+
}
|
|
246
|
+
document.saves.discard();
|
|
247
|
+
},
|
|
248
|
+
}),
|
|
249
|
+
settle: async () => {
|
|
250
|
+
await document.loading;
|
|
251
|
+
try {
|
|
252
|
+
await document.saves.flush(false);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
if (!document.text?.hasConflict)
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
serialize: (run) => this.#serializeWrite(run),
|
|
260
|
+
});
|
|
108
261
|
}
|
|
109
|
-
if (
|
|
110
|
-
|
|
262
|
+
if (file) {
|
|
263
|
+
if (!application.fileEditingSessions)
|
|
264
|
+
throw new Error("App has no file editing-session registry");
|
|
265
|
+
document.unregister = application.fileEditingSessions.register(file.path, {
|
|
266
|
+
native: {
|
|
267
|
+
hold: () => this.#holdEditing(),
|
|
268
|
+
savedText: () => document.text
|
|
269
|
+
? document.text.baseline
|
|
270
|
+
: (document.saves.latest?.content ?? initialText),
|
|
271
|
+
refreshSavedText: (contents) => {
|
|
272
|
+
if (document.loading || document.saves.dirty || !document.text)
|
|
273
|
+
throw new Error("Native editor refresh requires clean conditional text authority");
|
|
274
|
+
document.text.acceptBaseline(contents);
|
|
275
|
+
if (!this.destroyed &&
|
|
276
|
+
this.#document === document &&
|
|
277
|
+
contents !== null) {
|
|
278
|
+
if (this.getValue() !== contents)
|
|
279
|
+
this.#replaceContent(contents);
|
|
280
|
+
this.data = contents;
|
|
281
|
+
this.onChange(contents);
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
get dirty() {
|
|
286
|
+
return !!document.loading || document.saves.dirty;
|
|
287
|
+
},
|
|
288
|
+
flush: async () => {
|
|
289
|
+
await document.loading;
|
|
290
|
+
await document.saves.flush();
|
|
291
|
+
},
|
|
292
|
+
protect: () => this.#protectDocument(document),
|
|
293
|
+
});
|
|
111
294
|
}
|
|
112
|
-
this.
|
|
113
|
-
|
|
114
|
-
}
|
|
295
|
+
this.#documents.add(document);
|
|
296
|
+
return document;
|
|
297
|
+
}
|
|
298
|
+
#currentDocument() {
|
|
299
|
+
return (this.#document ??= this.#createDocument(this.app, this.file, this.persistence, this.#externalWriter));
|
|
300
|
+
}
|
|
301
|
+
captureChange(content) {
|
|
302
|
+
return {
|
|
303
|
+
content,
|
|
304
|
+
documentGeneration: this.#documentGeneration,
|
|
305
|
+
sequence: ++this.#sequence,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
get hasUnsavedChanges() {
|
|
309
|
+
return ((!this.destroyed && this.getValue() !== this.data) ||
|
|
310
|
+
[...this.#documents].some((document) => document.saves.dirty));
|
|
311
|
+
}
|
|
312
|
+
get editingProtected() {
|
|
313
|
+
return this.destroyed || this.#editingProtected > 0;
|
|
314
|
+
}
|
|
315
|
+
/** Native CodeMirror protection, retained whenever editor state is rebuilt. */
|
|
316
|
+
protectionExtensions() {
|
|
317
|
+
return this.#protection.of([
|
|
318
|
+
EditorState.readOnly.of(this.editingProtected),
|
|
319
|
+
EditorView.editable.of(!this.editingProtected),
|
|
320
|
+
]);
|
|
321
|
+
}
|
|
322
|
+
#holdEditing() {
|
|
323
|
+
this.#editingProtected++;
|
|
324
|
+
this.#updateProtection();
|
|
325
|
+
let held = true;
|
|
326
|
+
return () => {
|
|
327
|
+
if (!held)
|
|
328
|
+
return;
|
|
329
|
+
held = false;
|
|
330
|
+
this.#editingProtected--;
|
|
331
|
+
this.#updateProtection();
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
#updateProtection() {
|
|
335
|
+
if (!this.destroyed)
|
|
336
|
+
this.view.dispatch({
|
|
337
|
+
effects: this.#protection.reconfigure([
|
|
338
|
+
EditorState.readOnly.of(this.editingProtected),
|
|
339
|
+
EditorView.editable.of(!this.editingProtected),
|
|
340
|
+
]),
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
async #protectDocument(document) {
|
|
344
|
+
if (document.persistence === "external" && !document.writer)
|
|
345
|
+
throw new Error("External editor persistence requires an explicit writer for protection");
|
|
346
|
+
const release = !this.destroyed && (this.#document === document || !!document.loading)
|
|
347
|
+
? this.#holdEditing()
|
|
348
|
+
: () => undefined;
|
|
349
|
+
try {
|
|
350
|
+
await tick();
|
|
351
|
+
await document.loading;
|
|
352
|
+
await document.saves.flush();
|
|
353
|
+
return release;
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
release();
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/** Protect the current document and drain its captured persistence. */
|
|
361
|
+
protect() {
|
|
362
|
+
if (this.destroyed)
|
|
363
|
+
return Promise.reject(new Error("Editor is destroyed"));
|
|
364
|
+
return this.#protectDocument(this.#currentDocument());
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Register and protect a target, then publish its file and contents
|
|
368
|
+
* atomically.
|
|
369
|
+
*/
|
|
370
|
+
async loadDocument(file, read, publish) {
|
|
371
|
+
if (this.destroyed)
|
|
372
|
+
throw new Error("Editor is destroyed");
|
|
373
|
+
if (this.#editingProtected > (this.#loading ? 1 : 0))
|
|
374
|
+
throw new Error("Editor editing is protected");
|
|
375
|
+
const application = this.app, path = file.path;
|
|
376
|
+
// Admission failure leaves any previous load and binding untouched.
|
|
377
|
+
const document = this.#createDocument(application, file, this.persistence, this.#externalWriter);
|
|
378
|
+
const release = this.#holdEditing();
|
|
379
|
+
let cancelRead;
|
|
380
|
+
const cancellation = new Promise((resolve) => {
|
|
381
|
+
cancelRead = resolve;
|
|
382
|
+
});
|
|
383
|
+
let finish;
|
|
384
|
+
document.loading = new Promise((resolve) => {
|
|
385
|
+
finish = resolve;
|
|
386
|
+
});
|
|
387
|
+
let canceled = false, completed = false;
|
|
388
|
+
const load = {
|
|
389
|
+
cancel: () => {
|
|
390
|
+
if (completed || canceled)
|
|
391
|
+
return;
|
|
392
|
+
canceled = true;
|
|
393
|
+
cancelRead({ canceled: true });
|
|
394
|
+
document.loading = undefined;
|
|
395
|
+
document.saves.retire();
|
|
396
|
+
finish();
|
|
397
|
+
release();
|
|
398
|
+
if (this.#loading === load)
|
|
399
|
+
this.#loading = undefined;
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
this.#loading?.cancel();
|
|
403
|
+
this.#loading = load;
|
|
404
|
+
let published = false;
|
|
405
|
+
try {
|
|
406
|
+
if (document.unregister?.native) {
|
|
407
|
+
const admission = await Promise.race([
|
|
408
|
+
document.unregister.ready().then(() => ({ ready: true })),
|
|
409
|
+
cancellation,
|
|
410
|
+
]);
|
|
411
|
+
if ("canceled" in admission || canceled)
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
const drained = await Promise.race([
|
|
415
|
+
this.flushChanges().then(() => ({ drained: true })),
|
|
416
|
+
cancellation,
|
|
417
|
+
]);
|
|
418
|
+
if ("canceled" in drained || canceled)
|
|
419
|
+
return false;
|
|
420
|
+
const result = await Promise.race([
|
|
421
|
+
Promise.resolve()
|
|
422
|
+
.then(read)
|
|
423
|
+
.then((contents) => ({ contents })),
|
|
424
|
+
cancellation,
|
|
425
|
+
]);
|
|
426
|
+
if ("canceled" in result || canceled)
|
|
427
|
+
return false;
|
|
428
|
+
if (this.destroyed || this.app !== application || file.path !== path)
|
|
429
|
+
throw new Error("Editor document load authority changed");
|
|
430
|
+
const previous = {
|
|
431
|
+
document: this.#document,
|
|
432
|
+
file: this.#file,
|
|
433
|
+
data: this.data,
|
|
434
|
+
state: this.view.state,
|
|
435
|
+
extensions: this.extensions,
|
|
436
|
+
};
|
|
437
|
+
this.#document = document;
|
|
438
|
+
this.#file = file;
|
|
439
|
+
try {
|
|
440
|
+
this.#controlledLoad = true;
|
|
441
|
+
this.setValue(result.contents);
|
|
442
|
+
publish?.(result.contents);
|
|
443
|
+
if (this.destroyed || canceled)
|
|
444
|
+
throw new Error("Editor document load canceled during publication");
|
|
445
|
+
if (this.app !== application || file.path !== path)
|
|
446
|
+
throw new Error("Editor document load authority changed");
|
|
447
|
+
published = true;
|
|
448
|
+
previous.document?.saves.retire();
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
this.#document = previous.document;
|
|
452
|
+
this.#file = previous.file;
|
|
453
|
+
this.data = previous.data;
|
|
454
|
+
this.extensions = previous.extensions;
|
|
455
|
+
this.#documentGeneration++;
|
|
456
|
+
if (!this.destroyed)
|
|
457
|
+
this.view.setState(previous.state);
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
finally {
|
|
461
|
+
this.#controlledLoad = false;
|
|
462
|
+
}
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
finally {
|
|
466
|
+
if (!canceled) {
|
|
467
|
+
completed = true;
|
|
468
|
+
document.loading = undefined;
|
|
469
|
+
if (!published)
|
|
470
|
+
document.saves.retire();
|
|
471
|
+
finish();
|
|
472
|
+
release();
|
|
473
|
+
}
|
|
474
|
+
if (this.#loading === load)
|
|
475
|
+
this.#loading = undefined;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/** Cancel a pending publication while observing any late read result. */
|
|
479
|
+
cancelDocumentLoad() {
|
|
480
|
+
this.#loading?.cancel();
|
|
481
|
+
}
|
|
482
|
+
get refreshError() {
|
|
483
|
+
return this.#refreshError;
|
|
484
|
+
}
|
|
115
485
|
constructor(data = "", extensions = [], application) {
|
|
116
486
|
super();
|
|
117
487
|
this.data = data;
|
|
@@ -121,19 +491,32 @@ export class Editor extends EventDispatcher {
|
|
|
121
491
|
}
|
|
122
492
|
/** Bind this editor to the application that owns its view. */
|
|
123
493
|
bindApplication(application) {
|
|
494
|
+
if (this.application === application)
|
|
495
|
+
return this;
|
|
496
|
+
this.#assertWritable();
|
|
497
|
+
const next = this.#createDocument(application, this.#file, this.#persistence, this.#externalWriter);
|
|
498
|
+
this.#retireDocument();
|
|
124
499
|
this.application = application;
|
|
500
|
+
this.#document = next;
|
|
501
|
+
this.#documentGeneration++;
|
|
125
502
|
return this;
|
|
126
503
|
}
|
|
127
504
|
/** The owning application, with the global alias retained as fallback. */
|
|
128
505
|
get app() {
|
|
129
506
|
return resolveApplication(this.application);
|
|
130
507
|
}
|
|
131
|
-
save =
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return
|
|
136
|
-
},
|
|
508
|
+
save = Object.assign(() => {
|
|
509
|
+
this.#assertWritable();
|
|
510
|
+
const document = this.#currentDocument();
|
|
511
|
+
document.unregister?.changed();
|
|
512
|
+
return document.saves.queue(this.captureChange(this.getValue()), true);
|
|
513
|
+
}, {
|
|
514
|
+
flush: () => this.#document?.saves.flushSave(),
|
|
515
|
+
cancel: () => {
|
|
516
|
+
this.#assertWritable();
|
|
517
|
+
this.#document?.saves.cancelSave();
|
|
518
|
+
},
|
|
519
|
+
});
|
|
137
520
|
get cm() {
|
|
138
521
|
return this.view;
|
|
139
522
|
}
|
|
@@ -142,15 +525,41 @@ export class Editor extends EventDispatcher {
|
|
|
142
525
|
}
|
|
143
526
|
/** Queue the latest editor contents for persistence/change notification. */
|
|
144
527
|
queueChange(data) {
|
|
145
|
-
this
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
528
|
+
this.#assertWritable();
|
|
529
|
+
const document = this.#currentDocument();
|
|
530
|
+
document.unregister?.changed();
|
|
531
|
+
document.saves.queue(this.captureChange(data));
|
|
532
|
+
document.text?.updateLocal(data);
|
|
533
|
+
}
|
|
534
|
+
/** Drain every captured document, including failed or retired saves. */
|
|
535
|
+
async flushChanges() {
|
|
536
|
+
const errors = new Map();
|
|
537
|
+
const visited = new Set();
|
|
538
|
+
let sequence, generation;
|
|
539
|
+
do {
|
|
540
|
+
sequence = this.#sequence;
|
|
541
|
+
generation = this.#documentGeneration;
|
|
542
|
+
const documents = [...this.#documents];
|
|
543
|
+
const results = await Promise.allSettled(documents.map((document) => {
|
|
544
|
+
const retry = !visited.has(document);
|
|
545
|
+
visited.add(document);
|
|
546
|
+
return document.saves.flush(retry);
|
|
547
|
+
}));
|
|
548
|
+
results.forEach((result, index) => {
|
|
549
|
+
if (result.status === "rejected")
|
|
550
|
+
errors.set(documents[index], result.reason);
|
|
551
|
+
});
|
|
552
|
+
} while (sequence !== this.#sequence ||
|
|
553
|
+
generation !== this.#documentGeneration);
|
|
554
|
+
if (errors.size === 1)
|
|
555
|
+
throw errors.values().next().value;
|
|
556
|
+
if (errors.size)
|
|
557
|
+
throw new AggregateError([...errors.values()], "Editor persistence failed");
|
|
150
558
|
}
|
|
151
559
|
/** Cancel a queued change when the owning document is intentionally reset. */
|
|
152
560
|
cancelPendingChanges() {
|
|
153
|
-
this
|
|
561
|
+
this.#assertWritable();
|
|
562
|
+
this.#document?.saves.cancelChange();
|
|
154
563
|
}
|
|
155
564
|
getValue() {
|
|
156
565
|
return this.view.state.doc.toString();
|
|
@@ -159,16 +568,25 @@ export class Editor extends EventDispatcher {
|
|
|
159
568
|
this.view.requestMeasure();
|
|
160
569
|
}
|
|
161
570
|
destroy() {
|
|
162
|
-
if (this.destroyed)
|
|
571
|
+
if (this.destroyed)
|
|
163
572
|
return;
|
|
164
|
-
}
|
|
165
573
|
this.destroyed = true;
|
|
166
|
-
this.
|
|
167
|
-
this
|
|
574
|
+
this.cancelDocumentLoad();
|
|
575
|
+
for (const document of this.#documents)
|
|
576
|
+
document.saves.retire();
|
|
168
577
|
this.view.destroy();
|
|
169
578
|
}
|
|
579
|
+
/** Stop editing immediately and await all retained document persistence. */
|
|
580
|
+
async dispose() {
|
|
581
|
+
this.destroy();
|
|
582
|
+
await this.flushChanges();
|
|
583
|
+
}
|
|
170
584
|
trackChanges(callback) {
|
|
171
|
-
const
|
|
585
|
+
const owner = this.app;
|
|
586
|
+
let active = true;
|
|
587
|
+
const editorUpdated = owner.workspace.on("editor-updated", (editor, transactions) => {
|
|
588
|
+
if (!active || this.destroyed || this.app !== owner)
|
|
589
|
+
return;
|
|
172
590
|
if (editor.id !== this.id && editor.file?.path === this.file?.path) {
|
|
173
591
|
if (this.getValue() !== editor.getValue()) {
|
|
174
592
|
this.view.dispatch(...transactions);
|
|
@@ -177,22 +595,62 @@ export class Editor extends EventDispatcher {
|
|
|
177
595
|
}
|
|
178
596
|
}
|
|
179
597
|
});
|
|
180
|
-
const fileChanged =
|
|
598
|
+
const fileChanged = owner.workspace.on("file-change", (file, event) => {
|
|
599
|
+
if (!active || this.destroyed || this.app !== owner)
|
|
600
|
+
return;
|
|
181
601
|
if (file.path === this.file?.path) {
|
|
182
602
|
if (event !== "delete" && file) {
|
|
183
|
-
this.app.
|
|
603
|
+
const application = this.app, target = this.file, path = target?.path;
|
|
604
|
+
const readGeneration = ++this.#fileReadGeneration, documentGeneration = this.#documentGeneration;
|
|
605
|
+
const sequence = this.#sequence, completed = this.#completedSequence;
|
|
606
|
+
void application.vault
|
|
607
|
+
.read(file)
|
|
608
|
+
.then((contents) => {
|
|
609
|
+
if (!active ||
|
|
610
|
+
this.destroyed ||
|
|
611
|
+
this.app !== application ||
|
|
612
|
+
this.file !== target ||
|
|
613
|
+
this.file?.path !== path ||
|
|
614
|
+
this.#fileReadGeneration !== readGeneration ||
|
|
615
|
+
this.#documentGeneration !== documentGeneration ||
|
|
616
|
+
this.#sequence !== sequence ||
|
|
617
|
+
this.#completedSequence !== completed)
|
|
618
|
+
return;
|
|
619
|
+
this.#refreshError = undefined;
|
|
620
|
+
if (this.getValue() === contents) {
|
|
621
|
+
this.data = contents;
|
|
622
|
+
if (!this.#document?.saves.dirty)
|
|
623
|
+
this.#document?.text?.acceptBaseline(contents);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (this.getValue() !== this.data ||
|
|
627
|
+
this.#document?.saves.dirty)
|
|
628
|
+
return;
|
|
184
629
|
if (this.getValue() !== contents) {
|
|
185
|
-
this
|
|
630
|
+
this.#replaceContent(contents);
|
|
186
631
|
this.onChange(this.getValue());
|
|
187
632
|
callback?.(this.getValue(), this);
|
|
188
633
|
}
|
|
634
|
+
})
|
|
635
|
+
.catch((error) => {
|
|
636
|
+
if (active &&
|
|
637
|
+
!this.destroyed &&
|
|
638
|
+
this.app === application &&
|
|
639
|
+
this.file?.path === path &&
|
|
640
|
+
this.#fileReadGeneration === readGeneration &&
|
|
641
|
+
this.file === target &&
|
|
642
|
+
this.#documentGeneration === documentGeneration &&
|
|
643
|
+
this.#sequence === sequence &&
|
|
644
|
+
this.#completedSequence === completed)
|
|
645
|
+
this.#refreshError = error;
|
|
189
646
|
});
|
|
190
647
|
}
|
|
191
648
|
}
|
|
192
649
|
});
|
|
193
650
|
return () => {
|
|
194
|
-
|
|
195
|
-
|
|
651
|
+
active = false;
|
|
652
|
+
owner.workspace.offref(editorUpdated);
|
|
653
|
+
owner.workspace.offref(fileChanged);
|
|
196
654
|
};
|
|
197
655
|
}
|
|
198
656
|
updateExtensions(extensions, context) {
|
|
@@ -204,11 +662,10 @@ export class Editor extends EventDispatcher {
|
|
|
204
662
|
}
|
|
205
663
|
span.setAttribute("editor.extension_count", editorExtensions.length);
|
|
206
664
|
span.setAttribute("file.extension", ext ?? "");
|
|
207
|
-
const content = this.getValue();
|
|
208
|
-
const selection = this.view.state.selection;
|
|
209
665
|
this.extensions = editorExtensions;
|
|
210
|
-
this.
|
|
211
|
-
|
|
666
|
+
this.view.dispatch({
|
|
667
|
+
effects: StateEffect.reconfigure.of(editorStateExtensions(this, this.extensions)),
|
|
668
|
+
});
|
|
212
669
|
return this;
|
|
213
670
|
}, {
|
|
214
671
|
attributes: { "file.extension": this.file?.extension ?? "" },
|
|
@@ -216,6 +673,14 @@ export class Editor extends EventDispatcher {
|
|
|
216
673
|
});
|
|
217
674
|
}
|
|
218
675
|
setValue(content) {
|
|
676
|
+
if (!this.#controlledLoad)
|
|
677
|
+
this.#assertWritable();
|
|
678
|
+
else if (this.destroyed)
|
|
679
|
+
throw new Error("Editor is destroyed");
|
|
680
|
+
this.#documentGeneration++;
|
|
681
|
+
this.#refreshError = undefined;
|
|
682
|
+
if (!this.#document?.saves.dirty)
|
|
683
|
+
this.#document?.text?.acceptBaseline(content);
|
|
219
684
|
this.app.telemetry.measure("editor.set_value", (span) => {
|
|
220
685
|
span.setAttribute("editor.content_length", content.length);
|
|
221
686
|
span.setAttribute("file.extension", this.file?.extension ?? "");
|
|
@@ -227,6 +692,14 @@ export class Editor extends EventDispatcher {
|
|
|
227
692
|
});
|
|
228
693
|
}
|
|
229
694
|
replaceContent(content, options = {}) {
|
|
695
|
+
this.#assertWritable();
|
|
696
|
+
this.#replaceContent(content, options);
|
|
697
|
+
}
|
|
698
|
+
#replaceContent(content, options = {}) {
|
|
699
|
+
this.#documentGeneration++;
|
|
700
|
+
this.#refreshError = undefined;
|
|
701
|
+
if (!this.#document?.saves.dirty)
|
|
702
|
+
this.#document?.text?.acceptBaseline(content);
|
|
230
703
|
this.data = content;
|
|
231
704
|
const annotations = [syncAnnotation.of(true)];
|
|
232
705
|
this.view.dispatch({
|