- New check for full indexing completion - Renamed `hasInitialIndexCompleted` to `hasFullIndexCompleted` for clarity. - Removed file:initial event (no longer required and not documented for public consumption) Hopefully doesn't introduce new regressions. Needs testing time "in the field".
273 lines
9.0 KiB
TypeScript
273 lines
9.0 KiB
TypeScript
import { PlugNamespaceHook } from "./plugos/hooks/plug_namespace.ts";
|
|
import type { SilverBulletHooks } from "@silverbulletmd/silverbullet/type/manifest";
|
|
import type { EventHook } from "./plugos/hooks/event.ts";
|
|
import { createWorkerSandboxFromLocalPath } from "./plugos/sandboxes/web_worker_sandbox.ts";
|
|
|
|
import assetSyscalls from "./plugos/syscalls/asset.ts";
|
|
import { System } from "./plugos/system.ts";
|
|
import type { Client } from "./client.ts";
|
|
import { CodeWidgetHook } from "./plugos/hooks/code_widget.ts";
|
|
import { CommandHook } from "./plugos/hooks/command.ts";
|
|
import { SlashCommandHook } from "./plugos/hooks/slash_command.ts";
|
|
import { SyscallHook } from "./plugos/hooks/syscall.ts";
|
|
import { clientStoreSyscalls } from "./plugos/syscalls/clientStore.ts";
|
|
import { editorSyscalls } from "./plugos/syscalls/editor.ts";
|
|
import { sandboxFetchSyscalls } from "./plugos/syscalls/fetch.ts";
|
|
import { markdownSyscalls } from "./plugos/syscalls/markdown.ts";
|
|
import { shellSyscalls } from "./plugos/syscalls/shell.ts";
|
|
import {
|
|
spaceReadSyscalls,
|
|
spaceWriteSyscalls,
|
|
} from "./plugos/syscalls/space.ts";
|
|
import { syncSyscalls } from "./plugos/syscalls/sync.ts";
|
|
import { systemSyscalls } from "./plugos/syscalls/system.ts";
|
|
import type { Space } from "./space.ts";
|
|
import { MQHook } from "./plugos/hooks/mq.ts";
|
|
import { mqSyscalls } from "./plugos/syscalls/mq.ts";
|
|
import {
|
|
dataStoreReadSyscalls,
|
|
dataStoreWriteSyscalls,
|
|
} from "./plugos/syscalls/datastore.ts";
|
|
import type { DataStore } from "./data/datastore.ts";
|
|
import { languageSyscalls } from "./plugos/syscalls/language.ts";
|
|
import { codeWidgetSyscalls } from "./plugos/syscalls/code_widget.ts";
|
|
import { clientCodeWidgetSyscalls } from "./plugos/syscalls/client_code_widget.ts";
|
|
import { KVPrimitivesManifestCache } from "./plugos/manifest_cache.ts";
|
|
import { createKeyBindings } from "./codemirror/editor_state.ts";
|
|
import type { DataStoreMQ } from "./data/mq.datastore.ts";
|
|
import { jsonschemaSyscalls } from "./plugos/syscalls/jsonschema.ts";
|
|
import { luaSyscalls } from "./plugos/syscalls/lua.ts";
|
|
import { indexSyscalls } from "./plugos/syscalls/index.ts";
|
|
import { configSyscalls } from "./plugos/syscalls/config.ts";
|
|
import { eventSyscalls } from "./plugos/syscalls/event.ts";
|
|
import { DocumentEditorHook } from "./plugos/hooks/document_editor.ts";
|
|
import type { Command } from "./types/command.ts";
|
|
import { SpaceLuaEnvironment } from "./space_lua.ts";
|
|
import { builtinPlugPaths } from "../plugs/builtin_plugs.ts";
|
|
import { ServiceRegistry } from "./service_registry.ts";
|
|
import { serviceRegistrySyscalls } from "./plugos/syscalls/service_registry.ts";
|
|
import type { ObjectIndex } from "./data/object_index.ts";
|
|
|
|
const mqTimeout = 10000; // 10s
|
|
const mqTimeoutRetry = 3;
|
|
|
|
/**
|
|
* Handles the extension-related mechanisms of the client by wrapping a PlugOS System object as well as Space Lua environments
|
|
*/
|
|
export class ClientSystem {
|
|
// PlugOS system
|
|
system!: System<SilverBulletHooks>;
|
|
// ... and hooks
|
|
commandHook!: CommandHook;
|
|
slashCommandHook!: SlashCommandHook;
|
|
namespaceHook!: PlugNamespaceHook;
|
|
codeWidgetHook!: CodeWidgetHook;
|
|
documentEditorHook!: DocumentEditorHook;
|
|
mqHook!: MQHook;
|
|
|
|
serviceRegistry!: ServiceRegistry;
|
|
|
|
// Space Lua
|
|
spaceLuaEnv: SpaceLuaEnvironment;
|
|
readonly scriptCommands = new Map<string, Command>();
|
|
scriptsLoaded: boolean = false;
|
|
|
|
// Known files (for UI)
|
|
readonly allKnownFiles = new Set<string>();
|
|
public knownFilesLoaded: boolean = false;
|
|
|
|
constructor(
|
|
private client: Client,
|
|
protected mq: DataStoreMQ,
|
|
public ds: DataStore,
|
|
public eventHook: EventHook,
|
|
private objectIndex: ObjectIndex,
|
|
public readOnlyMode: boolean,
|
|
) {
|
|
this.system = new System(undefined, {
|
|
manifestCache: new KVPrimitivesManifestCache<SilverBulletHooks>(
|
|
ds.kv,
|
|
"manifest",
|
|
),
|
|
});
|
|
|
|
this.spaceLuaEnv = new SpaceLuaEnvironment(this.system, objectIndex);
|
|
this.serviceRegistry = new ServiceRegistry(this.eventHook, client.config);
|
|
|
|
setInterval(() => {
|
|
mq.requeueTimeouts(mqTimeout, mqTimeoutRetry, true).catch(console.error);
|
|
}, 20000); // Look to requeue every 20s
|
|
|
|
this.system.addHook(this.eventHook);
|
|
|
|
// Plug page namespace hook
|
|
this.namespaceHook = new PlugNamespaceHook();
|
|
this.system.addHook(this.namespaceHook);
|
|
|
|
// Code widget hook
|
|
this.codeWidgetHook = new CodeWidgetHook();
|
|
this.system.addHook(this.codeWidgetHook);
|
|
|
|
// Document editor hook
|
|
this.documentEditorHook = new DocumentEditorHook();
|
|
this.system.addHook(this.documentEditorHook);
|
|
|
|
// Command hook
|
|
this.commandHook = new CommandHook(
|
|
this.readOnlyMode,
|
|
this.scriptCommands,
|
|
);
|
|
this.commandHook.on({
|
|
commandsUpdated: (commandMap) => {
|
|
this.client.ui?.viewDispatch({
|
|
type: "update-commands",
|
|
commands: commandMap,
|
|
});
|
|
// Replace the key mapping compartment (keybindings)
|
|
this.client.editorView.dispatch({
|
|
effects: this.client.keyHandlerCompartment?.reconfigure(
|
|
createKeyBindings(this.client),
|
|
),
|
|
});
|
|
},
|
|
});
|
|
|
|
this.slashCommandHook = new SlashCommandHook(this.client);
|
|
|
|
// MQ hook
|
|
this.mqHook = new MQHook(this.system, this.mq, this.client.config);
|
|
this.system.addHook(this.mqHook);
|
|
|
|
// Syscall hook
|
|
this.system.addHook(new SyscallHook());
|
|
|
|
this.eventHook.addLocalListener("editor:reloadState", async () => {
|
|
await this.reloadState();
|
|
});
|
|
}
|
|
|
|
init() {
|
|
// Init is called after the editor is initialized, so we can safely add the command hook
|
|
this.system.addHook(this.commandHook);
|
|
this.system.addHook(this.slashCommandHook);
|
|
|
|
// Syscalls available to all plugs
|
|
this.system.registerSyscalls(
|
|
[],
|
|
eventSyscalls(this.eventHook, this.client),
|
|
editorSyscalls(this.client),
|
|
spaceReadSyscalls(this.client),
|
|
systemSyscalls(client, this.readOnlyMode),
|
|
markdownSyscalls(client),
|
|
assetSyscalls(this.system),
|
|
codeWidgetSyscalls(this.codeWidgetHook),
|
|
clientCodeWidgetSyscalls(),
|
|
languageSyscalls(),
|
|
jsonschemaSyscalls(),
|
|
indexSyscalls(this.objectIndex, this.client),
|
|
//commandSyscalls(client),
|
|
luaSyscalls(this),
|
|
mqSyscalls(this.mq),
|
|
serviceRegistrySyscalls(this.serviceRegistry),
|
|
dataStoreReadSyscalls(this.ds, this),
|
|
dataStoreWriteSyscalls(this.ds),
|
|
syncSyscalls(this.client),
|
|
clientStoreSyscalls(this.ds),
|
|
configSyscalls(this.client.config),
|
|
);
|
|
|
|
if (!this.readOnlyMode) {
|
|
// Write syscalls
|
|
this.system.registerSyscalls(
|
|
[],
|
|
spaceWriteSyscalls(this.client),
|
|
);
|
|
// Syscalls that require some additional permissions
|
|
this.system.registerSyscalls(
|
|
["fetch"],
|
|
sandboxFetchSyscalls(this.client),
|
|
);
|
|
|
|
this.system.registerSyscalls(
|
|
["shell"],
|
|
shellSyscalls(this.client),
|
|
);
|
|
}
|
|
}
|
|
|
|
async loadLuaScripts() {
|
|
if (this.client.bootConfig.disableSpaceLua) {
|
|
console.info("Space Lua scripts are disabled, skipping loading scripts");
|
|
return;
|
|
}
|
|
if (!await this.objectIndex.hasFullIndexCompleted()) {
|
|
console.info(
|
|
"Not loading space scripts, since full indexing has not completed yet",
|
|
);
|
|
return;
|
|
}
|
|
this.client.config.clear();
|
|
try {
|
|
await this.spaceLuaEnv.reload();
|
|
} catch (e: any) {
|
|
console.error("Error loading Lua script:", e.message);
|
|
}
|
|
|
|
// Reset the space script commands
|
|
this.scriptCommands.clear();
|
|
for (
|
|
const [name, command] of Object.entries(
|
|
this.client.config.get<Record<string, Command>>("commands", {}),
|
|
)
|
|
) {
|
|
this.scriptCommands.set(name, command);
|
|
}
|
|
|
|
// Make scripted (slash) commands available
|
|
this.commandHook.throttledBuildAllCommandsAndEmit();
|
|
this.slashCommandHook.throttledBuildAllCommands();
|
|
this.mqHook.throttledReloadQueues();
|
|
|
|
this.scriptsLoaded = true;
|
|
}
|
|
|
|
async reloadPlugsFromSpace(space: Space) {
|
|
console.log("(Re)loading plugs");
|
|
await this.system.unloadAll();
|
|
|
|
let allPlugs = await space.listPlugs();
|
|
// console.log("All plugs", allPlugs);
|
|
if (this.client.bootConfig.disablePlugs) {
|
|
// Only keep builtin plugs
|
|
allPlugs = allPlugs.filter(({ name }) => builtinPlugPaths.includes(name));
|
|
|
|
console.warn("Not loading custom plugs as `disablePlugs` has been set");
|
|
}
|
|
|
|
await Promise.all(allPlugs.map((fileMeta) =>
|
|
this.system.loadPlug(
|
|
createWorkerSandboxFromLocalPath(fileMeta.name),
|
|
fileMeta.name,
|
|
fileMeta.lastModified,
|
|
).catch((e) =>
|
|
console.error(
|
|
`Could not load plug ${fileMeta.name} error: ${e.message}`,
|
|
)
|
|
)
|
|
));
|
|
}
|
|
|
|
localSyscall(name: string, args: any[]) {
|
|
return this.system.localSyscall(name, args);
|
|
}
|
|
|
|
public async reloadState() {
|
|
console.log(
|
|
"Now loading space scripts, custom styles and rebuilding editor state",
|
|
);
|
|
await this.loadLuaScripts();
|
|
await this.client.loadCustomStyles();
|
|
this.client.rebuildEditorState();
|
|
}
|
|
}
|