@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/workspace.svelte.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { appStatePath } from "./storage/path";
|
|
1
2
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
3
|
import { EventDispatcher } from "./events";
|
|
3
4
|
import { EmptyView, FileView, ItemView, TextFileView, View, } from "./view.svelte";
|
|
@@ -7,10 +8,13 @@ import { tick, untrack } from "svelte";
|
|
|
7
8
|
import { toast } from "svelte-sonner";
|
|
8
9
|
import { SidebarState, } from "@lapis-notes/ui/sidebar-custom";
|
|
9
10
|
import { uniqueId } from "./utils";
|
|
11
|
+
import { attachWorkspaceView } from "./workspace-view-document";
|
|
12
|
+
import { workspaceCloseGuard } from "./workspace-close-guard";
|
|
10
13
|
import { normalizeWorkspaceJson } from "./workspace-layout-normalizer";
|
|
11
14
|
import { promptLoadWorkspaceLayout, promptSaveWorkspaceLayout, } from "./workspace-layout-commands";
|
|
12
15
|
import { NAMED_WORKSPACES_FILE, parseNamedWorkspaceStore, serializeNamedWorkspaceStore, } from "./workspace-layouts";
|
|
13
16
|
import { debounce, isEqual } from "lodash-es";
|
|
17
|
+
import { registerWorkspaceFileSettings } from "./files-configuration";
|
|
14
18
|
import { basename, dirname, joinPath } from "./storage";
|
|
15
19
|
import { HistoryManager } from "./history.svelte";
|
|
16
20
|
import { EditorViewRegistry, } from "./editor-view-registry";
|
|
@@ -25,7 +29,7 @@ import { problemsPlugin, PROBLEMS_VIEW_TYPE, } from "@lapismd/design-core/worksp
|
|
|
25
29
|
import { createAppShellPluginPersistence } from "./app-shell-plugin-persistence";
|
|
26
30
|
import { resolveWorkspaceHostBinding, setWorkspaceHostBinding, } from "./workspace-host-internal";
|
|
27
31
|
import { getWorkspaceHostBinding } from "./workspace-host";
|
|
28
|
-
import { DiagnosticsManager, pathFromDiagnosticResource } from "./diagnostics";
|
|
32
|
+
import { DiagnosticsManager, pathFromDiagnosticResource, } from "./diagnostics";
|
|
29
33
|
import { getApplicationCompatibility, resolveApplication, } from "./application-compatibility";
|
|
30
34
|
const DEFAULT_LAPIS_LOGO_URL = new URL("./assets/lapis-logo.svg", import.meta.url).href;
|
|
31
35
|
const WORKSPACE_HINT_TARGET_SELECTOR = "[data-hint-target]";
|
|
@@ -205,8 +209,8 @@ function collectWorkspaceProjectionItem(item, items) {
|
|
|
205
209
|
item.children.forEach((child) => collectWorkspaceProjectionItem(child, items));
|
|
206
210
|
}
|
|
207
211
|
}
|
|
208
|
-
function beginWorkspaceProjection(workspace) {
|
|
209
|
-
const items = new Map();
|
|
212
|
+
function beginWorkspaceProjection(workspace, isCurrent) {
|
|
213
|
+
const items = Object.assign(new Map(), { isCurrent });
|
|
210
214
|
workspace.rootSplit.children.forEach((child) => collectWorkspaceProjectionItem(child, items));
|
|
211
215
|
workspace.leftSplit.children.forEach((child) => collectWorkspaceProjectionItem(child, items));
|
|
212
216
|
workspace.rightSplit.children.forEach((child) => collectWorkspaceProjectionItem(child, items));
|
|
@@ -223,8 +227,13 @@ function claimWorkspaceProjectionItem(projectionItems, id, matches, create) {
|
|
|
223
227
|
}
|
|
224
228
|
return create();
|
|
225
229
|
}
|
|
226
|
-
function finishWorkspaceProjection(projectionItems) {
|
|
230
|
+
function finishWorkspaceProjection(projectionItems, workspace) {
|
|
231
|
+
const retained = projectionItems.isCurrent?.() === false
|
|
232
|
+
? beginWorkspaceProjection(workspace, () => true)
|
|
233
|
+
: undefined;
|
|
227
234
|
for (const item of projectionItems.values()) {
|
|
235
|
+
if (retained?.get(item.id) === item)
|
|
236
|
+
continue;
|
|
228
237
|
if (item instanceof WorkspaceLeaf) {
|
|
229
238
|
item.view.unload();
|
|
230
239
|
if (hasDestroyableEditor(item.view))
|
|
@@ -238,6 +247,10 @@ function finishWorkspaceProjection(projectionItems) {
|
|
|
238
247
|
export const WORKSPACE_ROOT_HOST_ID = "root";
|
|
239
248
|
export const WORKSPACE_POPOUT_UNSUPPORTED_ERROR_MESSAGE = "Workspace popout windows are not supported in the current renderer host.";
|
|
240
249
|
let workspacePopoutHost = null;
|
|
250
|
+
const layoutMutationRevisions = new WeakMap();
|
|
251
|
+
function recordLayoutMutation(workspace) {
|
|
252
|
+
layoutMutationRevisions.set(workspace, (layoutMutationRevisions.get(workspace) ?? 0) + 1);
|
|
253
|
+
}
|
|
241
254
|
export function setWorkspacePopoutHost(host) {
|
|
242
255
|
workspacePopoutHost = host;
|
|
243
256
|
}
|
|
@@ -279,6 +292,8 @@ export class WorkspaceSplit extends WorkspaceParent {
|
|
|
279
292
|
this.sizes[i] = sizes[i] ?? 50;
|
|
280
293
|
});
|
|
281
294
|
return Promise.all(promises).then(() => {
|
|
295
|
+
if (projectionItems?.isCurrent?.() === false)
|
|
296
|
+
return;
|
|
282
297
|
this.id = layout.id;
|
|
283
298
|
this.type = layout.direction;
|
|
284
299
|
});
|
|
@@ -417,6 +432,8 @@ export class WorkspaceSidedock extends WorkspaceSplit {
|
|
|
417
432
|
}
|
|
418
433
|
loadJson(layout, projectionItems) {
|
|
419
434
|
return super.loadJson(layout, projectionItems).then(() => {
|
|
435
|
+
if (projectionItems?.isCurrent?.() === false)
|
|
436
|
+
return;
|
|
420
437
|
if (/^0(?:px|rem|em|%)?$/.test(layout.width.trim())) {
|
|
421
438
|
this.open = false;
|
|
422
439
|
}
|
|
@@ -520,6 +537,8 @@ export class WorkspaceWindow extends WorkspaceContainer {
|
|
|
520
537
|
children: layout.children,
|
|
521
538
|
}, projectionItems)
|
|
522
539
|
.then(() => {
|
|
540
|
+
if (projectionItems?.isCurrent?.() === false)
|
|
541
|
+
return;
|
|
523
542
|
this.id = layout.id;
|
|
524
543
|
this.applyInitData({
|
|
525
544
|
mode: layout.mode,
|
|
@@ -574,22 +593,26 @@ function hasDestroyableEditor(view) {
|
|
|
574
593
|
}
|
|
575
594
|
export class WorkspaceFloating extends WorkspaceParent {
|
|
576
595
|
children = $state([]);
|
|
577
|
-
loadJson(layouts = [], projectionItems) {
|
|
596
|
+
loadJson(layouts = [], projectionItems, resolveHandle) {
|
|
578
597
|
const promises = [];
|
|
579
598
|
this.children.slice().forEach((child) => this.removeChild(child, true));
|
|
580
599
|
layouts.forEach((layout) => {
|
|
581
600
|
const child = claimWorkspaceProjectionItem(projectionItems, layout.id, (item) => item instanceof WorkspaceWindow, () => new WorkspaceWindow());
|
|
582
601
|
this.addChild(child);
|
|
583
602
|
promises.push(child.loadWindowJson(layout, projectionItems).then(() => {
|
|
603
|
+
if (projectionItems?.isCurrent?.() === false)
|
|
604
|
+
return;
|
|
584
605
|
if (child.mode !== "popout") {
|
|
585
606
|
return;
|
|
586
607
|
}
|
|
587
|
-
const handle =
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
608
|
+
const handle = resolveHandle
|
|
609
|
+
? resolveHandle(child.id)
|
|
610
|
+
: openWorkspacePopoutHandle({
|
|
611
|
+
x: child.x,
|
|
612
|
+
y: child.y,
|
|
613
|
+
size: { width: child.width, height: child.height },
|
|
614
|
+
mode: "popout",
|
|
615
|
+
});
|
|
593
616
|
if (!handle) {
|
|
594
617
|
child.mode = "floating";
|
|
595
618
|
return;
|
|
@@ -741,9 +764,7 @@ export class WorkspaceSidebarGroup extends WorkspaceParent {
|
|
|
741
764
|
const selected = this.selectedLeafId
|
|
742
765
|
? this.children.find((leaf) => leaf.id === this.selectedLeafId && !this.isLeafHidden(leaf))
|
|
743
766
|
: undefined;
|
|
744
|
-
return (selected ??
|
|
745
|
-
this.children.find((leaf) => !this.isLeafHidden(leaf)) ??
|
|
746
|
-
null);
|
|
767
|
+
return (selected ?? this.children.find((leaf) => !this.isLeafHidden(leaf)) ?? null);
|
|
747
768
|
}
|
|
748
769
|
selectLeaf(leaf) {
|
|
749
770
|
if (!this.children.includes(leaf)) {
|
|
@@ -812,9 +833,11 @@ export class WorkspaceSidebarGroup extends WorkspaceParent {
|
|
|
812
833
|
layout.children.forEach((config) => {
|
|
813
834
|
const leaf = claimWorkspaceProjectionItem(projectionItems, config.id, (item) => item instanceof WorkspaceLeaf, () => new WorkspaceLeaf());
|
|
814
835
|
this.addChild(leaf);
|
|
815
|
-
promises.push(leaf
|
|
836
|
+
promises.push(loadWorkspaceLeafJson(leaf, config, projectionItems?.isCurrent));
|
|
816
837
|
});
|
|
817
838
|
return Promise.all(promises).then(() => {
|
|
839
|
+
if (projectionItems?.isCurrent?.() === false)
|
|
840
|
+
return;
|
|
818
841
|
this.id = layout.id;
|
|
819
842
|
this.name = layout.name;
|
|
820
843
|
this.icon = layout.icon;
|
|
@@ -874,10 +897,12 @@ export class WorkspaceTabs extends WorkspaceParent {
|
|
|
874
897
|
else {
|
|
875
898
|
const leaf = claimWorkspaceProjectionItem(projectionItems, config.id, (item) => item instanceof WorkspaceLeaf, () => new WorkspaceLeaf());
|
|
876
899
|
this.addChild(leaf);
|
|
877
|
-
promises.push(leaf
|
|
900
|
+
promises.push(loadWorkspaceLeafJson(leaf, config, projectionItems?.isCurrent));
|
|
878
901
|
}
|
|
879
902
|
});
|
|
880
903
|
return Promise.all(promises).then(() => {
|
|
904
|
+
if (projectionItems?.isCurrent?.() === false)
|
|
905
|
+
return;
|
|
881
906
|
this.id = layout.id;
|
|
882
907
|
this.stacked = layout.stacked;
|
|
883
908
|
this.selected = layout.currentTab;
|
|
@@ -1035,6 +1060,8 @@ export class WorkspaceBottomPanel extends WorkspaceTabs {
|
|
|
1035
1060
|
}
|
|
1036
1061
|
loadJson(layout, projectionItems) {
|
|
1037
1062
|
return super.loadJson(layout, projectionItems).then(() => {
|
|
1063
|
+
if (projectionItems?.isCurrent?.() === false)
|
|
1064
|
+
return;
|
|
1038
1065
|
const collapsed = /^0(?:px|rem|em|%)?$/.test(layout.height.trim());
|
|
1039
1066
|
this.open = !collapsed;
|
|
1040
1067
|
if (!collapsed)
|
|
@@ -1253,6 +1280,9 @@ export class Workspace extends EventDispatcher {
|
|
|
1253
1280
|
viewTypes = new Map([
|
|
1254
1281
|
["empty", (leaf) => new EmptyView(leaf)],
|
|
1255
1282
|
]);
|
|
1283
|
+
fileIconListeners = new Set();
|
|
1284
|
+
iconExtensionSequence = 0;
|
|
1285
|
+
iconExtensions = new Map();
|
|
1256
1286
|
viewExtensions = new Map();
|
|
1257
1287
|
editorViews = new EditorViewRegistry();
|
|
1258
1288
|
sidebarViewPlacements = new Map();
|
|
@@ -1281,6 +1311,7 @@ export class Workspace extends EventDispatcher {
|
|
|
1281
1311
|
layoutHandlers = [];
|
|
1282
1312
|
suspendedLayoutPersistence = 0;
|
|
1283
1313
|
#workspaceHostController = null;
|
|
1314
|
+
#diagnosticOpeners = new Set();
|
|
1284
1315
|
diagnostics;
|
|
1285
1316
|
#syncingWorkspaceHost = 0;
|
|
1286
1317
|
#workspaceHostProjection = Promise.resolve();
|
|
@@ -1292,27 +1323,53 @@ export class Workspace extends EventDispatcher {
|
|
|
1292
1323
|
#notificationProgressBridgeDisposer = null;
|
|
1293
1324
|
#commandBridgeDisposer = null;
|
|
1294
1325
|
workspaceLayoutFile = "workspace.json";
|
|
1326
|
+
#layoutSaveTail = Promise.resolve();
|
|
1327
|
+
#layoutPersistenceDisposed = false;
|
|
1295
1328
|
statusEl = $state();
|
|
1296
1329
|
statusCompatEl = $state();
|
|
1297
1330
|
leftRibbon = new WorkspaceRibbon(this, "left");
|
|
1298
1331
|
rightRibbon = new WorkspaceRibbon(this, "right");
|
|
1299
1332
|
containerEl = $state(createDiv("workspace"));
|
|
1300
1333
|
saveLayoutDebounced = debounce(() => {
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
return;
|
|
1304
|
-
}
|
|
1305
|
-
return this.app.vault.create(joinPath("/.obsidian", this.workspaceLayoutFile), JSON.stringify(this.toJson(), null, 2));
|
|
1334
|
+
void this.saveLayoutNow().catch((error) => {
|
|
1335
|
+
console.error("Unable to save workspace layout", error);
|
|
1306
1336
|
});
|
|
1307
1337
|
}, 1000);
|
|
1338
|
+
saveLayoutNow() {
|
|
1339
|
+
const write = this.#layoutSaveTail
|
|
1340
|
+
.catch(() => undefined)
|
|
1341
|
+
.then(async () => {
|
|
1342
|
+
await tick();
|
|
1343
|
+
await this.#workspaceHostProjection;
|
|
1344
|
+
if (!this.layoutReady || this.#layoutPersistenceDisposed)
|
|
1345
|
+
return;
|
|
1346
|
+
await this.app.vault.create(joinPath("/", appStatePath(this.workspaceLayoutFile)), JSON.stringify(this.toJson(), null, 2));
|
|
1347
|
+
});
|
|
1348
|
+
this.#layoutSaveTail = write;
|
|
1349
|
+
return write;
|
|
1350
|
+
}
|
|
1351
|
+
/** Await admitted App Shell transitions before the owning host tears down. */
|
|
1352
|
+
async flushPluginChanges() {
|
|
1353
|
+
await this.#workspaceHostController?.plugins.flushChanges();
|
|
1354
|
+
}
|
|
1355
|
+
/** Drain the single layout writer before a host closes, reloads or switches. */
|
|
1356
|
+
async flushLayout() {
|
|
1357
|
+
await tick();
|
|
1358
|
+
await this.#workspaceHostProjection;
|
|
1359
|
+
this.saveLayoutDebounced.cancel();
|
|
1360
|
+
await this.saveLayoutNow();
|
|
1361
|
+
}
|
|
1308
1362
|
requestSaveLayout(event = { source: "api" }) {
|
|
1309
|
-
if (this.suspendedLayoutPersistence > 0
|
|
1363
|
+
if (this.suspendedLayoutPersistence > 0 ||
|
|
1364
|
+
this.#layoutPersistenceDisposed) {
|
|
1310
1365
|
return;
|
|
1311
1366
|
}
|
|
1312
1367
|
untrack(() => this.commitCompatibilityLayoutToHost(event));
|
|
1313
1368
|
this.emitLayoutChangeAndScheduleSave(event);
|
|
1314
1369
|
}
|
|
1315
1370
|
emitLayoutChangeAndScheduleSave(event) {
|
|
1371
|
+
if (this.#layoutPersistenceDisposed)
|
|
1372
|
+
return;
|
|
1316
1373
|
this.trigger("layout-change", event);
|
|
1317
1374
|
this.saveLayoutDebounced();
|
|
1318
1375
|
}
|
|
@@ -1730,23 +1787,44 @@ export class Workspace extends EventDispatcher {
|
|
|
1730
1787
|
active: this.activeLeaf?.id,
|
|
1731
1788
|
};
|
|
1732
1789
|
}
|
|
1733
|
-
async restoreLayoutJson(config) {
|
|
1734
|
-
|
|
1735
|
-
|
|
1790
|
+
async restoreLayoutJson(config, live = false, isCurrent = () => true) {
|
|
1791
|
+
if (!live)
|
|
1792
|
+
this.exitFocusMode();
|
|
1793
|
+
const normalized = normalizeWorkspaceJson(config, {
|
|
1794
|
+
preservePopouts: live,
|
|
1795
|
+
});
|
|
1736
1796
|
await this.activateLayoutPlugins(normalized);
|
|
1737
|
-
|
|
1797
|
+
if (!isCurrent())
|
|
1798
|
+
return;
|
|
1799
|
+
const projectionItems = beginWorkspaceProjection(this, isCurrent);
|
|
1738
1800
|
try {
|
|
1739
1801
|
await Promise.all([
|
|
1740
1802
|
this.rootSplit.loadJson(normalized.main, projectionItems),
|
|
1741
1803
|
this.leftSplit.loadJson(normalized.left, projectionItems),
|
|
1742
1804
|
this.rightSplit.loadJson(normalized.right, projectionItems),
|
|
1743
1805
|
this.bottomPanel.loadJson(normalized.bottom, projectionItems),
|
|
1744
|
-
this.floating.loadJson(normalized.floating, projectionItems
|
|
1806
|
+
this.floating.loadJson(normalized.floating, projectionItems, live
|
|
1807
|
+
? (id) => {
|
|
1808
|
+
const handle = this.#workspaceHostController?.renderer.getPopoutHandle(id);
|
|
1809
|
+
return handle
|
|
1810
|
+
? {
|
|
1811
|
+
win: handle.window,
|
|
1812
|
+
doc: handle.document,
|
|
1813
|
+
focus: () => handle.focus(),
|
|
1814
|
+
// The controller owns host removal; wrappers only project identity.
|
|
1815
|
+
close: () => { },
|
|
1816
|
+
onClose: () => () => { },
|
|
1817
|
+
}
|
|
1818
|
+
: null;
|
|
1819
|
+
}
|
|
1820
|
+
: undefined),
|
|
1745
1821
|
]);
|
|
1746
1822
|
}
|
|
1747
1823
|
finally {
|
|
1748
|
-
finishWorkspaceProjection(projectionItems);
|
|
1824
|
+
finishWorkspaceProjection(projectionItems, this);
|
|
1749
1825
|
}
|
|
1826
|
+
if (!isCurrent())
|
|
1827
|
+
return;
|
|
1750
1828
|
if (!normalized.active) {
|
|
1751
1829
|
if (!this.containsLeaf(this.activeLeaf))
|
|
1752
1830
|
this.activeLeaf = null;
|
|
@@ -1768,7 +1846,7 @@ export class Workspace extends EventDispatcher {
|
|
|
1768
1846
|
this.workspaceLayoutFile = file;
|
|
1769
1847
|
return this.app.telemetry.measureAsync("workspace.load_layout", async (span) => {
|
|
1770
1848
|
span.setAttribute("workspace.layout_file", file);
|
|
1771
|
-
const workspaceFile = this.app.vault.getFileByPath(joinPath("
|
|
1849
|
+
const workspaceFile = this.app.vault.getFileByPath(joinPath("/", appStatePath(file)));
|
|
1772
1850
|
this.layoutReady = false;
|
|
1773
1851
|
if (workspaceFile) {
|
|
1774
1852
|
await this.restoreLayoutJson(JSON.parse(await this.app.vault.read(workspaceFile)));
|
|
@@ -1814,7 +1892,7 @@ export class Workspace extends EventDispatcher {
|
|
|
1814
1892
|
await this.revealLeaf(rightLeaves[0]);
|
|
1815
1893
|
}
|
|
1816
1894
|
namedWorkspacesPath() {
|
|
1817
|
-
return joinPath("
|
|
1895
|
+
return joinPath("/", appStatePath(NAMED_WORKSPACES_FILE));
|
|
1818
1896
|
}
|
|
1819
1897
|
async readNamedWorkspaceStore() {
|
|
1820
1898
|
const file = this.app.vault.getFileByPath(this.namedWorkspacesPath());
|
|
@@ -1993,12 +2071,30 @@ export class Workspace extends EventDispatcher {
|
|
|
1993
2071
|
values: Object.fromEntries(this.app.configuration.getConfiguration().entries()),
|
|
1994
2072
|
}),
|
|
1995
2073
|
save: async (snapshot) => {
|
|
2074
|
+
// Conditional hosts admit changes in bindConfiguration, before debounce.
|
|
2075
|
+
if (this.app.vault?.supportsConditionalTextWrites)
|
|
2076
|
+
return;
|
|
1996
2077
|
await this.app.configuration.updateConfigurationOptions(snapshot.values);
|
|
1997
2078
|
},
|
|
1998
2079
|
},
|
|
1999
|
-
plugins: createAppShellPluginPersistence(() => this.app.vault)
|
|
2080
|
+
plugins: createAppShellPluginPersistence(() => this.app.vault, this.app, async (enabled) => {
|
|
2081
|
+
const plugins = this.#workspaceHostController.plugins;
|
|
2082
|
+
for (const state of plugins.states) {
|
|
2083
|
+
if (state.required || typeof enabled[state.id] !== "boolean")
|
|
2084
|
+
continue;
|
|
2085
|
+
const success = enabled[state.id]
|
|
2086
|
+
? await plugins.enable(state.id)
|
|
2087
|
+
: await plugins.disable(state.id);
|
|
2088
|
+
if (!success)
|
|
2089
|
+
throw new Error(`Could not restore App Shell plugin ${state.id}`);
|
|
2090
|
+
}
|
|
2091
|
+
}),
|
|
2000
2092
|
},
|
|
2001
2093
|
});
|
|
2094
|
+
this.#workspaceHostController.renderer.setCloseGuard(workspaceCloseGuard(this, () => this.#workspaceHostController.renderer.layout.windows.flatMap((win) => {
|
|
2095
|
+
const handle = this.#workspaceHostController.renderer.getPopoutHandle(win.id);
|
|
2096
|
+
return handle ? [handle.document] : [];
|
|
2097
|
+
})));
|
|
2002
2098
|
setWorkspaceHostBinding(this, {
|
|
2003
2099
|
controller: this.#workspaceHostController,
|
|
2004
2100
|
});
|
|
@@ -2036,7 +2132,17 @@ export class Workspace extends EventDispatcher {
|
|
|
2036
2132
|
});
|
|
2037
2133
|
});
|
|
2038
2134
|
}
|
|
2135
|
+
/** Register an owned opener for application-specific diagnostic resources. */
|
|
2136
|
+
registerDiagnosticOpener(opener) {
|
|
2137
|
+
this.#diagnosticOpeners.add(opener);
|
|
2138
|
+
return () => {
|
|
2139
|
+
this.#diagnosticOpeners.delete(opener);
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2039
2142
|
async openDiagnosticLocation(location) {
|
|
2143
|
+
for (const opener of [...this.#diagnosticOpeners])
|
|
2144
|
+
if (await opener(location))
|
|
2145
|
+
return;
|
|
2040
2146
|
const path = pathFromDiagnosticResource(location.resource);
|
|
2041
2147
|
if (!path)
|
|
2042
2148
|
return;
|
|
@@ -2070,6 +2176,27 @@ export class Workspace extends EventDispatcher {
|
|
|
2070
2176
|
const controller = this.#workspaceHostController;
|
|
2071
2177
|
if (!controller)
|
|
2072
2178
|
return;
|
|
2179
|
+
let projecting = false;
|
|
2180
|
+
registerWorkspaceFileSettings(controller.settings);
|
|
2181
|
+
const project = (values) => {
|
|
2182
|
+
projecting = true;
|
|
2183
|
+
try {
|
|
2184
|
+
controller.settings.changeSnapshot({ version: 1, values });
|
|
2185
|
+
}
|
|
2186
|
+
finally {
|
|
2187
|
+
projecting = false;
|
|
2188
|
+
}
|
|
2189
|
+
};
|
|
2190
|
+
const settingsRef = controller.settings.on("change", ({ id }) => {
|
|
2191
|
+
if (projecting || !this.app.vault?.supportsConditionalTextWrites)
|
|
2192
|
+
return;
|
|
2193
|
+
const { values } = controller.settings.getSnapshot();
|
|
2194
|
+
if (id && !Object.hasOwn(values, id))
|
|
2195
|
+
return;
|
|
2196
|
+
const change = this.app.configuration.updateConfigurationOptions(id ? { [id]: values[id] } : values);
|
|
2197
|
+
// The App-owned writer retains failures in the visible conflict registry.
|
|
2198
|
+
void change.catch(() => undefined);
|
|
2199
|
+
});
|
|
2073
2200
|
const syncAll = () => {
|
|
2074
2201
|
const snapshot = controller.settings.getSnapshot();
|
|
2075
2202
|
const apiValues = Object.fromEntries(this.app.configuration.getConfiguration().entries());
|
|
@@ -2079,7 +2206,7 @@ export class Workspace extends EventDispatcher {
|
|
|
2079
2206
|
next[key] = apiValues[key];
|
|
2080
2207
|
}
|
|
2081
2208
|
if (!isEqual(next, snapshot.values)) {
|
|
2082
|
-
|
|
2209
|
+
project(next);
|
|
2083
2210
|
}
|
|
2084
2211
|
};
|
|
2085
2212
|
const configurationRef = this.app.configuration.on("updated", ({ key, value }) => {
|
|
@@ -2092,13 +2219,14 @@ export class Workspace extends EventDispatcher {
|
|
|
2092
2219
|
else
|
|
2093
2220
|
next[key] = value;
|
|
2094
2221
|
if (!isEqual(next, snapshot.values)) {
|
|
2095
|
-
|
|
2222
|
+
project(next);
|
|
2096
2223
|
}
|
|
2097
2224
|
});
|
|
2098
2225
|
const schemaRef = controller.settings.on("schema-change", syncAll);
|
|
2099
2226
|
this.#configurationBridgeDisposer = () => {
|
|
2100
2227
|
this.app.configuration.offref(configurationRef);
|
|
2101
2228
|
controller.settings.offref(schemaRef);
|
|
2229
|
+
controller.settings.offref(settingsRef);
|
|
2102
2230
|
};
|
|
2103
2231
|
}
|
|
2104
2232
|
/** @internal Connect the API plugin lifecycle to Design Core presentation. */
|
|
@@ -2130,11 +2258,13 @@ export class Workspace extends EventDispatcher {
|
|
|
2130
2258
|
const loaded = manager.on("plugins-loaded", listener);
|
|
2131
2259
|
const enabled = manager.on("plugin-enabled", listener);
|
|
2132
2260
|
const disabled = manager.on("plugin-disabled", listener);
|
|
2261
|
+
const unloaded = manager.on("plugin-unloaded", listener);
|
|
2133
2262
|
const error = manager.on("plugin-error", listener);
|
|
2134
2263
|
return () => {
|
|
2135
2264
|
manager.offref(loaded);
|
|
2136
2265
|
manager.offref(enabled);
|
|
2137
2266
|
manager.offref(disabled);
|
|
2267
|
+
manager.offref(unloaded);
|
|
2138
2268
|
manager.offref(error);
|
|
2139
2269
|
};
|
|
2140
2270
|
},
|
|
@@ -2144,6 +2274,12 @@ export class Workspace extends EventDispatcher {
|
|
|
2144
2274
|
}
|
|
2145
2275
|
/** Dispose the API-owned shell controller and its compatibility bridges. */
|
|
2146
2276
|
async disposeWorkspaceHost() {
|
|
2277
|
+
await this.#workspaceHostProjection;
|
|
2278
|
+
this.saveLayoutDebounced.cancel();
|
|
2279
|
+
await this.#layoutSaveTail;
|
|
2280
|
+
this.#layoutPersistenceDisposed = true;
|
|
2281
|
+
this.saveLayoutDebounced.cancel();
|
|
2282
|
+
this.app.icons?.dispose();
|
|
2147
2283
|
this.app.languageServices.unbindDiagnostics();
|
|
2148
2284
|
this.#configurationBridgeDisposer?.();
|
|
2149
2285
|
this.#configurationBridgeDisposer = null;
|
|
@@ -2160,6 +2296,7 @@ export class Workspace extends EventDispatcher {
|
|
|
2160
2296
|
this.#workspaceHostViewDisposers.forEach((dispose) => dispose());
|
|
2161
2297
|
this.#workspaceHostViewDisposers.clear();
|
|
2162
2298
|
await this.#workspaceHostController?.dispose();
|
|
2299
|
+
this.#diagnosticOpeners.clear();
|
|
2163
2300
|
}
|
|
2164
2301
|
installCommandBridge(controller) {
|
|
2165
2302
|
const disposers = new Map();
|
|
@@ -2208,6 +2345,7 @@ export class Workspace extends EventDispatcher {
|
|
|
2208
2345
|
projectStatusBarItem(controller, item, align) {
|
|
2209
2346
|
controller.status.addItem({
|
|
2210
2347
|
id: item.id,
|
|
2348
|
+
mount: item.mount,
|
|
2211
2349
|
align,
|
|
2212
2350
|
priority: item.priority,
|
|
2213
2351
|
label: item.text,
|
|
@@ -2233,13 +2371,11 @@ export class Workspace extends EventDispatcher {
|
|
|
2233
2371
|
for (const id of projectedIds)
|
|
2234
2372
|
controller.status.removeItem(id);
|
|
2235
2373
|
projectedIds.clear();
|
|
2236
|
-
for (const
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
projectedIds.add(item.id);
|
|
2242
|
-
this.projectStatusBarItem(controller, item, "right");
|
|
2374
|
+
for (const align of ["left", "center", "right"]) {
|
|
2375
|
+
for (const item of this.app.statusBar.getVisibleItems(align, this.app.contextKeys)) {
|
|
2376
|
+
projectedIds.add(item.id);
|
|
2377
|
+
this.projectStatusBarItem(controller, item, align);
|
|
2378
|
+
}
|
|
2243
2379
|
}
|
|
2244
2380
|
};
|
|
2245
2381
|
const unsubscribeStatus = this.app.statusBar.subscribe(sync);
|
|
@@ -2290,9 +2426,19 @@ export class Workspace extends EventDispatcher {
|
|
|
2290
2426
|
}
|
|
2291
2427
|
};
|
|
2292
2428
|
const changed = notifications.on("changed", sync);
|
|
2429
|
+
const notified = notifications.on("notify", (record) => {
|
|
2430
|
+
controller.notifications.notify({
|
|
2431
|
+
id: record.id,
|
|
2432
|
+
title: record.title,
|
|
2433
|
+
message: record.message,
|
|
2434
|
+
severity: record.severity,
|
|
2435
|
+
source: record.source,
|
|
2436
|
+
});
|
|
2437
|
+
});
|
|
2293
2438
|
sync();
|
|
2294
2439
|
this.#notificationProgressBridgeDisposer = () => {
|
|
2295
2440
|
notifications.offref(changed);
|
|
2441
|
+
notifications.offref(notified);
|
|
2296
2442
|
for (const handle of projected.values()) {
|
|
2297
2443
|
handle.complete();
|
|
2298
2444
|
}
|
|
@@ -2346,7 +2492,10 @@ export class Workspace extends EventDispatcher {
|
|
|
2346
2492
|
});
|
|
2347
2493
|
}
|
|
2348
2494
|
enqueueWorkspaceHostProjection(layout, event) {
|
|
2349
|
-
|
|
2495
|
+
if (this.#layoutPersistenceDisposed)
|
|
2496
|
+
return;
|
|
2497
|
+
const mutationRevision = layoutMutationRevisions.get(this) ?? 0;
|
|
2498
|
+
const projected = workspaceJsonForCompatibility(normalizeWorkspaceJson(layout, { preservePopouts: true }));
|
|
2350
2499
|
const preserveSidebarWidth = (side, current) => {
|
|
2351
2500
|
const value = projected[side].width.trim();
|
|
2352
2501
|
if (/^0(?:px|rem|em|%)?$/.test(value)) {
|
|
@@ -2359,13 +2508,26 @@ export class Workspace extends EventDispatcher {
|
|
|
2359
2508
|
preserveSidebarWidth("left", this.leftSplit);
|
|
2360
2509
|
preserveSidebarWidth("right", this.rightSplit);
|
|
2361
2510
|
const run = this.#workspaceHostProjection.then(async () => {
|
|
2511
|
+
const isCurrent = () => mutationRevision === (layoutMutationRevisions.get(this) ?? 0);
|
|
2512
|
+
if (!isCurrent())
|
|
2513
|
+
return;
|
|
2362
2514
|
await this.withoutLayoutPersistence(async () => {
|
|
2363
|
-
await this.restoreLayoutJson(projected);
|
|
2515
|
+
await this.restoreLayoutJson(projected, true, isCurrent);
|
|
2516
|
+
if (!isCurrent())
|
|
2517
|
+
return;
|
|
2518
|
+
this.focusedHostId = this.getCommandHostIdForLeaf(this.activeLeaf);
|
|
2364
2519
|
// Sidedock compatibility effects flush after loadJson resolves. Keep
|
|
2365
2520
|
// persistence suppressed through that flush so an older queued
|
|
2366
2521
|
// projection cannot be committed back over a newer controller layout.
|
|
2367
2522
|
await tick();
|
|
2368
2523
|
});
|
|
2524
|
+
if (!isCurrent()) {
|
|
2525
|
+
this.requestSaveLayout({
|
|
2526
|
+
source: "api",
|
|
2527
|
+
operation: "superseded-projection",
|
|
2528
|
+
});
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2369
2531
|
this.emitLayoutChangeAndScheduleSave(compatibilityLayoutEventFromDesign(event));
|
|
2370
2532
|
});
|
|
2371
2533
|
this.#workspaceHostProjection = run.catch((error) => {
|
|
@@ -2487,13 +2649,27 @@ export class Workspace extends EventDispatcher {
|
|
|
2487
2649
|
};
|
|
2488
2650
|
},
|
|
2489
2651
|
mount: (target, context) => {
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2652
|
+
let active = true;
|
|
2653
|
+
let attached = null;
|
|
2654
|
+
const attach = () => {
|
|
2655
|
+
if (!active)
|
|
2656
|
+
return;
|
|
2657
|
+
const leaf = this.getLeafById(context.tab.id);
|
|
2658
|
+
if (!leaf || leaf.containerEl.parentElement === target)
|
|
2659
|
+
return;
|
|
2660
|
+
attachWorkspaceView(target, leaf);
|
|
2661
|
+
attached = leaf;
|
|
2662
|
+
};
|
|
2663
|
+
attach();
|
|
2664
|
+
// Controller leaves can render before their compatibility projection.
|
|
2665
|
+
// Reattach the same retained view; do not create another lifecycle owner.
|
|
2666
|
+
void this.#workspaceHostProjection.then(attach).catch((error) => {
|
|
2667
|
+
console.error("Unable to attach projected workspace view", error);
|
|
2668
|
+
});
|
|
2494
2669
|
return () => {
|
|
2495
|
-
|
|
2496
|
-
|
|
2670
|
+
active = false;
|
|
2671
|
+
if (attached?.containerEl.parentElement === target) {
|
|
2672
|
+
attached.containerEl.remove();
|
|
2497
2673
|
}
|
|
2498
2674
|
};
|
|
2499
2675
|
},
|
|
@@ -2565,6 +2741,15 @@ export class Workspace extends EventDispatcher {
|
|
|
2565
2741
|
* @public
|
|
2566
2742
|
*/
|
|
2567
2743
|
async revealLeaf(leaf) {
|
|
2744
|
+
const wasActive = this.activeLeaf === leaf;
|
|
2745
|
+
const revision = layoutMutationRevisions.get(this) ?? 0;
|
|
2746
|
+
await this.#workspaceHostProjection;
|
|
2747
|
+
if (revision !== (layoutMutationRevisions.get(this) ?? 0) ||
|
|
2748
|
+
!this.containsLeaf(leaf))
|
|
2749
|
+
return;
|
|
2750
|
+
recordLayoutMutation(this);
|
|
2751
|
+
if (wasActive)
|
|
2752
|
+
this.activeLeaf = leaf;
|
|
2568
2753
|
let child = leaf;
|
|
2569
2754
|
let parent = leaf.parent;
|
|
2570
2755
|
while (parent) {
|
|
@@ -2587,12 +2772,22 @@ export class Workspace extends EventDispatcher {
|
|
|
2587
2772
|
if (floatingWindow) {
|
|
2588
2773
|
this.focusFloatingWindow(floatingWindow);
|
|
2589
2774
|
}
|
|
2775
|
+
this.requestSaveLayout({ source: "api", operation: "reveal-leaf" });
|
|
2590
2776
|
}
|
|
2591
2777
|
registerView(type, viewCreator) {
|
|
2592
2778
|
this.viewTypes.set(type, viewCreator);
|
|
2593
2779
|
this.registerWorkspaceHostView(type);
|
|
2780
|
+
for (const listener of this.fileIconListeners)
|
|
2781
|
+
listener();
|
|
2594
2782
|
}
|
|
2595
2783
|
registerEditorView(contribution) {
|
|
2784
|
+
const existing = this.editorViews.get(contribution.id);
|
|
2785
|
+
if (existing?.source === "manifest" &&
|
|
2786
|
+
contribution.source === "plugin" &&
|
|
2787
|
+
existing.pluginId &&
|
|
2788
|
+
existing.pluginId === contribution.pluginId) {
|
|
2789
|
+
return this.editorViews.overlay(contribution);
|
|
2790
|
+
}
|
|
2596
2791
|
return this.editorViews.register(contribution);
|
|
2597
2792
|
}
|
|
2598
2793
|
registerSidebarView(type, options = {}) {
|
|
@@ -2629,7 +2824,11 @@ export class Workspace extends EventDispatcher {
|
|
|
2629
2824
|
unregisterView(type) {
|
|
2630
2825
|
this.#workspaceHostViewDisposers.get(type)?.();
|
|
2631
2826
|
this.#workspaceHostViewDisposers.delete(type);
|
|
2632
|
-
|
|
2827
|
+
const removed = this.viewTypes.delete(type);
|
|
2828
|
+
if (removed)
|
|
2829
|
+
for (const listener of this.fileIconListeners)
|
|
2830
|
+
listener();
|
|
2831
|
+
return removed;
|
|
2633
2832
|
}
|
|
2634
2833
|
normalizeExtensionKey(extension) {
|
|
2635
2834
|
const normalized = extension.trim().toLowerCase();
|
|
@@ -2642,7 +2841,10 @@ export class Workspace extends EventDispatcher {
|
|
|
2642
2841
|
if (!extensionKey) {
|
|
2643
2842
|
return undefined;
|
|
2644
2843
|
}
|
|
2645
|
-
return this.
|
|
2844
|
+
return ([...this.iconExtensions.values()]
|
|
2845
|
+
.reverse()
|
|
2846
|
+
.find((entry) => entry.keys.includes(extensionKey))?.viewType ??
|
|
2847
|
+
this.viewExtensions.get(extensionKey));
|
|
2646
2848
|
}
|
|
2647
2849
|
resolveViewTypeForPath(path) {
|
|
2648
2850
|
const normalizedPath = path.trim().toLowerCase();
|
|
@@ -2669,13 +2871,14 @@ export class Workspace extends EventDispatcher {
|
|
|
2669
2871
|
.replace(/[._-]+/g, " ")
|
|
2670
2872
|
.replace(/\b\w/gu, (char) => char.toUpperCase());
|
|
2671
2873
|
}
|
|
2672
|
-
registeredEditorViewForPath(path) {
|
|
2874
|
+
registeredEditorViewForPath(path, metadataOnly = false) {
|
|
2673
2875
|
return this.editorViews
|
|
2674
2876
|
.getAll()
|
|
2675
2877
|
.flatMap((view, viewIndex) => view.filenamePatterns
|
|
2676
2878
|
.filter((pattern) => matchesEditorAssociationGlob(pattern, path))
|
|
2677
2879
|
.map((pattern) => ({ view, viewIndex, pattern })))
|
|
2678
|
-
.filter(({ view }) => this.viewTypes.has(view.viewType)
|
|
2880
|
+
.filter(({ view }) => this.viewTypes.has(view.viewType) ||
|
|
2881
|
+
(metadataOnly && view.source === "manifest"))
|
|
2679
2882
|
.sort((left, right) => {
|
|
2680
2883
|
const priority = { option: 0, default: 1, exclusive: 2 };
|
|
2681
2884
|
const priorityDelta = priority[right.view.priority] - priority[left.view.priority];
|
|
@@ -2722,7 +2925,55 @@ export class Workspace extends EventDispatcher {
|
|
|
2722
2925
|
}
|
|
2723
2926
|
return this.registeredEditorViewForPath(path)?.viewType;
|
|
2724
2927
|
}
|
|
2725
|
-
|
|
2928
|
+
/** Resolve presentation metadata without activating plugins or creating views. */
|
|
2929
|
+
getFileIconForPath(path) {
|
|
2930
|
+
const association = this.getEditorAssociationForPath(path)?.view;
|
|
2931
|
+
const eligible = association &&
|
|
2932
|
+
(this.viewTypes.has(association.viewType) ||
|
|
2933
|
+
association.source === "manifest");
|
|
2934
|
+
return (eligible ? association : this.registeredEditorViewForPath(path, true))?.fileIcon;
|
|
2935
|
+
}
|
|
2936
|
+
onFileIconsChanged(listener) {
|
|
2937
|
+
this.fileIconListeners.add(listener);
|
|
2938
|
+
const view = this.editorViews.on("changed", listener);
|
|
2939
|
+
const config = this.app.configuration.on("updated", ({ key }) => {
|
|
2940
|
+
if (key === "workspace.editorAssociations")
|
|
2941
|
+
listener();
|
|
2942
|
+
});
|
|
2943
|
+
return () => {
|
|
2944
|
+
this.fileIconListeners.delete(listener);
|
|
2945
|
+
this.editorViews.offref(view);
|
|
2946
|
+
this.app.configuration.offref(config);
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
registerExtensions(extensions, viewType, options) {
|
|
2950
|
+
if (options) {
|
|
2951
|
+
const keys = [
|
|
2952
|
+
...new Set(extensions
|
|
2953
|
+
.map((ext) => this.normalizeExtensionKey(ext))
|
|
2954
|
+
.filter(Boolean)),
|
|
2955
|
+
];
|
|
2956
|
+
if (!keys.length)
|
|
2957
|
+
return () => { };
|
|
2958
|
+
const id = `compat:${viewType}:${++this.iconExtensionSequence}`;
|
|
2959
|
+
const dispose = this.editorViews.register({
|
|
2960
|
+
id,
|
|
2961
|
+
viewType,
|
|
2962
|
+
label: this.humanizeViewType(viewType),
|
|
2963
|
+
filenamePatterns: keys.map((key) => this.extensionKeyToFilenamePattern(key)),
|
|
2964
|
+
priority: "default",
|
|
2965
|
+
source: "compat",
|
|
2966
|
+
fileIcon: options.fileIcon,
|
|
2967
|
+
});
|
|
2968
|
+
const entry = { keys, viewType, dispose };
|
|
2969
|
+
this.iconExtensions.set(id, entry);
|
|
2970
|
+
return () => {
|
|
2971
|
+
if (this.iconExtensions.get(id) !== entry)
|
|
2972
|
+
return;
|
|
2973
|
+
this.iconExtensions.delete(id);
|
|
2974
|
+
dispose();
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2726
2977
|
const filenamePatterns = [];
|
|
2727
2978
|
extensions.forEach((ext) => {
|
|
2728
2979
|
const extensionKey = this.normalizeExtensionKey(ext);
|
|
@@ -2733,12 +2984,12 @@ export class Workspace extends EventDispatcher {
|
|
|
2733
2984
|
filenamePatterns.push(this.extensionKeyToFilenamePattern(extensionKey));
|
|
2734
2985
|
});
|
|
2735
2986
|
if (filenamePatterns.length === 0) {
|
|
2736
|
-
return;
|
|
2987
|
+
return () => { };
|
|
2737
2988
|
}
|
|
2738
2989
|
const existing = this.editorViews.getByViewType(viewType)[0];
|
|
2739
2990
|
if (existing) {
|
|
2740
2991
|
this.editorViews.update(existing.id, { filenamePatterns });
|
|
2741
|
-
return;
|
|
2992
|
+
return () => this.unregisterExtensions(extensions, viewType);
|
|
2742
2993
|
}
|
|
2743
2994
|
this.editorViews.upsert({
|
|
2744
2995
|
id: viewType,
|
|
@@ -2748,8 +2999,19 @@ export class Workspace extends EventDispatcher {
|
|
|
2748
2999
|
priority: "default",
|
|
2749
3000
|
source: "compat",
|
|
2750
3001
|
});
|
|
3002
|
+
return () => this.unregisterExtensions(extensions, viewType);
|
|
2751
3003
|
}
|
|
2752
3004
|
unregisterExtensions(extensions, viewType) {
|
|
3005
|
+
const removed = new Set(extensions.map((ext) => this.normalizeExtensionKey(ext)));
|
|
3006
|
+
for (const [id, entry] of this.iconExtensions) {
|
|
3007
|
+
if (entry.viewType !== viewType)
|
|
3008
|
+
continue;
|
|
3009
|
+
entry.keys = entry.keys.filter((key) => !removed.has(key));
|
|
3010
|
+
if (!entry.keys.length) {
|
|
3011
|
+
this.iconExtensions.delete(id);
|
|
3012
|
+
entry.dispose();
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
2753
3015
|
const filenamePatterns = [];
|
|
2754
3016
|
extensions.forEach((ext) => {
|
|
2755
3017
|
const extensionKey = this.normalizeExtensionKey(ext);
|
|
@@ -2900,6 +3162,11 @@ export class Workspace extends EventDispatcher {
|
|
|
2900
3162
|
}
|
|
2901
3163
|
return this.lastActiveFile;
|
|
2902
3164
|
}
|
|
3165
|
+
/** Reconcile the remembered file through its workspace owner. */
|
|
3166
|
+
reconcileFileReference(oldPath, file) {
|
|
3167
|
+
if (this.lastActiveFile?.path === oldPath)
|
|
3168
|
+
this.lastActiveFile = file?.copy() ?? null;
|
|
3169
|
+
}
|
|
2903
3170
|
/**
|
|
2904
3171
|
* Iterate through all leaves, including main area leaves, floating leaves,
|
|
2905
3172
|
* and sidebar leaves.
|
|
@@ -3023,6 +3290,8 @@ export class Workspace extends EventDispatcher {
|
|
|
3023
3290
|
if (!this.containsLeaf(leaf)) {
|
|
3024
3291
|
return false;
|
|
3025
3292
|
}
|
|
3293
|
+
if (options.source !== "layout-load")
|
|
3294
|
+
recordLayoutMutation(this);
|
|
3026
3295
|
const tabs = this.tabsForLeaf(leaf);
|
|
3027
3296
|
if (leaf.parent instanceof WorkspaceSidebarGroup) {
|
|
3028
3297
|
leaf.parent.selectLeaf(leaf);
|
|
@@ -3676,7 +3945,9 @@ export class Workspace extends EventDispatcher {
|
|
|
3676
3945
|
this.app.workspace.activeLeaf = duplicateLeaf;
|
|
3677
3946
|
this.app.workspace.requestSaveLayout({
|
|
3678
3947
|
source: "api",
|
|
3679
|
-
operation: leafType === "split"
|
|
3948
|
+
operation: leafType === "split"
|
|
3949
|
+
? `duplicate-leaf-${direction}`
|
|
3950
|
+
: "duplicate-leaf-tab",
|
|
3680
3951
|
});
|
|
3681
3952
|
return duplicateLeaf;
|
|
3682
3953
|
});
|
|
@@ -3889,7 +4160,7 @@ export class OnDemandPluginInstallView extends FileView {
|
|
|
3889
4160
|
function canReuseLeafForFileNavigation(leaf) {
|
|
3890
4161
|
if (!leaf)
|
|
3891
4162
|
return false;
|
|
3892
|
-
return
|
|
4163
|
+
return leaf.view.getViewType() === "empty" || leaf.view instanceof FileView;
|
|
3893
4164
|
}
|
|
3894
4165
|
function isPoorerViewState(incoming, live) {
|
|
3895
4166
|
const next = incoming ?? {};
|
|
@@ -3900,6 +4171,25 @@ function isPoorerViewState(incoming, live) {
|
|
|
3900
4171
|
return true;
|
|
3901
4172
|
return liveEntries.some(([key]) => !(key in next));
|
|
3902
4173
|
}
|
|
4174
|
+
function loadWorkspaceLeafJson(leaf, layout, isCurrent = () => true) {
|
|
4175
|
+
if (!isCurrent())
|
|
4176
|
+
return Promise.resolve();
|
|
4177
|
+
leaf.id = layout.id;
|
|
4178
|
+
const liveState = leaf.view.getState();
|
|
4179
|
+
if (leaf.view.getViewType() === layout.state.type &&
|
|
4180
|
+
(isEqual(liveState, layout.state.state) ||
|
|
4181
|
+
isPoorerViewState(layout.state.state, liveState))) {
|
|
4182
|
+
leaf.state = { type: layout.state.type, state: { ...liveState } };
|
|
4183
|
+
return Promise.resolve();
|
|
4184
|
+
}
|
|
4185
|
+
return leaf.setViewState({ type: layout.state.type, state: layout.state.state }, {
|
|
4186
|
+
history: true,
|
|
4187
|
+
layoutProjection: true,
|
|
4188
|
+
projectionIsCurrent: isCurrent,
|
|
4189
|
+
title: layout.state.title,
|
|
4190
|
+
icon: layout.state.icon,
|
|
4191
|
+
});
|
|
4192
|
+
}
|
|
3903
4193
|
function collectLayoutViewTypes(node, target) {
|
|
3904
4194
|
if (node.type === "split" || node.type === "floating") {
|
|
3905
4195
|
for (const child of node.children) {
|
|
@@ -3914,8 +4204,8 @@ function collectLayoutViewTypes(node, target) {
|
|
|
3914
4204
|
}
|
|
3915
4205
|
}
|
|
3916
4206
|
/**
|
|
3917
|
-
* Compatibility stand-in for a Design Core class view. The host owns the
|
|
3918
|
-
*
|
|
4207
|
+
* Compatibility stand-in for a Design Core class view. The host owns the live
|
|
4208
|
+
* mount; this view only preserves type and chrome across API commits.
|
|
3919
4209
|
*/
|
|
3920
4210
|
class HostOwnedWorkspaceView extends View {
|
|
3921
4211
|
#viewType;
|
|
@@ -4170,6 +4460,9 @@ export class WorkspaceLeaf extends WorkspaceItem {
|
|
|
4170
4460
|
});
|
|
4171
4461
|
}
|
|
4172
4462
|
async setViewState(viewState, eState) {
|
|
4463
|
+
if (!eState?.layoutProjection)
|
|
4464
|
+
recordLayoutMutation(this.app.workspace);
|
|
4465
|
+
const isCurrent = eState?.projectionIsCurrent ?? (() => true);
|
|
4173
4466
|
return this.app.telemetry.measureAsync("workspace.leaf.set_view_state", async (span) => {
|
|
4174
4467
|
span.setAttribute("leaf.id", this.id);
|
|
4175
4468
|
span.setAttribute("view.type", viewState.type);
|
|
@@ -4184,13 +4477,19 @@ export class WorkspaceLeaf extends WorkspaceItem {
|
|
|
4184
4477
|
if (requestedViewType === ON_DEMAND_PLUGIN_INSTALL_VIEW_TYPE &&
|
|
4185
4478
|
file instanceof TFile) {
|
|
4186
4479
|
const promptView = await this.app.workspace.createOnDemandPluginInstallView(this, file, { confirm: false });
|
|
4480
|
+
if (!isCurrent())
|
|
4481
|
+
return;
|
|
4187
4482
|
if (promptView) {
|
|
4188
|
-
this.state = viewState;
|
|
4189
|
-
this.pinned = !!viewState.pinned;
|
|
4190
4483
|
const result = {
|
|
4191
4484
|
history: eState?.history === true,
|
|
4192
4485
|
};
|
|
4193
4486
|
await promptView.setState({ ...(viewState.state || {}) });
|
|
4487
|
+
if (!isCurrent()) {
|
|
4488
|
+
promptView.unload();
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
4491
|
+
this.state = viewState;
|
|
4492
|
+
this.pinned = !!viewState.pinned;
|
|
4194
4493
|
await this.open(promptView, result, viewState);
|
|
4195
4494
|
return;
|
|
4196
4495
|
}
|
|
@@ -4198,6 +4497,8 @@ export class WorkspaceLeaf extends WorkspaceItem {
|
|
|
4198
4497
|
if (eState?.activatePlugins !== false) {
|
|
4199
4498
|
await this.app.plugins.activateForViewType(requestedViewType);
|
|
4200
4499
|
}
|
|
4500
|
+
if (!isCurrent())
|
|
4501
|
+
return;
|
|
4201
4502
|
let viewCreator = this.app.workspace.viewCreator(requestedViewType);
|
|
4202
4503
|
let resolvedViewState = viewState;
|
|
4203
4504
|
if (viewCreator && persistedMissingViewType) {
|
|
@@ -4238,11 +4539,17 @@ export class WorkspaceLeaf extends WorkspaceItem {
|
|
|
4238
4539
|
this.app.workspace.viewCreator("empty") ||
|
|
4239
4540
|
((leaf) => new EmptyView(leaf));
|
|
4240
4541
|
}
|
|
4241
|
-
this.state = resolvedViewState;
|
|
4242
|
-
this.pinned = !!viewState.pinned;
|
|
4243
4542
|
const view = viewCreator(this);
|
|
4244
4543
|
const result = { history: eState?.history === true };
|
|
4245
4544
|
await view.setState({ ...(resolvedViewState.state || {}) });
|
|
4545
|
+
if (!isCurrent()) {
|
|
4546
|
+
view.unload();
|
|
4547
|
+
if (hasDestroyableEditor(view))
|
|
4548
|
+
view.editor.destroy();
|
|
4549
|
+
return;
|
|
4550
|
+
}
|
|
4551
|
+
this.state = resolvedViewState;
|
|
4552
|
+
this.pinned = !!viewState.pinned;
|
|
4246
4553
|
await this.open(view, result, resolvedViewState);
|
|
4247
4554
|
}, {
|
|
4248
4555
|
attributes: {
|
|
@@ -4298,25 +4605,7 @@ export class WorkspaceLeaf extends WorkspaceItem {
|
|
|
4298
4605
|
this.parent.removeChild(this);
|
|
4299
4606
|
}
|
|
4300
4607
|
loadJson(layout) {
|
|
4301
|
-
this
|
|
4302
|
-
const liveState = this.view.getState();
|
|
4303
|
-
if (this.view.getViewType() === layout.state.type &&
|
|
4304
|
-
(isEqual(liveState, layout.state.state) ||
|
|
4305
|
-
isPoorerViewState(layout.state.state, liveState))) {
|
|
4306
|
-
this.state = {
|
|
4307
|
-
type: layout.state.type,
|
|
4308
|
-
state: { ...liveState },
|
|
4309
|
-
};
|
|
4310
|
-
return Promise.resolve();
|
|
4311
|
-
}
|
|
4312
|
-
return this.setViewState({
|
|
4313
|
-
type: layout.state.type,
|
|
4314
|
-
state: layout.state.state,
|
|
4315
|
-
}, {
|
|
4316
|
-
history: true,
|
|
4317
|
-
title: layout.state.title,
|
|
4318
|
-
icon: layout.state.icon,
|
|
4319
|
-
});
|
|
4608
|
+
return loadWorkspaceLeafJson(this, layout);
|
|
4320
4609
|
}
|
|
4321
4610
|
reload() {
|
|
4322
4611
|
this.loadJson(this.toJson());
|