1
0

Rewrote some navigation stuff based on new parser

This commit is contained in:
Zef Hemel 2022-04-03 18:42:12 +02:00
parent 16bf0d866d
commit 07453d638b
21 changed files with 206 additions and 235 deletions

View File

@ -3,13 +3,13 @@ import {MarkdownTree, nodeAtPos, parse, render} from "../tree";
export function markdownSyscalls(): SysCallMapping { export function markdownSyscalls(): SysCallMapping {
return { return {
parse(ctx, text: string): MarkdownTree { "markdown.parse": (ctx, text: string): MarkdownTree => {
return parse(text); return parse(text);
}, },
nodeAtPos(ctx, mdTree: MarkdownTree, pos: number): MarkdownTree | null { "markdown.nodeAtPos": (ctx, mdTree: MarkdownTree, pos: number): MarkdownTree | null => {
return nodeAtPos(mdTree, pos); return nodeAtPos(mdTree, pos);
}, },
render(ctx, mdTree: MarkdownTree): string { "markdown.render": (ctx, mdTree: MarkdownTree): string => {
return render(mdTree); return render(mdTree);
}, },
}; };

View File

@ -8,7 +8,7 @@ export async function parse(text: string): Promise<MarkdownTree> {
export async function nodeAtPos( export async function nodeAtPos(
mdTree: MarkdownTree, mdTree: MarkdownTree,
pos: number pos: number
): Promise<any | null> { ): Promise<MarkdownTree | null> {
return syscall("markdown.nodeAtPos", mdTree, pos); return syscall("markdown.nodeAtPos", mdTree, pos);
} }

View File

@ -10,11 +10,7 @@ import {System} from "../system";
import {EndpointHook, EndpointHookT} from "../hooks/endpoint"; import {EndpointHook, EndpointHookT} from "../hooks/endpoint";
import {safeRun} from "../util"; import {safeRun} from "../util";
import knex from "knex"; import knex from "knex";
import { import {ensureTable, storeSyscalls} from "../syscalls/store.knex_node";
ensureTable,
storeReadSyscalls,
storeWriteSyscalls,
} from "../syscalls/store.knex_node";
import {fetchSyscalls} from "../syscalls/fetch.node"; import {fetchSyscalls} from "../syscalls/fetch.node";
import {EventHook, EventHookT} from "../hooks/event"; import {EventHook, EventHookT} from "../hooks/event";
import {eventSyscalls} from "../syscalls/event"; import {eventSyscalls} from "../syscalls/event";
@ -55,16 +51,11 @@ safeRun(async () => {
system.addHook(new NodeCronHook()); system.addHook(new NodeCronHook());
let eventHook = new EventHook(); let eventHook = new EventHook();
system.addHook(eventHook); system.addHook(eventHook);
system.registerSyscalls("event", [], eventSyscalls(eventHook)); system.registerSyscalls([], eventSyscalls(eventHook));
system.addHook(new EndpointHook(app, "")); system.addHook(new EndpointHook(app, ""));
system.registerSyscalls("shell", [], shellSyscalls(".")); system.registerSyscalls([], shellSyscalls("."));
system.registerSyscalls("fetch", [], fetchSyscalls()); system.registerSyscalls([], fetchSyscalls());
system.registerSyscalls( system.registerSyscalls([], storeSyscalls(db, "item"));
"store",
[],
storeWriteSyscalls(db, "item"),
storeReadSyscalls(db, "item")
);
app.listen(args.port, () => { app.listen(args.port, () => {
console.log(`Plugbox server listening on port ${args.port}`); console.log(`Plugbox server listening on port ${args.port}`);
}); });

View File

@ -1,10 +1,10 @@
import { createSandbox } from "./environments/node_sandbox"; import {createSandbox} from "./environments/node_sandbox";
import { expect, test } from "@jest/globals"; import {expect, test} from "@jest/globals";
import { System } from "./system"; import {System} from "./system";
test("Run a Node sandbox", async () => { test("Run a Node sandbox", async () => {
let system = new System("server"); let system = new System("server");
system.registerSyscalls("", [], { system.registerSyscalls([], {
addNumbers: (ctx, a, b) => { addNumbers: (ctx, a, b) => {
return a + b; return a + b;
}, },
@ -12,12 +12,12 @@ test("Run a Node sandbox", async () => {
throw new Error("#fail"); throw new Error("#fail");
}, },
}); });
system.registerSyscalls("", ["restricted"], { system.registerSyscalls(["restricted"], {
restrictedSyscall: () => { restrictedSyscall: () => {
return "restricted"; return "restricted";
}, },
}); });
system.registerSyscalls("", ["dangerous"], { system.registerSyscalls(["dangerous"], {
dangerousSyscall: () => { dangerousSyscall: () => {
return "yay"; return "yay";
}, },

View File

@ -1,9 +1,9 @@
import { SysCallMapping } from "../system"; import {SysCallMapping} from "../system";
import { EventHook } from "../hooks/event"; import {EventHook} from "../hooks/event";
export function eventSyscalls(eventHook: EventHook): SysCallMapping { export function eventSyscalls(eventHook: EventHook): SysCallMapping {
return { return {
async dispatch(ctx, eventName: string, data: any) { "event.dispatch": async(ctx, eventName: string, data: any) => {
return eventHook.dispatchEvent(eventName, data); return eventHook.dispatchEvent(eventName, data);
}, },
}; };

View File

@ -1,13 +1,13 @@
import fetch, { RequestInfo, RequestInit } from "node-fetch"; import fetch, {RequestInfo, RequestInit} from "node-fetch";
import { SysCallMapping } from "../system"; import {SysCallMapping} from "../system";
export function fetchSyscalls(): SysCallMapping { export function fetchSyscalls(): SysCallMapping {
return { return {
async json(ctx, url: RequestInfo, init: RequestInit) { "fetch.json": async (ctx, url: RequestInfo, init: RequestInit) => {
let resp = await fetch(url, init); let resp = await fetch(url, init);
return resp.json(); return resp.json();
}, },
async text(ctx, url: RequestInfo, init: RequestInit) { "fetch.text": async(ctx, url: RequestInfo, init: RequestInit) => {
let resp = await fetch(url, init); let resp = await fetch(url, init);
return resp.text(); return resp.text();
}, },

View File

@ -1,12 +1,12 @@
import { promisify } from "util"; import {promisify} from "util";
import { execFile } from "child_process"; import {execFile} from "child_process";
import type { SysCallMapping } from "../system"; import type {SysCallMapping} from "../system";
const execFilePromise = promisify(execFile); const execFilePromise = promisify(execFile);
export default function (cwd: string): SysCallMapping { export default function (cwd: string): SysCallMapping {
return { return {
run: async ( "shell.run": async (
ctx, ctx,
cmd: string, cmd: string,
args: string[] args: string[]

View File

@ -1,14 +1,14 @@
import { createSandbox } from "../environments/node_sandbox"; import {createSandbox} from "../environments/node_sandbox";
import { expect, test } from "@jest/globals"; import {expect, test} from "@jest/globals";
import { System } from "../system"; import {System} from "../system";
import { storeSyscalls } from "./store.dexie_browser"; import {storeSyscalls} from "./store.dexie_browser";
// For testing in node.js // For testing in node.js
require("fake-indexeddb/auto"); require("fake-indexeddb/auto");
test("Test store", async () => { test("Test store", async () => {
let system = new System("server"); let system = new System("server");
system.registerSyscalls("store", [], storeSyscalls("test", "test")); system.registerSyscalls([], storeSyscalls("test", "test"));
let plug = await system.load( let plug = await system.load(
"test", "test",
{ {

View File

@ -1,5 +1,5 @@
import Dexie from "dexie"; import Dexie from "dexie";
import { SysCallMapping } from "../system"; import {SysCallMapping} from "../system";
export type KV = { export type KV = {
key: string; key: string;
@ -17,26 +17,26 @@ export function storeSyscalls(
const items = db.table(tableName); const items = db.table(tableName);
return { return {
async delete(ctx, key: string) { "store.delete": async (ctx, key: string) => {
await items.delete(key); await items.delete(key);
}, },
async deletePrefix(ctx, prefix: string) { "store.deletePrefix": async (ctx, prefix: string) => {
await items.where("key").startsWith(prefix).delete(); await items.where("key").startsWith(prefix).delete();
}, },
async deleteAll() { "store.deleteAll": async () => {
await items.clear(); await items.clear();
}, },
async set(ctx, key: string, value: any) { "store.set": async (ctx, key: string, value: any) => {
await items.put({ await items.put({
key, key,
value, value,
}); });
}, },
async batchSet(ctx, kvs: KV[]) { "store.batchSet": async (ctx, kvs: KV[]) => {
await items.bulkPut( await items.bulkPut(
kvs.map(({ key, value }) => ({ kvs.map(({ key, value }) => ({
key, key,
@ -45,17 +45,17 @@ export function storeSyscalls(
); );
}, },
async get(ctx, key: string): Promise<any | null> { "store.get": async (ctx, key: string): Promise<any | null> => {
let result = await items.get({ let result = await items.get({
key, key,
}); });
return result ? result.value : null; return result ? result.value : null;
}, },
async queryPrefix( "store.queryPrefix": async (
ctx, ctx,
keyPrefix: string keyPrefix: string
): Promise<{ key: string; value: any }[]> { ): Promise<{ key: string; value: any }[]> => {
let results = await items.where("key").startsWith(keyPrefix).toArray(); let results = await items.where("key").startsWith(keyPrefix).toArray();
return results.map((result) => ({ return results.map((result) => ({
key: result.key, key: result.key,

View File

@ -1,11 +1,7 @@
import { createSandbox } from "../environments/node_sandbox"; import {createSandbox} from "../environments/node_sandbox";
import { expect, test } from "@jest/globals"; import {expect, test} from "@jest/globals";
import { System } from "../system"; import {System} from "../system";
import { import {ensureTable, storeSyscalls} from "./store.knex_node";
ensureTable,
storeReadSyscalls,
storeWriteSyscalls,
} from "./store.knex_node";
import knex from "knex"; import knex from "knex";
import fs from "fs/promises"; import fs from "fs/promises";
@ -19,12 +15,7 @@ test("Test store", async () => {
}); });
await ensureTable(db, "test_table"); await ensureTable(db, "test_table");
let system = new System("server"); let system = new System("server");
system.registerSyscalls( system.registerSyscalls([], storeSyscalls(db, "test_table"));
"store",
[],
storeWriteSyscalls(db, "test_table"),
storeReadSyscalls(db, "test_table")
);
let plug = await system.load( let plug = await system.load(
"test", "test",
{ {

View File

@ -1,5 +1,5 @@
import { Knex } from "knex"; import {Knex} from "knex";
import { SysCallMapping } from "../system"; import {SysCallMapping} from "../system";
type Item = { type Item = {
page: string; page: string;
@ -23,21 +23,21 @@ export async function ensureTable(db: Knex<any, unknown>, tableName: string) {
} }
} }
export function storeWriteSyscalls( export function storeSyscalls(
db: Knex<any, unknown>, db: Knex<any, unknown>,
tableName: string tableName: string
): SysCallMapping { ): SysCallMapping {
const apiObj: SysCallMapping = { const apiObj: SysCallMapping = {
delete: async (ctx, key: string) => { "store.delete": async (ctx, key: string) => {
await db<Item>(tableName).where({ key }).del(); await db<Item>(tableName).where({ key }).del();
}, },
deletePrefix: async (ctx, prefix: string) => { "store.deletePrefix": async (ctx, prefix: string) => {
return db<Item>(tableName).andWhereLike("key", `${prefix}%`).del(); return db<Item>(tableName).andWhereLike("key", `${prefix}%`).del();
}, },
deleteAll: async (ctx) => { "store.deleteAll": async (ctx) => {
await db<Item>(tableName).del(); await db<Item>(tableName).del();
}, },
set: async (ctx, key: string, value: any) => { "store.set": async (ctx, key: string, value: any) => {
let changed = await db<Item>(tableName) let changed = await db<Item>(tableName)
.where({ key }) .where({ key })
.update("value", JSON.stringify(value)); .update("value", JSON.stringify(value));
@ -49,26 +49,17 @@ export function storeWriteSyscalls(
} }
}, },
// TODO: Optimize // TODO: Optimize
batchSet: async (ctx, kvs: KV[]) => { "store.batchSet": async (ctx, kvs: KV[]) => {
for (let { key, value } of kvs) { for (let { key, value } of kvs) {
await apiObj.set(ctx, key, value); await apiObj["store.set"](ctx, key, value);
} }
}, },
batchDelete: async (ctx, keys: string[]) => { "store.batchDelete": async (ctx, keys: string[]) => {
for (let key of keys) { for (let key of keys) {
await apiObj.delete(ctx, key); await apiObj["store.delete"](ctx, key);
} }
}, },
}; "store.get": async (ctx, key: string): Promise<any | null> => {
return apiObj;
}
export function storeReadSyscalls(
db: Knex<any, unknown>,
tableName: string
): SysCallMapping {
return {
get: async (ctx, key: string): Promise<any | null> => {
let result = await db<Item>(tableName).where({ key }).select("value"); let result = await db<Item>(tableName).where({ key }).select("value");
if (result.length) { if (result.length) {
return JSON.parse(result[0].value); return JSON.parse(result[0].value);
@ -76,7 +67,7 @@ export function storeReadSyscalls(
return null; return null;
} }
}, },
queryPrefix: async (ctx, prefix: string) => { "store.queryPrefix": async (ctx, prefix: string) => {
return ( return (
await db<Item>(tableName) await db<Item>(tableName)
.andWhereLike("key", `${prefix}%`) .andWhereLike("key", `${prefix}%`)
@ -87,4 +78,5 @@ export function storeReadSyscalls(
})); }));
}, },
}; };
return apiObj;
} }

View File

@ -1,7 +1,7 @@
import { Hook, Manifest, RuntimeEnvironment } from "./types"; import {Hook, Manifest, RuntimeEnvironment} from "./types";
import { EventEmitter } from "../common/event"; import {EventEmitter} from "../common/event";
import { SandboxFactory } from "./sandbox"; import {SandboxFactory} from "./sandbox";
import { Plug } from "./plug"; import {Plug} from "./plug";
export interface SysCallMapping { export interface SysCallMapping {
[key: string]: (ctx: SyscallContext, ...args: any) => Promise<any> | any; [key: string]: (ctx: SyscallContext, ...args: any) => Promise<any> | any;
@ -29,31 +29,32 @@ type Syscall = {
}; };
export class System<HookT> extends EventEmitter<SystemEvents<HookT>> { export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
readonly runtimeEnv: RuntimeEnvironment;
protected plugs = new Map<string, Plug<HookT>>(); protected plugs = new Map<string, Plug<HookT>>();
protected registeredSyscalls = new Map<string, Syscall>(); protected registeredSyscalls = new Map<string, Syscall>();
protected enabledHooks = new Set<Hook<HookT>>(); protected enabledHooks = new Set<Hook<HookT>>();
readonly runtimeEnv: RuntimeEnvironment;
constructor(env: RuntimeEnvironment) { constructor(env: RuntimeEnvironment) {
super(); super();
this.runtimeEnv = env; this.runtimeEnv = env;
} }
get loadedPlugs(): Map<string, Plug<HookT>> {
return this.plugs;
}
addHook(feature: Hook<HookT>) { addHook(feature: Hook<HookT>) {
this.enabledHooks.add(feature); this.enabledHooks.add(feature);
feature.apply(this); feature.apply(this);
} }
registerSyscalls( registerSyscalls(
namespace: string,
requiredCapabilities: string[], requiredCapabilities: string[],
...registrationObjects: SysCallMapping[] ...registrationObjects: SysCallMapping[]
) { ) {
for (const registrationObject of registrationObjects) { for (const registrationObject of registrationObjects) {
for (let [name, callback] of Object.entries(registrationObject)) { for (let [name, callback] of Object.entries(registrationObject)) {
const callName = namespace ? `${namespace}.${name}` : name; this.registeredSyscalls.set(name, {
this.registeredSyscalls.set(callName, {
requiredPermissions: requiredCapabilities, requiredPermissions: requiredCapabilities,
callback, callback,
}); });
@ -116,10 +117,6 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
this.plugs.delete(name); this.plugs.delete(name);
} }
get loadedPlugs(): Map<string, Plug<HookT>> {
return this.plugs;
}
toJSON(): SystemJSON<HookT> { toJSON(): SystemJSON<HookT> {
let plugJSON: { [key: string]: Manifest<HookT> } = {}; let plugJSON: { [key: string]: Manifest<HookT> } = {};
for (let [name, plug] of this.plugs) { for (let [name, plug] of this.plugs) {

View File

@ -1,62 +1,54 @@
import {ClickEvent} from "../../webapp/app_event"; import {ClickEvent} from "../../webapp/app_event";
import {updateMaterializedQueriesCommand} from "./materialized_queries"; import {updateMaterializedQueriesCommand} from "./materialized_queries";
import { import {getCursor, getText, navigate as navigateTo, openUrl,} from "plugos-silverbullet-syscall/editor";
getSyntaxNodeAtPos,
getSyntaxNodeUnderCursor,
getText,
navigate as navigateTo,
openUrl,
} from "plugos-silverbullet-syscall/editor";
import {taskToggleAtPos} from "../tasks/task"; import {taskToggleAtPos} from "../tasks/task";
import {nodeAtPos, parse} from "plugos-silverbullet-syscall/markdown"; import {nodeAtPos, parse} from "plugos-silverbullet-syscall/markdown";
import type {MarkdownTree} from "../../common/tree";
const materializedQueryPrefix = /<!--\s*#query\s+/; const materializedQueryPrefix = /<!--\s*#query\s+/;
async function actionClickOrActionEnter(syntaxNode: any) { async function actionClickOrActionEnter(mdTree: MarkdownTree | null) {
if (!syntaxNode) { if (!mdTree) {
return; return;
} }
console.log("Attempting to navigate based on syntax node", syntaxNode); console.log("Attempting to navigate based on syntax node", mdTree);
switch (syntaxNode.name) { switch (mdTree.type) {
case "WikiLinkPage": case "WikiLinkPage":
let pageLink = syntaxNode.text; let pageLink = mdTree.children![0].text!;
let pos = 0; let pos = "0";
if (pageLink.includes("@")) { if (pageLink.includes("@")) {
[pageLink, pos] = syntaxNode.text.split("@"); [pageLink, pos] = pageLink.split("@");
} }
await navigateTo(pageLink, +pos); await navigateTo(pageLink, +pos);
break; break;
case "URL": case "URL":
await openUrl(syntaxNode.text); await openUrl(mdTree.children![0].text!);
break; break;
case "CommentBlock": case "CommentBlock":
if (syntaxNode.text.match(materializedQueryPrefix)) { if (mdTree.children![0].text!.match(materializedQueryPrefix)) {
await updateMaterializedQueriesCommand(); await updateMaterializedQueriesCommand();
} }
break; break;
case "Link": case "Link":
// Markdown link: [bla](URLHERE) needs extraction await openUrl(mdTree.children![4].children![0].text!);
let match = /\[[^\\]+\]\(([^\)]+)\)/.exec(syntaxNode.text);
if (match) {
await openUrl(match[1]);
}
break; break;
case "TaskMarker": case "TaskMarker":
await taskToggleAtPos(syntaxNode.from + 1); await taskToggleAtPos(mdTree.from + 1);
break; break;
} }
} }
export async function linkNavigate() { export async function linkNavigate() {
await actionClickOrActionEnter(await getSyntaxNodeUnderCursor()); let mdTree = await parse(await getText());
let newNode = await nodeAtPos(mdTree, await getCursor());
await actionClickOrActionEnter(newNode);
} }
export async function clickNavigate(event: ClickEvent) { export async function clickNavigate(event: ClickEvent) {
if (event.ctrlKey || event.metaKey) { if (event.ctrlKey || event.metaKey) {
let syntaxNode = await getSyntaxNodeAtPos(event.pos);
let mdTree = await parse(await getText()); let mdTree = await parse(await getText());
let newNode = await nodeAtPos(mdTree, event.pos); let newNode = await nodeAtPos(mdTree, event.pos);
console.log("New node", newNode); console.log("New node", newNode);
await actionClickOrActionEnter(syntaxNode); await actionClickOrActionEnter(newNode);
} }
} }

View File

@ -51,13 +51,13 @@ export class ExpressServer {
useNullAsDefault: true, useNullAsDefault: true,
}); });
system.registerSyscalls("shell", ["shell"], shellSyscalls(rootPath)); system.registerSyscalls(["shell"], shellSyscalls(rootPath));
system.addHook(new NodeCronHook()); system.addHook(new NodeCronHook());
system.registerSyscalls("index", [], pageIndexSyscalls(this.db)); system.registerSyscalls([], pageIndexSyscalls(this.db));
system.registerSyscalls("space", [], spaceSyscalls(this.storage)); system.registerSyscalls([], spaceSyscalls(this.storage));
system.registerSyscalls("event", [], eventSyscalls(this.eventHook)); system.registerSyscalls([], eventSyscalls(this.eventHook));
system.registerSyscalls("markdown", [], markdownSyscalls()); system.registerSyscalls([], markdownSyscalls());
system.addHook(new EndpointHook(app, "/_")); system.addHook(new EndpointHook(app, "/_"));
} }

View File

@ -1,11 +1,7 @@
import { Knex } from "knex"; import {Knex} from "knex";
import { SysCallMapping } from "../../plugos/system"; import {SysCallMapping} from "../../plugos/system";
import { import {ensureTable, storeSyscalls,} from "../../plugos/syscalls/store.knex_node";
ensureTable,
storeReadSyscalls,
storeWriteSyscalls,
} from "../../plugos/syscalls/store.knex_node";
type IndexItem = { type IndexItem = {
page: string; page: string;
@ -52,39 +48,38 @@ export async function ensurePageIndexTable(db: Knex<any, unknown>) {
} }
export function pageIndexSyscalls(db: Knex<any, unknown>): SysCallMapping { export function pageIndexSyscalls(db: Knex<any, unknown>): SysCallMapping {
const readCalls = storeReadSyscalls(db, "page_index"); const storeCalls = storeSyscalls(db, "page_index");
const writeCalls = storeWriteSyscalls(db, "page_index");
const apiObj: SysCallMapping = { const apiObj: SysCallMapping = {
set: async (ctx, page: string, key: string, value: any) => { "index.set": async (ctx, page: string, key: string, value: any) => {
await writeCalls.set(ctx, pageKey(page, key), value); await storeCalls["store.set"](ctx, pageKey(page, key), value);
await writeCalls.set(ctx, globalKey(page, key), value); await storeCalls["store.set"](ctx, globalKey(page, key), value);
}, },
batchSet: async (ctx, page: string, kvs: KV[]) => { "index.batchSet": async (ctx, page: string, kvs: KV[]) => {
for (let { key, value } of kvs) { for (let { key, value } of kvs) {
await apiObj.set(ctx, page, key, value); await apiObj["index.set"](ctx, page, key, value);
} }
}, },
delete: async (ctx, page: string, key: string) => { "index.delete": async (ctx, page: string, key: string) => {
await writeCalls.delete(ctx, pageKey(page, key)); await storeCalls["store.delete"](ctx, pageKey(page, key));
await writeCalls.delete(ctx, globalKey(page, key)); await storeCalls["store.delete"](ctx, globalKey(page, key));
}, },
get: async (ctx, page: string, key: string) => { "index.get": async (ctx, page: string, key: string) => {
return readCalls.get(ctx, pageKey(page, key)); return storeCalls["store.get"](ctx, pageKey(page, key));
}, },
scanPrefixForPage: async (ctx, page: string, prefix: string) => { "index.scanPrefixForPage": async (ctx, page: string, prefix: string) => {
return (await readCalls.queryPrefix(ctx, pageKey(page, prefix))).map( return (
({ key, value }: { key: string; value: any }) => { await storeCalls["store.queryPrefix"](ctx, pageKey(page, prefix))
).map(({ key, value }: { key: string; value: any }) => {
const { key: pageKey } = unpackPageKey(key); const { key: pageKey } = unpackPageKey(key);
return { return {
page, page,
key: pageKey, key: pageKey,
value, value,
}; };
} });
);
}, },
scanPrefixGlobal: async (ctx, prefix: string) => { "index.scanPrefixGlobal": async (ctx, prefix: string) => {
return (await readCalls.queryPrefix(ctx, `k~${prefix}`)).map( return (await storeCalls["store.queryPrefix"](ctx, `k~${prefix}`)).map(
({ key, value }: { key: string; value: any }) => { ({ key, value }: { key: string; value: any }) => {
const { page, key: pageKey } = unpackGlobalKey(key); const { page, key: pageKey } = unpackGlobalKey(key);
return { return {
@ -95,22 +90,22 @@ export function pageIndexSyscalls(db: Knex<any, unknown>): SysCallMapping {
} }
); );
}, },
clearPageIndexForPage: async (ctx, page: string) => { "index.clearPageIndexForPage": async (ctx, page: string) => {
await apiObj.deletePrefixForPage(ctx, page, ""); await apiObj["index.deletePrefixForPage"](ctx, page, "");
}, },
deletePrefixForPage: async (ctx, page: string, prefix: string) => { "index.deletePrefixForPage": async (ctx, page: string, prefix: string) => {
// Collect all global keys for this page to delete // Collect all global keys for this page to delete
let keysToDelete = ( let keysToDelete = (
await readCalls.queryPrefix(ctx, pageKey(page, prefix)) await storeCalls["store.queryPrefix"](ctx, pageKey(page, prefix))
).map(({ key }: { key: string; value: string }) => ).map(({ key }: { key: string; value: string }) =>
globalKey(page, unpackPageKey(key).key) globalKey(page, unpackPageKey(key).key)
); );
// Delete all page keys // Delete all page keys
await writeCalls.deletePrefix(ctx, pageKey(page, prefix)); await storeCalls["store.deletePrefix"](ctx, pageKey(page, prefix));
await writeCalls.batchDelete(ctx, keysToDelete); await storeCalls["store.batchDelete"](ctx, keysToDelete);
}, },
clearPageIndex: async (ctx) => { "index.clearPageIndex": async (ctx) => {
await writeCalls.deleteAll(ctx); await storeCalls["store.deleteAll"](ctx);
}, },
}; };
return apiObj; return apiObj;

View File

@ -1,22 +1,26 @@
import { PageMeta } from "../../common/types"; import {PageMeta} from "../../common/types";
import { SysCallMapping } from "../../plugos/system"; import {SysCallMapping} from "../../plugos/system";
import { Storage } from "../disk_storage"; import {Storage} from "../disk_storage";
export default (storage: Storage): SysCallMapping => { export default (storage: Storage): SysCallMapping => {
return { return {
listPages: (ctx): Promise<PageMeta[]> => { "space.listPages": (ctx): Promise<PageMeta[]> => {
return storage.listPages(); return storage.listPages();
}, },
readPage: async ( "space.readPage": async (
ctx, ctx,
name: string name: string
): Promise<{ text: string; meta: PageMeta }> => { ): Promise<{ text: string; meta: PageMeta }> => {
return storage.readPage(name); return storage.readPage(name);
}, },
writePage: async (ctx, name: string, text: string): Promise<PageMeta> => { "space.writePage": async (
ctx,
name: string,
text: string
): Promise<PageMeta> => {
return storage.writePage(name, text); return storage.writePage(name, text);
}, },
deletePage: async (ctx, name: string) => { "space.deletePage": async (ctx, name: string) => {
return storage.deletePage(name); return storage.deletePage(name);
}, },
}; };

View File

@ -47,7 +47,6 @@ import {CompleterHook} from "./hooks/completer";
import {pasteLinkExtension} from "./editor_paste"; import {pasteLinkExtension} from "./editor_paste";
import {markdownSyscalls} from "../common/syscalls/markdown"; import {markdownSyscalls} from "../common/syscalls/markdown";
class PageState { class PageState {
scrollTop: number; scrollTop: number;
selection: EditorSelection; selection: EditorSelection;
@ -61,11 +60,9 @@ class PageState {
const saveInterval = 2000; const saveInterval = 2000;
export class Editor implements AppEventDispatcher { export class Editor implements AppEventDispatcher {
private system = new System<SilverBulletHooks>("client");
readonly commandHook: CommandHook; readonly commandHook: CommandHook;
readonly slashCommandHook: SlashCommandHook; readonly slashCommandHook: SlashCommandHook;
readonly completerHook: CompleterHook; readonly completerHook: CompleterHook;
openPages = new Map<string, PageState>(); openPages = new Map<string, PageState>();
editorView?: EditorView; editorView?: EditorView;
viewState: AppViewState; viewState: AppViewState;
@ -73,6 +70,8 @@ export class Editor implements AppEventDispatcher {
space: Space; space: Space;
pageNavigator: PathPageNavigator; pageNavigator: PathPageNavigator;
eventHook: EventHook; eventHook: EventHook;
saveTimeout: any;
private system = new System<SilverBulletHooks>("client");
constructor(space: Space, parent: Element) { constructor(space: Space, parent: Element) {
this.space = space; this.space = space;
@ -110,11 +109,15 @@ export class Editor implements AppEventDispatcher {
}); });
this.pageNavigator = new PathPageNavigator(); this.pageNavigator = new PathPageNavigator();
this.system.registerSyscalls("editor", [], editorSyscalls(this)); this.system.registerSyscalls([], editorSyscalls(this));
this.system.registerSyscalls("space", [], spaceSyscalls(this)); this.system.registerSyscalls([], spaceSyscalls(this));
this.system.registerSyscalls("index", [], indexerSyscalls(this.space)); this.system.registerSyscalls([], indexerSyscalls(this.space));
this.system.registerSyscalls("system", [], systemSyscalls(this.space)); this.system.registerSyscalls([], systemSyscalls(this.space));
this.system.registerSyscalls("markdown", [], markdownSyscalls()); this.system.registerSyscalls([], markdownSyscalls());
}
get currentPage(): string | undefined {
return this.viewState.currentPage;
} }
async init() { async init() {
@ -180,8 +183,6 @@ export class Editor implements AppEventDispatcher {
} }
} }
saveTimeout: any;
async save(immediate: boolean = false): Promise<void> { async save(immediate: boolean = false): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.viewState.unsavedChanges) { if (!this.viewState.unsavedChanges) {
@ -236,10 +237,6 @@ export class Editor implements AppEventDispatcher {
return this.eventHook.dispatchEvent(name, data); return this.eventHook.dispatchEvent(name, data);
} }
get currentPage(): string | undefined {
return this.viewState.currentPage;
}
createEditorState(pageName: string, text: string): EditorState { createEditorState(pageName: string, text: string): EditorState {
let commandKeyBindings: KeyBinding[] = []; let commandKeyBindings: KeyBinding[] = [];
for (let def of this.commandHook.editorCommands.values()) { for (let def of this.commandHook.editorCommands.values()) {

View File

@ -28,37 +28,37 @@ function ensureAnchor(expr: any, start: boolean) {
export function editorSyscalls(editor: Editor): SysCallMapping { export function editorSyscalls(editor: Editor): SysCallMapping {
return { return {
getCurrentPage: (): string => { "editor.getCurrentPage": (): string => {
return editor.currentPage!; return editor.currentPage!;
}, },
getText: () => { "editor.getText": () => {
return editor.editorView?.state.sliceDoc(); return editor.editorView?.state.sliceDoc();
}, },
getCursor: (): number => { "editor.getCursor": (): number => {
return editor.editorView!.state.selection.main.from; return editor.editorView!.state.selection.main.from;
}, },
save: async () => { "editor.save": async () => {
return editor.save(true); return editor.save(true);
}, },
navigate: async (ctx, name: string, pos: number) => { "editor.navigate": async (ctx, name: string, pos: number) => {
await editor.navigate(name, pos); await editor.navigate(name, pos);
}, },
reloadPage: async (ctx) => { "editor.reloadPage": async (ctx) => {
await editor.reloadPage(); await editor.reloadPage();
}, },
openUrl: async (ctx, url: string) => { "editor.openUrl": async (ctx, url: string) => {
window.open(url, "_blank")!.focus(); window.open(url, "_blank")!.focus();
}, },
flashNotification: (ctx, message: string) => { "editor.flashNotification": (ctx, message: string) => {
editor.flashNotification(message); editor.flashNotification(message);
}, },
showRhs: (ctx, html: string) => { "editor.showRhs": (ctx, html: string) => {
editor.viewDispatch({ editor.viewDispatch({
type: "show-rhs", type: "show-rhs",
html: html, html: html,
}); });
}, },
insertAtPos: (ctx, text: string, pos: number) => { "editor.insertAtPos": (ctx, text: string, pos: number) => {
editor.editorView!.dispatch({ editor.editorView!.dispatch({
changes: { changes: {
insert: text, insert: text,
@ -66,7 +66,7 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
}, },
}); });
}, },
replaceRange: (ctx, from: number, to: number, text: string) => { "editor.replaceRange": (ctx, from: number, to: number, text: string) => {
editor.editorView!.dispatch({ editor.editorView!.dispatch({
changes: { changes: {
insert: text, insert: text,
@ -75,14 +75,14 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
}, },
}); });
}, },
moveCursor: (ctx, pos: number) => { "editor.moveCursor": (ctx, pos: number) => {
editor.editorView!.dispatch({ editor.editorView!.dispatch({
selection: { selection: {
anchor: pos, anchor: pos,
}, },
}); });
}, },
insertAtCursor: (ctx, text: string) => { "editor.insertAtCursor": (ctx, text: string) => {
let editorView = editor.editorView!; let editorView = editor.editorView!;
let from = editorView.state.selection.main.from; let from = editorView.state.selection.main.from;
editorView.dispatch({ editorView.dispatch({
@ -95,7 +95,7 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
}, },
}); });
}, },
getSyntaxNodeUnderCursor: (): SyntaxNode | undefined => { "editor.getSyntaxNodeUnderCursor": (): SyntaxNode | undefined => {
const editorState = editor.editorView!.state; const editorState = editor.editorView!.state;
let selection = editorState.selection.main; let selection = editorState.selection.main;
if (selection.empty) { if (selection.empty) {
@ -110,13 +110,13 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
} }
} }
}, },
getLineUnderCursor: (): string => { "editor.getLineUnderCursor": (): string => {
const editorState = editor.editorView!.state; const editorState = editor.editorView!.state;
let selection = editorState.selection.main; let selection = editorState.selection.main;
let line = editorState.doc.lineAt(selection.from); let line = editorState.doc.lineAt(selection.from);
return editorState.sliceDoc(line.from, line.to); return editorState.sliceDoc(line.from, line.to);
}, },
matchBefore: ( "editor.matchBefore": (
ctx, ctx,
regexp: string regexp: string
): { from: number; to: number; text: string } | null => { ): { from: number; to: number; text: string } | null => {
@ -135,7 +135,7 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
} }
return null; return null;
}, },
getSyntaxNodeAtPos: (ctx, pos: number): SyntaxNode | undefined => { "editor.getSyntaxNodeAtPos": (ctx, pos: number): SyntaxNode | undefined => {
const editorState = editor.editorView!.state; const editorState = editor.editorView!.state;
let node = syntaxTree(editorState).resolveInner(pos); let node = syntaxTree(editorState).resolveInner(pos);
if (node) { if (node) {
@ -147,10 +147,14 @@ export function editorSyscalls(editor: Editor): SysCallMapping {
}; };
} }
}, },
dispatch: (ctx, change: Transaction) => { "editor.dispatch": (ctx, change: Transaction) => {
editor.editorView!.dispatch(change); editor.editorView!.dispatch(change);
}, },
prompt: (ctx, message: string, defaultValue = ""): string | null => { "editor.prompt": (
ctx,
message: string,
defaultValue = ""
): string | null => {
return prompt(message, defaultValue); return prompt(message, defaultValue);
}, },
}; };

View File

@ -5,13 +5,13 @@ import {transportSyscalls} from "../../plugos/syscalls/transport";
export function indexerSyscalls(space: Space): SysCallMapping { export function indexerSyscalls(space: Space): SysCallMapping {
return transportSyscalls( return transportSyscalls(
[ [
"scanPrefixForPage", "index.scanPrefixForPage",
"scanPrefixGlobal", "index.scanPrefixGlobal",
"get", "index.get",
"set", "index.set",
"batchSet", "index.batchSet",
"delete", "index.delete",
], ],
(ctx, name, ...args) => space.remoteSyscall(ctx.plug, `index.${name}`, args) (ctx, name, ...args) => space.remoteSyscall(ctx.plug, name, args)
); );
} }

View File

@ -4,19 +4,23 @@ import {PageMeta} from "../../common/types";
export function spaceSyscalls(editor: Editor): SysCallMapping { export function spaceSyscalls(editor: Editor): SysCallMapping {
return { return {
listPages: async (): Promise<PageMeta[]> => { "space.listPages": async (): Promise<PageMeta[]> => {
return [...(await editor.space.listPages())]; return [...(await editor.space.listPages())];
}, },
readPage: async ( "space.readPage": async (
ctx, ctx,
name: string name: string
): Promise<{ text: string; meta: PageMeta }> => { ): Promise<{ text: string; meta: PageMeta }> => {
return await editor.space.readPage(name); return await editor.space.readPage(name);
}, },
writePage: async (ctx, name: string, text: string): Promise<PageMeta> => { "space.writePage": async (
ctx,
name: string,
text: string
): Promise<PageMeta> => {
return await editor.space.writePage(name, text); return await editor.space.writePage(name, text);
}, },
deletePage: async (ctx, name: string) => { "space.deletePage": async (ctx, name: string) => {
// If we're deleting the current page, navigate to the start page // If we're deleting the current page, navigate to the start page
if (editor.currentPage === name) { if (editor.currentPage === name) {
await editor.navigate("start"); await editor.navigate("start");

View File

@ -1,9 +1,13 @@
import { SysCallMapping } from "../../plugos/system"; import {SysCallMapping} from "../../plugos/system";
import { Space } from "../space"; import {Space} from "../space";
export function systemSyscalls(space: Space): SysCallMapping { export function systemSyscalls(space: Space): SysCallMapping {
return { return {
async invokeFunctionOnServer(ctx, name: string, ...args: any[]) { "system.invokeFunctionOnServer": async (
ctx,
name: string,
...args: any[]
) => {
if (!ctx.plug) { if (!ctx.plug) {
throw Error("No plug associated with context"); throw Error("No plug associated with context");
} }