diff --git a/common/manifest.ts b/common/manifest.ts index 05392c53..f54a6a4c 100644 --- a/common/manifest.ts +++ b/common/manifest.ts @@ -1,24 +1,14 @@ import * as plugos from "../plugos/types"; -import { EndpointHook } from "../plugos/feature/endpoint"; -import { CronHook } from "../plugos/feature/node_cron"; -import { EventHook } from "../plugos/feature/event"; +import { EndpointHookT } from "../plugos/hooks/endpoint"; +import { CronHookT } from "../plugos/hooks/node_cron"; +import { EventHookT } from "../plugos/hooks/event"; +import { CommandHookT } from "../webapp/hooks/command"; +import { SlashCommandHookT } from "../webapp/hooks/slash_command"; -export type CommandDef = { - name: string; - - // Bind to keyboard shortcut - key?: string; - mac?: string; - - // If to show in slash invoked menu and if so, with what label - // should match slashCommandRegexp - slashCommand?: string; -}; - -export type SilverBulletHooks = { - command?: CommandDef | CommandDef[]; -} & EndpointHook & - CronHook & - EventHook; +export type SilverBulletHooks = CommandHookT & + SlashCommandHookT & + EndpointHookT & + CronHookT & + EventHookT; export type Manifest = plugos.Manifest; diff --git a/package.json b/package.json index 183f5eae..450b0a71 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "clean": "rm -rf dist", "plugs": "cd plugs && ../plugos/dist/plugos/plugos-bundle.js -w --dist dist */*.plug.yaml", "server": "nodemon -w dist/server dist/server/server.js pages", - "test": "jest dist/test" + "test": "jest dist/test plugos/dist/test" }, "files": [ "dist" diff --git a/plugos/bin/plugos-server.ts b/plugos/bin/plugos-server.ts index 48c7b7ed..93ef422a 100755 --- a/plugos/bin/plugos-server.ts +++ b/plugos/bin/plugos-server.ts @@ -2,29 +2,29 @@ import express from "express"; import yargs from "yargs"; -import { hideBin } from "yargs/helpers"; -import { DiskPlugLoader } from "../plug_loader"; -import { CronHook, NodeCronFeature } from "../feature/node_cron"; -import shellSyscalls from "../syscall/shell.node"; -import { System } from "../system"; -import { EndpointFeature, EndpointHook } from "../feature/endpoint"; -import { safeRun } from "../util"; +import {hideBin} from "yargs/helpers"; +import {DiskPlugLoader} from "../plug_loader"; +import {CronHookT, NodeCronHook} from "../hooks/node_cron"; +import shellSyscalls from "../syscalls/shell.node"; +import {System} from "../system"; +import {EndpointHook, EndpointHookT} from "../hooks/endpoint"; +import {safeRun} from "../util"; import knex from "knex"; import { ensureTable, storeReadSyscalls, storeWriteSyscalls, -} from "../syscall/store.knex_node"; -import { fetchSyscalls } from "../syscall/fetch.node"; -import { EventFeature, EventHook } from "../feature/event"; -import { eventSyscalls } from "../syscall/event"; +} from "../syscalls/store.knex_node"; +import {fetchSyscalls} from "../syscalls/fetch.node"; +import {EventHook, EventHookT} from "../hooks/event"; +import {eventSyscalls} from "../syscalls/event"; let args = yargs(hideBin(process.argv)) - .option("port", { - type: "number", - default: 1337, - }) - .parse(); + .option("port", { + type: "number", + default: 1337, + }) + .parse(); if (!args._.length) { console.error("Usage: plugos-server "); @@ -35,7 +35,7 @@ const plugPath = args._[0] as string; const app = express(); -type ServerHook = EndpointHook & CronHook & EventHook; +type ServerHook = EndpointHookT & CronHookT & EventHookT; const system = new System("server"); safeRun(async () => { @@ -52,11 +52,11 @@ safeRun(async () => { let plugLoader = new DiskPlugLoader(system, plugPath); await plugLoader.loadPlugs(); plugLoader.watcher(); - system.addFeature(new NodeCronFeature()); - let eventFeature = new EventFeature(); - system.addFeature(eventFeature); - system.registerSyscalls("event", [], eventSyscalls(eventFeature)); - system.addFeature(new EndpointFeature(app, "")); + system.addHook(new NodeCronHook()); + let eventHook = new EventHook(); + system.addHook(eventHook); + system.registerSyscalls("event", [], eventSyscalls(eventHook)); + system.addHook(new EndpointHook(app, "")); system.registerSyscalls("shell", [], shellSyscalls(".")); system.registerSyscalls("fetch", [], fetchSyscalls()); system.registerSyscalls( diff --git a/plugos/environment/iframe_sandbox.html b/plugos/environments/iframe_sandbox.html similarity index 100% rename from plugos/environment/iframe_sandbox.html rename to plugos/environments/iframe_sandbox.html diff --git a/plugos/environment/iframe_sandbox.ts b/plugos/environments/iframe_sandbox.ts similarity index 100% rename from plugos/environment/iframe_sandbox.ts rename to plugos/environments/iframe_sandbox.ts diff --git a/plugos/environment/node_sandbox.ts b/plugos/environments/node_sandbox.ts similarity index 100% rename from plugos/environment/node_sandbox.ts rename to plugos/environments/node_sandbox.ts diff --git a/plugos/environment/node_worker.ts b/plugos/environments/node_worker.ts similarity index 98% rename from plugos/environment/node_worker.ts rename to plugos/environments/node_worker.ts index fa02d688..e3c60960 100644 --- a/plugos/environment/node_worker.ts +++ b/plugos/environments/node_worker.ts @@ -50,7 +50,6 @@ parentPort.on("message", (data: any) => { safeRun(async () => { switch (data.type) { case "load": - console.log("Booting", data.name); loadedFunctions.set(data.name, new VMScript(wrapScript(data.code))); parentPort.postMessage({ type: "inited", diff --git a/plugos/environment/sandbox_worker.ts b/plugos/environments/sandbox_worker.ts similarity index 98% rename from plugos/environment/sandbox_worker.ts rename to plugos/environments/sandbox_worker.ts index 94b94558..1650b832 100644 --- a/plugos/environment/sandbox_worker.ts +++ b/plugos/environments/sandbox_worker.ts @@ -48,7 +48,6 @@ self.addEventListener("message", (event: { data: WorkerMessage }) => { let data = messageEvent.data; switch (data.type) { case "load": - console.log("Booting", data.name); loadedFunctions.set(data.name!, new Function(wrapScript(data.code!))); postMessage( { diff --git a/plugos/environment/webworker_sandbox.ts b/plugos/environments/webworker_sandbox.ts similarity index 100% rename from plugos/environment/webworker_sandbox.ts rename to plugos/environments/webworker_sandbox.ts diff --git a/plugos/environment/worker.ts b/plugos/environments/worker.ts similarity index 100% rename from plugos/environment/worker.ts rename to plugos/environments/worker.ts diff --git a/plugos/feature/endpoint.test.ts b/plugos/hooks/endpoint.test.ts similarity index 81% rename from plugos/feature/endpoint.test.ts rename to plugos/hooks/endpoint.test.ts index 2d01aa22..39d674f1 100644 --- a/plugos/feature/endpoint.test.ts +++ b/plugos/hooks/endpoint.test.ts @@ -1,13 +1,13 @@ -import { createSandbox } from "../environment/node_sandbox"; +import { createSandbox } from "../environments/node_sandbox"; import { expect, test } from "@jest/globals"; import { Manifest } from "../types"; import express from "express"; import request from "supertest"; -import { EndpointFeature, EndpointHook } from "./endpoint"; +import { EndpointHook, EndpointHookT } from "./endpoint"; import { System } from "../system"; test("Run a plugos endpoint server", async () => { - let system = new System("server"); + let system = new System("server"); let plug = await system.load( "test", { @@ -26,14 +26,14 @@ test("Run a plugos endpoint server", async () => { })()`, }, }, - } as Manifest, + } as Manifest, createSandbox ); const app = express(); const port = 3123; - system.addFeature(new EndpointFeature(app, "/_")); + system.addHook(new EndpointHook(app, "/_")); let server = app.listen(port, () => { console.log(`Listening on port ${port}`); diff --git a/plugos/feature/endpoint.ts b/plugos/hooks/endpoint.ts similarity index 93% rename from plugos/feature/endpoint.ts rename to plugos/hooks/endpoint.ts index 0ee55615..3d7ff7f5 100644 --- a/plugos/feature/endpoint.ts +++ b/plugos/hooks/endpoint.ts @@ -1,4 +1,4 @@ -import { Feature, Manifest } from "../types"; +import { Hook, Manifest } from "../types"; import { Express, NextFunction, Request, Response } from "express"; import { System } from "../system"; @@ -16,7 +16,7 @@ export type EndpointResponse = { body: any; }; -export type EndpointHook = { +export type EndpointHookT = { http?: EndPointDef | EndPointDef[]; }; @@ -25,7 +25,7 @@ export type EndPointDef = { path: string; }; -export class EndpointFeature implements Feature { +export class EndpointHook implements Hook { private app: Express; private prefix: string; @@ -34,7 +34,7 @@ export class EndpointFeature implements Feature { this.prefix = prefix; } - apply(system: System): void { + apply(system: System): void { this.app.use((req: Request, res: Response, next: NextFunction) => { if (!req.path.startsWith(this.prefix)) { return next(); @@ -106,7 +106,7 @@ export class EndpointFeature implements Feature { }); } - validateManifest(manifest: Manifest): string[] { + validateManifest(manifest: Manifest): string[] { let errors = []; for (const [name, functionDef] of Object.entries(manifest.functions)) { if (!functionDef.http) { diff --git a/plugos/feature/event.ts b/plugos/hooks/event.ts similarity index 77% rename from plugos/feature/event.ts rename to plugos/hooks/event.ts index 63c69904..8d20ee3c 100644 --- a/plugos/feature/event.ts +++ b/plugos/hooks/event.ts @@ -1,19 +1,19 @@ -import { Feature, Manifest } from "../types"; +import { Hook, Manifest } from "../types"; import { System } from "../system"; // System events: // - plug:load (plugName: string) -export type EventHook = { +export type EventHookT = { events?: string[]; }; -export class EventFeature implements Feature { - private system?: System; +export class EventHook implements Hook { + private system?: System; async dispatchEvent(eventName: string, data?: any): Promise { if (!this.system) { - throw new Error("EventFeature is not initialized"); + throw new Error("Event hook is not initialized"); } let promises: Promise[] = []; for (const plug of this.system.loadedPlugs.values()) { @@ -31,7 +31,7 @@ export class EventFeature implements Feature { return Promise.all(promises); } - apply(system: System): void { + apply(system: System): void { this.system = system; this.system.on({ plugLoaded: (name) => { @@ -40,7 +40,7 @@ export class EventFeature implements Feature { }); } - validateManifest(manifest: Manifest): string[] { + validateManifest(manifest: Manifest): string[] { let errors = []; for (const [name, functionDef] of Object.entries(manifest.functions)) { if (functionDef.events && !Array.isArray(functionDef.events)) { diff --git a/plugos/feature/node_cron.ts b/plugos/hooks/node_cron.ts similarity index 88% rename from plugos/feature/node_cron.ts rename to plugos/hooks/node_cron.ts index 7d565d02..ddf4b97a 100644 --- a/plugos/feature/node_cron.ts +++ b/plugos/hooks/node_cron.ts @@ -1,14 +1,14 @@ -import { Feature, Manifest } from "../types"; +import { Hook, Manifest } from "../types"; import cron, { ScheduledTask } from "node-cron"; import { safeRun } from "../util"; import { System } from "../system"; -export type CronHook = { +export type CronHookT = { cron?: string | string[]; }; -export class NodeCronFeature implements Feature { - apply(system: System): void { +export class NodeCronHook implements Hook { + apply(system: System): void { let tasks: ScheduledTask[] = []; system.on({ plugLoaded: (name, plug) => { @@ -56,7 +56,7 @@ export class NodeCronFeature implements Feature { } } - validateManifest(manifest: Manifest): string[] { + validateManifest(manifest: Manifest): string[] { let errors = []; for (const [name, functionDef] of Object.entries(manifest.functions)) { if (!functionDef.cron) { diff --git a/plugos/package.json b/plugos/package.json index 4c711ff9..c785d27b 100644 --- a/plugos/package.json +++ b/plugos/package.json @@ -10,7 +10,7 @@ "watch": "rm -rf .parcel-cache && parcel watch", "build": "parcel build", "clean": "rm -rf dist", - "test": "jest dist" + "test": "jest dist/test" }, "files": [ "dist" @@ -28,9 +28,9 @@ "test": { "source": [ "runtime.test.ts", - "feature/endpoint.test.ts", - "syscall/store.knex_node.test.ts", - "syscall/store.dexie_browser.test.ts" + "hooks/endpoint.test.ts", + "syscalls/store.knex_node.test.ts", + "syscalls/store.dexie_browser.test.ts" ], "outputFormat": "commonjs", "isLibrary": true, diff --git a/plugos/plug_loader.ts b/plugos/plug_loader.ts index 585263f8..d61b01f8 100644 --- a/plugos/plug_loader.ts +++ b/plugos/plug_loader.ts @@ -1,7 +1,7 @@ import fs from "fs/promises"; import watch from "node-watch"; import path from "path"; -import { createSandbox } from "./environment/node_sandbox"; +import { createSandbox } from "./environments/node_sandbox"; import { safeRun } from "../server/util"; import { System } from "./system"; diff --git a/plugos/runtime.test.ts b/plugos/runtime.test.ts index cd671673..e22a18f8 100644 --- a/plugos/runtime.test.ts +++ b/plugos/runtime.test.ts @@ -1,4 +1,4 @@ -import { createSandbox } from "./environment/node_sandbox"; +import { createSandbox } from "./environments/node_sandbox"; import { expect, test } from "@jest/globals"; import { System } from "./system"; diff --git a/plugos/sandbox.ts b/plugos/sandbox.ts index 1ec78dba..63c5de97 100644 --- a/plugos/sandbox.ts +++ b/plugos/sandbox.ts @@ -2,7 +2,7 @@ import { ControllerMessage, WorkerLike, WorkerMessage, -} from "./environment/worker"; +} from "./environments/worker"; import { Plug } from "./plug"; export type SandboxFactory = (plug: Plug) => Sandbox; diff --git a/plugos/syscall/event.ts b/plugos/syscall/event.ts deleted file mode 100644 index 7fcca5e8..00000000 --- a/plugos/syscall/event.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { SysCallMapping } from "../system"; -import { EventFeature } from "../feature/event"; - -export function eventSyscalls(eventFeature: EventFeature): SysCallMapping { - return { - async dispatch(ctx, eventName: string, data: any) { - return eventFeature.dispatchEvent(eventName, data); - }, - }; -} diff --git a/plugos/syscalls/event.ts b/plugos/syscalls/event.ts new file mode 100644 index 00000000..92aa88af --- /dev/null +++ b/plugos/syscalls/event.ts @@ -0,0 +1,10 @@ +import { SysCallMapping } from "../system"; +import { EventHook } from "../hooks/event"; + +export function eventSyscalls(eventHook: EventHook): SysCallMapping { + return { + async dispatch(ctx, eventName: string, data: any) { + return eventHook.dispatchEvent(eventName, data); + }, + }; +} diff --git a/plugos/syscall/fetch.node.ts b/plugos/syscalls/fetch.node.ts similarity index 100% rename from plugos/syscall/fetch.node.ts rename to plugos/syscalls/fetch.node.ts diff --git a/plugos/syscall/shell.node.ts b/plugos/syscalls/shell.node.ts similarity index 100% rename from plugos/syscall/shell.node.ts rename to plugos/syscalls/shell.node.ts diff --git a/plugos/syscall/store.dexie_browser.test.ts b/plugos/syscalls/store.dexie_browser.test.ts similarity index 96% rename from plugos/syscall/store.dexie_browser.test.ts rename to plugos/syscalls/store.dexie_browser.test.ts index d519823a..b649106e 100644 --- a/plugos/syscall/store.dexie_browser.test.ts +++ b/plugos/syscalls/store.dexie_browser.test.ts @@ -1,4 +1,4 @@ -import { createSandbox } from "../environment/node_sandbox"; +import { createSandbox } from "../environments/node_sandbox"; import { expect, test } from "@jest/globals"; import { System } from "../system"; import { storeSyscalls } from "./store.dexie_browser"; diff --git a/plugos/syscall/store.dexie_browser.ts b/plugos/syscalls/store.dexie_browser.ts similarity index 100% rename from plugos/syscall/store.dexie_browser.ts rename to plugos/syscalls/store.dexie_browser.ts diff --git a/plugos/syscall/store.knex_node.test.ts b/plugos/syscalls/store.knex_node.test.ts similarity index 94% rename from plugos/syscall/store.knex_node.test.ts rename to plugos/syscalls/store.knex_node.test.ts index 9def2b88..609be5e1 100644 --- a/plugos/syscall/store.knex_node.test.ts +++ b/plugos/syscalls/store.knex_node.test.ts @@ -1,4 +1,4 @@ -import { createSandbox } from "../environment/node_sandbox"; +import { createSandbox } from "../environments/node_sandbox"; import { expect, test } from "@jest/globals"; import { System } from "../system"; import { diff --git a/plugos/syscall/store.knex_node.ts b/plugos/syscalls/store.knex_node.ts similarity index 100% rename from plugos/syscall/store.knex_node.ts rename to plugos/syscalls/store.knex_node.ts diff --git a/plugos/syscall/transport.ts b/plugos/syscalls/transport.ts similarity index 100% rename from plugos/syscall/transport.ts rename to plugos/syscalls/transport.ts diff --git a/plugos/system.ts b/plugos/system.ts index f99c4f9f..86bda8fe 100644 --- a/plugos/system.ts +++ b/plugos/system.ts @@ -1,4 +1,4 @@ -import { Feature, Manifest, RuntimeEnvironment } from "./types"; +import { Hook, Manifest, RuntimeEnvironment } from "./types"; import { EventEmitter } from "../common/event"; import { SandboxFactory } from "./sandbox"; import { Plug } from "./plug"; @@ -31,7 +31,7 @@ type Syscall = { export class System extends EventEmitter> { protected plugs = new Map>(); protected registeredSyscalls = new Map(); - protected enabledFeatures = new Set>(); + protected enabledHooks = new Set>(); readonly runtimeEnv: RuntimeEnvironment; @@ -40,8 +40,8 @@ export class System extends EventEmitter> { this.runtimeEnv = env; } - addFeature(feature: Feature) { - this.enabledFeatures.add(feature); + addHook(feature: Hook) { + this.enabledHooks.add(feature); feature.apply(this); } @@ -91,7 +91,7 @@ export class System extends EventEmitter> { } // Validate let errors: string[] = []; - for (const feature of this.enabledFeatures) { + for (const feature of this.enabledHooks) { errors = [...errors, ...feature.validateManifest(manifest)]; } if (errors.length > 0) { diff --git a/plugos/tsconfig.json b/plugos/tsconfig.json index c4528773..1d242b80 100644 --- a/plugos/tsconfig.json +++ b/plugos/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["bin/*", "environment/*", "feature/**", "syscall/*", "*"], + "include": ["bin/*", "environments/*", "hooks/**", "syscalls/*", "*"], "compilerOptions": { "target": "esnext", "strict": true, diff --git a/plugos/types.ts b/plugos/types.ts index 3da3103e..a671b7a6 100644 --- a/plugos/types.ts +++ b/plugos/types.ts @@ -15,7 +15,7 @@ export type FunctionDef = { export type RuntimeEnvironment = "client" | "server"; -export interface Feature { +export interface Hook { validateManifest(manifest: Manifest): string[]; apply(system: System): void; diff --git a/plugs/core/core.plug.yaml b/plugs/core/core.plug.yaml index bcfec855..de4e9f09 100644 --- a/plugs/core/core.plug.yaml +++ b/plugs/core/core.plug.yaml @@ -44,15 +44,10 @@ functions: - page:click insertToday: path: "./dates.ts:insertToday" - command: - name: Insert Current Date - slashCommand: today + slashCommand: + name: today welcome: path: "./server.ts:welcome" events: - plug:load env: server -# renderMD: -# path: "./markdown.ts:renderMD" -# command: -# name: Render Markdown diff --git a/plugs/core/server.ts b/plugs/core/server.ts index 229b9dd6..6600110b 100644 --- a/plugs/core/server.ts +++ b/plugs/core/server.ts @@ -1,7 +1,4 @@ -import { - EndpointRequest, - EndpointResponse, -} from "../../plugos/feature/endpoint"; +import { EndpointRequest, EndpointResponse } from "../../plugos/hooks/endpoint"; export function endpointTest(req: EndpointRequest): EndpointResponse { console.log("I'm running on the server!", req); diff --git a/server/express_server.ts b/server/express_server.ts index a4524bed..da5b131c 100644 --- a/server/express_server.ts +++ b/server/express_server.ts @@ -1,6 +1,6 @@ import { Express } from "express"; import { SilverBulletHooks } from "../common/manifest"; -import { EndpointFeature } from "../plugos/feature/endpoint"; +import { EndpointHook } from "../plugos/hooks/endpoint"; import { readFile } from "fs/promises"; import { System } from "../plugos/system"; @@ -19,7 +19,7 @@ export class ExpressServer { this.rootPath = rootPath; this.system = system; - system.addFeature(new EndpointFeature(app, "/_")); + system.addHook(new EndpointHook(app, "/_")); // Fallback, serve index.html let cachedIndex: string | undefined = undefined; diff --git a/server/page_api.ts b/server/page_api.ts index b0c630da..732ce47c 100644 --- a/server/page_api.ts +++ b/server/page_api.ts @@ -11,9 +11,9 @@ import { stat } from "fs/promises"; import { Cursor, cursorEffect } from "../webapp/cursorEffect"; import { SilverBulletHooks } from "../common/manifest"; import { System } from "../plugos/system"; -import { EventFeature } from "../plugos/feature/event"; +import { EventHook } from "../plugos/hooks/event"; import spaceSyscalls from "./syscalls/space"; -import { eventSyscalls } from "../plugos/syscall/event"; +import { eventSyscalls } from "../plugos/syscalls/event"; export class PageApi implements ApiProvider { openPages: Map; @@ -21,7 +21,7 @@ export class PageApi implements ApiProvider { rootPath: string; connectedSockets: Set; private system: System; - private eventFeature: EventFeature; + private eventHook: EventHook; constructor( rootPath: string, @@ -34,10 +34,10 @@ export class PageApi implements ApiProvider { this.openPages = openPages; this.connectedSockets = connectedSockets; this.system = system; - this.eventFeature = new EventFeature(); - system.addFeature(this.eventFeature); + this.eventHook = new EventHook(); + system.addHook(this.eventHook); system.registerSyscalls("space", [], spaceSyscalls(this)); - system.registerSyscalls("event", [], eventSyscalls(this.eventFeature)); + system.registerSyscalls("event", [], eventSyscalls(this.eventHook)); } async init(): Promise { @@ -229,11 +229,8 @@ export class PageApi implements ApiProvider { " to disk and indexing." ); await this.flushPageToDisk(pageName, page); - await this.eventFeature.dispatchEvent( - "page:saved", - pageName - ); - await this.eventFeature.dispatchEvent("page:index", { + await this.eventHook.dispatchEvent("page:saved", pageName); + await this.eventHook.dispatchEvent("page:index", { name: pageName, text: page.text.sliceString(0), }); @@ -312,8 +309,8 @@ export class PageApi implements ApiProvider { this.openPages.delete(pageName); } // Trigger system events - await this.eventFeature.dispatchEvent("page:saved", pageName); - await this.eventFeature.dispatchEvent("page:index", { + await this.eventHook.dispatchEvent("page:saved", pageName); + await this.eventHook.dispatchEvent("page:index", { name: pageName, text: text, }); @@ -325,7 +322,7 @@ export class PageApi implements ApiProvider { clientConn.openPages.delete(pageName); // Cascading of this to all connected clients will be handled by file watcher await this.pageStore.deletePage(pageName); - await this.eventFeature.dispatchEvent("page:deleted", pageName); + await this.eventHook.dispatchEvent("page:deleted", pageName); }, listPages: async (clientConn: ClientConnection): Promise => { diff --git a/server/server.ts b/server/server.ts index d4b90d96..28d503d5 100755 --- a/server/server.ts +++ b/server/server.ts @@ -9,8 +9,8 @@ import {hideBin} from "yargs/helpers"; import {SilverBulletHooks} from "../common/manifest"; import {ExpressServer} from "./express_server"; import {DiskPlugLoader} from "../plugos/plug_loader"; -import {NodeCronFeature} from "../plugos/feature/node_cron"; -import shellSyscalls from "../plugos/syscall/shell.node"; +import {NodeCronHook} from "../plugos/hooks/node_cron"; +import shellSyscalls from "../plugos/syscalls/shell.node"; import {System} from "../plugos/system"; let args = yargs(hideBin(process.argv)) @@ -21,7 +21,7 @@ let args = yargs(hideBin(process.argv)) .parse(); if (!args._.length) { - console.error("Usage: silverbullet "); + console.error("Usage: silverbullet "); process.exit(1); } @@ -58,11 +58,11 @@ expressServer ); await plugLoader.loadPlugs(); plugLoader.watcher(); - system.registerSyscalls("shell", ["shell"], shellSyscalls(pagesPath)); - system.addFeature(new NodeCronFeature()); - server.listen(port, () => { - console.log(`Server listening on port ${port}`); - }); + system.registerSyscalls("shell", ["shell"], shellSyscalls(pagesPath)); + system.addHook(new NodeCronHook()); + server.listen(port, () => { + console.log(`Server listening on port ${port}`); + }); }) .catch((e) => { console.error(e); diff --git a/server/syscalls/page_index.ts b/server/syscalls/page_index.ts index 2dc076b4..2af54dae 100644 --- a/server/syscalls/page_index.ts +++ b/server/syscalls/page_index.ts @@ -5,7 +5,7 @@ import { ensureTable, storeReadSyscalls, storeWriteSyscalls, -} from "../../plugos/syscall/store.knex_node"; +} from "../../plugos/syscalls/store.knex_node"; type IndexItem = { page: string; diff --git a/webapp/components/command_palette.tsx b/webapp/components/command_palette.tsx index aca4172f..4bc99731 100644 --- a/webapp/components/command_palette.tsx +++ b/webapp/components/command_palette.tsx @@ -1,7 +1,7 @@ -import { AppCommand } from "../types"; import { isMacLike } from "../util"; import { FilterList, Option } from "./filter"; import { faPersonRunning } from "@fortawesome/free-solid-svg-icons"; +import { AppCommand } from "../hooks/command"; export function CommandPalette({ commands, @@ -18,7 +18,6 @@ export function CommandPalette({ hint: isMac && def.command.mac ? def.command.mac : def.command.key, }); } - console.log("Commands", options); return ( ("client"); openPages = new Map(); - editorCommands = new Map(); + commandHook: CommandHook; editorView?: EditorView; viewState: AppViewState; viewDispatch: React.Dispatch; space: Space; - navigationResolve?: (val: undefined) => void; pageNavigator: PathPageNavigator; - private eventFeature: EventFeature; + eventHook: EventHook; + private slashCommandHook: SlashCommandHook; constructor(space: Space, parent: Element) { this.space = space; this.viewState = initialViewState; this.viewDispatch = () => {}; - this.eventFeature = new EventFeature(); - this.system.addFeature(this.eventFeature); + // Event hook + this.eventHook = new EventHook(); + this.system.addHook(this.eventHook); + + // Command hook + this.commandHook = new CommandHook(); + this.commandHook.on({ + commandsUpdated: (commandMap) => { + this.viewDispatch({ + type: "update-commands", + commands: commandMap, + }); + }, + }); + this.system.addHook(this.commandHook); + + // Slash command hook + this.slashCommandHook = new SlashCommandHook(this); + this.system.addHook(this.slashCommandHook); this.render(parent); this.editorView = new EditorView({ @@ -142,14 +153,12 @@ export class Editor implements AppEventDispatcher { loadSystem: (systemJSON) => { safeRun(async () => { await this.system.replaceAllFromJSON(systemJSON, createIFrameSandbox); - this.buildAllCommands(); }); }, plugLoaded: (plugName, plug) => { safeRun(async () => { console.log("Plug load", plugName); await this.system.load(plugName, plug, createIFrameSandbox); - this.buildAllCommands(); }); }, plugUnloaded: (plugName) => { @@ -161,39 +170,10 @@ export class Editor implements AppEventDispatcher { }); if (this.pageNavigator.getCurrentPage() === "") { - this.pageNavigator.navigate("start"); + await this.pageNavigator.navigate("start"); } } - private buildAllCommands() { - console.log("Loaded plugs, now updating editor commands"); - this.editorCommands.clear(); - for (let plug of this.system.loadedPlugs.values()) { - for (const [name, functionDef] of Object.entries( - plug.manifest!.functions - )) { - if (!functionDef.command) { - continue; - } - const cmds = Array.isArray(functionDef.command) - ? functionDef.command - : [functionDef.command]; - for (let cmd of cmds) { - this.editorCommands.set(cmd.name, { - command: cmd, - run: () => { - return plug.invoke(name, []); - }, - }); - } - } - } - this.viewDispatch({ - type: "update-commands", - commands: this.editorCommands, - }); - } - flashNotification(message: string) { let id = Math.floor(Math.random() * 1000000); this.viewDispatch({ @@ -213,7 +193,7 @@ export class Editor implements AppEventDispatcher { } async dispatchAppEvent(name: AppEvent, data?: any): Promise { - return this.eventFeature.dispatchEvent(name, data); + return this.eventHook.dispatchEvent(name, data); } get currentPage(): string | undefined { @@ -222,7 +202,7 @@ export class Editor implements AppEventDispatcher { createEditorState(pageName: string, doc: CollabDocument): EditorState { let commandKeyBindings: KeyBinding[] = []; - for (let def of this.editorCommands.values()) { + for (let def of this.commandHook.editorCommands.values()) { if (def.command.key) { commandKeyBindings.push({ key: def.command.key, @@ -257,7 +237,9 @@ export class Editor implements AppEventDispatcher { autocompletion({ override: [ this.plugCompleter.bind(this), - this.commandCompleter.bind(this), + this.slashCommandHook.slashCommandCompleter.bind( + this.slashCommandHook + ), ], }), EditorView.lineWrapping, @@ -361,39 +343,6 @@ export class Editor implements AppEventDispatcher { return null; } - commandCompleter(ctx: CompletionContext): CompletionResult | null { - let prefix = ctx.matchBefore(slashCommandRegexp); - if (!prefix) { - return null; - } - let options: Completion[] = []; - for (let [name, def] of this.viewState.commands) { - if (!def.command.slashCommand) { - continue; - } - options.push({ - label: def.command.slashCommand, - detail: name, - apply: () => { - this.editorView?.dispatch({ - changes: { - from: prefix!.from, - to: ctx.pos, - insert: "", - }, - }); - safeRun(async () => { - await def.run(); - }); - }, - }); - } - return { - from: prefix.from + 1, - options: options, - }; - } - focus() { this.editorView!.focus(); } @@ -469,7 +418,7 @@ export class Editor implements AppEventDispatcher { editor.focus(); if (page) { safeRun(async () => { - editor.navigate(page); + await editor.navigate(page); }); } }} @@ -497,7 +446,7 @@ export class Editor implements AppEventDispatcher { dispatch({ type: "start-navigate" }); }} /> -
+
); } diff --git a/webapp/hooks/command.ts b/webapp/hooks/command.ts new file mode 100644 index 00000000..1f82c165 --- /dev/null +++ b/webapp/hooks/command.ts @@ -0,0 +1,75 @@ +import { Hook, Manifest } from "../../plugos/types"; +import { System } from "../../plugos/system"; +import { EventEmitter } from "../../common/event"; + +export type CommandDef = { + name: string; + + // Bind to keyboard shortcut + key?: string; + mac?: string; +}; + +export type AppCommand = { + command: CommandDef; + run: () => Promise; +}; + +export type CommandHookT = { + command?: CommandDef; +}; + +export type CommandHookEvents = { + commandsUpdated(commandMap: Map): void; +}; + +export class CommandHook + extends EventEmitter + implements Hook +{ + editorCommands = new Map(); + + buildAllCommands(system: System) { + this.editorCommands.clear(); + for (let plug of system.loadedPlugs.values()) { + for (const [name, functionDef] of Object.entries( + plug.manifest!.functions + )) { + if (!functionDef.command) { + continue; + } + const cmd = functionDef.command; + this.editorCommands.set(cmd.name, { + command: cmd, + run: () => { + return plug.invoke(name, []); + }, + }); + } + } + this.emit("commandsUpdated", this.editorCommands); + } + + apply(system: System): void { + this.buildAllCommands(system); + system.on({ + plugLoaded: () => { + this.buildAllCommands(system); + }, + }); + } + + validateManifest(manifest: Manifest): string[] { + let errors = []; + for (const [name, functionDef] of Object.entries(manifest.functions)) { + if (!functionDef.command) { + continue; + } + const cmd = functionDef.command; + if (!cmd.name) { + errors.push(`Function ${name} has a command but no name`); + } + } + return []; + } +} diff --git a/webapp/hooks/slash_command.ts b/webapp/hooks/slash_command.ts new file mode 100644 index 00000000..4439388a --- /dev/null +++ b/webapp/hooks/slash_command.ts @@ -0,0 +1,111 @@ +import { Hook, Manifest } from "../../plugos/types"; +import { System } from "../../plugos/system"; +import { + Completion, + CompletionContext, + CompletionResult, +} from "@codemirror/autocomplete"; +import { slashCommandRegexp } from "../types"; +import { safeRun } from "../util"; +import { Editor } from "../editor"; + +export type SlashCommandDef = { + name: string; +}; + +export type AppSlashCommand = { + slashCommand: SlashCommandDef; + run: () => Promise; +}; + +export type SlashCommandHookT = { + slashCommand?: SlashCommandDef; +}; + +export class SlashCommandHook implements Hook { + slashCommands = new Map(); + private editor: Editor; + + constructor(editor: Editor) { + this.editor = editor; + } + + buildAllCommands(system: System) { + this.slashCommands.clear(); + for (let plug of system.loadedPlugs.values()) { + for (const [name, functionDef] of Object.entries( + plug.manifest!.functions + )) { + if (!functionDef.slashCommand) { + continue; + } + const cmd = functionDef.slashCommand; + this.slashCommands.set(cmd.name, { + slashCommand: cmd, + run: () => { + return plug.invoke(name, []); + }, + }); + } + } + } + + // Completer for CodeMirror + public slashCommandCompleter( + ctx: CompletionContext + ): CompletionResult | null { + let prefix = ctx.matchBefore(slashCommandRegexp); + if (!prefix) { + return null; + } + let options: Completion[] = []; + for (let [name, def] of this.slashCommands.entries()) { + options.push({ + label: def.slashCommand.name, + detail: name, + apply: () => { + // Delete slash command part + this.editor.editorView?.dispatch({ + changes: { + from: prefix!.from, + to: ctx.pos, + insert: "", + }, + }); + // Replace with whatever the completion is + safeRun(async () => { + await def.run(); + }); + }, + }); + } + return { + // + 1 because of the '/' + from: prefix.from + 1, + options: options, + }; + } + + apply(system: System): void { + this.buildAllCommands(system); + system.on({ + plugLoaded: () => { + this.buildAllCommands(system); + }, + }); + } + + validateManifest(manifest: Manifest): string[] { + let errors = []; + for (const [name, functionDef] of Object.entries(manifest.functions)) { + if (!functionDef.slashCommand) { + continue; + } + const cmd = functionDef.slashCommand; + if (!cmd.name) { + errors.push(`Function ${name} has a command but no name`); + } + } + return []; + } +} diff --git a/webapp/syscalls/indexer.ts b/webapp/syscalls/indexer.ts index f539766b..08a64858 100644 --- a/webapp/syscalls/indexer.ts +++ b/webapp/syscalls/indexer.ts @@ -1,6 +1,6 @@ import { Space } from "../space"; import { SysCallMapping } from "../../plugos/system"; -import { transportSyscalls } from "../../plugos/syscall/transport"; +import { transportSyscalls } from "../../plugos/syscalls/transport"; export default function indexerSyscalls(space: Space): SysCallMapping { return transportSyscalls( diff --git a/webapp/types.ts b/webapp/types.ts index b4ddd3e1..faa6af05 100644 --- a/webapp/types.ts +++ b/webapp/types.ts @@ -1,4 +1,4 @@ -import { CommandDef } from "../common/manifest"; +import { AppCommand } from "./hooks/command"; export type PageMeta = { name: string; @@ -7,11 +7,6 @@ export type PageMeta = { lastOpened?: number; }; -export type AppCommand = { - command: CommandDef; - run: () => Promise; -}; - export const slashCommandRegexp = /\/[\w\-]*/; export type Notification = {