When the vim extension is enabled and in "normal" mode, some default bindings collide with Silverbullets commands (eg: `ctrl-p` - Share, `ctrl-o` - Open Document, `ctrl-e` - Export, `ctrl-g h` - Home). These vim bindings are not always functional (they may appear to do nothing depending on vims state). This gives the impression that Silverbullet is bugged and wont trigger commands. In "insert" mode, most bindings are passed through correctly with some exceptions (eg: `ctrl-o`). By splitting the command keybindings into their own keymap extension and loading it before the vim extension, we can ensure Silverbullet commands always trigger. This does mean some vim bindings are overshadowed, but if a binding triggers a Silverbullet command, its generally obvious that *something* happened and the user should be able to recognise why the vim binding failed and then rebind the command or vim mapping.
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 { createCommandKeyBindings } 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.commandKeyHandlerCompartment?.reconfigure(
|
|
createCommandKeyBindings(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();
|
|
}
|
|
}
|