@silverbulletmd/silverbullet 2.6.1 → 2.8.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/README.md +0 -3
- package/client/markdown_parser/constants.ts +1 -0
- package/client/plugos/hooks/command.ts +46 -16
- package/client/plugos/hooks/slash_command.ts +3 -1
- package/client/plugos/plug.ts +5 -2
- package/client/plugos/plug_compile.ts +52 -2
- package/client/plugos/sandboxes/worker_sandbox.ts +11 -1
- package/client/plugos/syscalls/client_code_widget.ts +3 -1
- package/client/plugos/syscalls/config.ts +9 -2
- package/client/plugos/syscalls/editor.ts +147 -0
- package/client/plugos/syscalls/index.ts +8 -1
- package/client/plugos/syscalls/jsonschema.ts +1 -19
- package/client/plugos/syscalls/system.ts +13 -0
- package/client/plugos/system.ts +14 -10
- package/client/plugos/types.ts +25 -0
- package/client/plugos/util.ts +20 -0
- package/client/space_lua/aggregates.ts +2 -2
- package/client/space_lua/query_collection.ts +4 -4
- package/client/space_lua/render_lua_markdown.ts +1 -1
- package/client/space_lua/runtime.ts +1 -1
- package/client/space_lua/{liq_null.ts → sliq_null.ts} +2 -2
- package/client/space_lua/stdlib/space_lua.ts +1 -1
- package/client/space_lua/stdlib.ts +1 -1
- package/dist/plug-compile.js +44 -2
- package/package.json +1 -1
- package/plug-api/constants.ts +20 -0
- package/plug-api/lib/ref.ts +9 -2
- package/plug-api/lib/shortcut.ts +16 -0
- package/plug-api/lib/tree.ts +35 -4
- package/plug-api/syscalls/config.ts +37 -0
- package/plug-api/syscalls/editor.ts +160 -0
- package/plug-api/syscalls/index.ts +30 -0
- package/plug-api/syscalls/system.ts +15 -0
- package/plug-api/types/client.ts +12 -0
- package/plug-api/types/manifest.ts +63 -0
- package/plugs/builtin_plugs.ts +1 -1
- package/client/plugos/sandboxes/web_worker_sandbox.ts +0 -17
package/README.md
CHANGED
|
@@ -27,8 +27,6 @@ SilverBullet's frontend is written in [TypeScript](https://www.typescriptlang.or
|
|
|
27
27
|
|
|
28
28
|
The server backend is written in Go.
|
|
29
29
|
|
|
30
|
-
If you're considering contributing changes, be aware of the [LLM use policy](https://silverbullet.md/LLM%20Use).
|
|
31
|
-
|
|
32
30
|
## Code structure
|
|
33
31
|
* `client/`: The SilverBullet client, implemented with TypeScript
|
|
34
32
|
* `server/`: The SilverBullet server, written in Go
|
|
@@ -107,7 +105,6 @@ docker build -t silverbullet .
|
|
|
107
105
|
```
|
|
108
106
|
|
|
109
107
|
To run:
|
|
110
|
-
|
|
111
108
|
```shell
|
|
112
109
|
docker run -p 3000:3000 -v <PATH-TO-YOUR-SPACE>:/space silverbullet
|
|
113
110
|
```
|
|
@@ -12,3 +12,4 @@ export const frontmatterWikiLinkRegex =
|
|
|
12
12
|
/(?<leadingTrivia>!?\[\[)(?<stringRef>.*?)(?:\|(?<alias>.*?))?(?<trailingTrivia>\]\])/g;
|
|
13
13
|
export const frontmatterMailtoRegex = /(mailto:[^@\s]+@[^@\s"']+)/gi;
|
|
14
14
|
export const pWikiLinkRegex = new RegExp(`^${wikiLinkRegex.source}`); // Modified regex used only in parser
|
|
15
|
+
export const anchorRegex = /\$[A-Za-z_][A-Za-z0-9_/:-]*/;
|
|
@@ -14,6 +14,8 @@ export class CommandHook
|
|
|
14
14
|
this.buildAllCommandsAndEmit();
|
|
15
15
|
}, 200);
|
|
16
16
|
|
|
17
|
+
private registeredCommands = new Map<string, Command>();
|
|
18
|
+
|
|
17
19
|
constructor(
|
|
18
20
|
private readOnly: boolean,
|
|
19
21
|
private additionalCommands: Map<string, Command>,
|
|
@@ -21,11 +23,51 @@ export class CommandHook
|
|
|
21
23
|
super();
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Register a command directly with the hook. Intended for client-side code
|
|
28
|
+
* that wants to define commands without going through a plug manifest.
|
|
29
|
+
* Overwrites any previously-registered command with the same name.
|
|
30
|
+
*/
|
|
31
|
+
registerCommand(command: Command): void {
|
|
32
|
+
this.registeredCommands.set(command.name, command);
|
|
33
|
+
this.throttledBuildAllCommandsAndEmit();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private mergeCommand(
|
|
37
|
+
commands: Map<string, Command>,
|
|
38
|
+
name: string,
|
|
39
|
+
cmd: Command,
|
|
40
|
+
) {
|
|
41
|
+
if (commands.has(name)) {
|
|
42
|
+
// Existing command, let's do some inline patching
|
|
43
|
+
const existingCommand = commands.get(name)!;
|
|
44
|
+
const command: Command = {
|
|
45
|
+
...existingCommand,
|
|
46
|
+
...cmd,
|
|
47
|
+
};
|
|
48
|
+
if (cmd.run) {
|
|
49
|
+
command.run = cmd.run;
|
|
50
|
+
}
|
|
51
|
+
commands.set(name, command);
|
|
52
|
+
} else {
|
|
53
|
+
commands.set(name, cmd);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
24
57
|
/**
|
|
25
58
|
* Build the command map
|
|
26
59
|
*/
|
|
27
60
|
buildAllCommands(): Map<string, Command> {
|
|
28
61
|
const commands = new Map<string, Command>();
|
|
62
|
+
// Start with directly-registered commands (built-in client commands).
|
|
63
|
+
// These form the base layer and can be overridden by plug commands or
|
|
64
|
+
// script commands further down.
|
|
65
|
+
for (const [name, cmd] of this.registeredCommands) {
|
|
66
|
+
if (cmd.requireMode === "rw" && this.readOnly) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
commands.set(name, cmd);
|
|
70
|
+
}
|
|
29
71
|
// Add commands from plugs
|
|
30
72
|
if (!this.system) {
|
|
31
73
|
// Not initialized yet
|
|
@@ -43,7 +85,7 @@ export class CommandHook
|
|
|
43
85
|
// Bit hacky, but don't expose commands that require write mode in read-only mode
|
|
44
86
|
continue;
|
|
45
87
|
}
|
|
46
|
-
|
|
88
|
+
this.mergeCommand(commands, cmd.name, {
|
|
47
89
|
...cmd,
|
|
48
90
|
run: (args?: string[]) => {
|
|
49
91
|
return plug.invoke(name, [cmd, ...(args ?? [])]);
|
|
@@ -51,22 +93,10 @@ export class CommandHook
|
|
|
51
93
|
});
|
|
52
94
|
}
|
|
53
95
|
}
|
|
96
|
+
// Script commands come last — they always win over built-ins and plugs,
|
|
97
|
+
// so users can rebind keys / replace run bodies from Lua.
|
|
54
98
|
for (const [name, cmd] of this.additionalCommands) {
|
|
55
|
-
|
|
56
|
-
// Existing command, let's do some inline patching
|
|
57
|
-
const existingCommand = commands.get(name)!;
|
|
58
|
-
const command: Command = {
|
|
59
|
-
...existingCommand,
|
|
60
|
-
...cmd,
|
|
61
|
-
};
|
|
62
|
-
if (cmd.run) {
|
|
63
|
-
command.run = cmd.run;
|
|
64
|
-
}
|
|
65
|
-
commands.set(name, command);
|
|
66
|
-
} else {
|
|
67
|
-
// New command, let's just set
|
|
68
|
-
commands.set(name, cmd);
|
|
69
|
-
}
|
|
99
|
+
this.mergeCommand(commands, name, cmd);
|
|
70
100
|
}
|
|
71
101
|
return commands;
|
|
72
102
|
}
|
|
@@ -66,7 +66,9 @@ export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
|
|
66
66
|
const options: Completion[] = [];
|
|
67
67
|
|
|
68
68
|
// No slash commands in comment blocks (queries and such) or links
|
|
69
|
-
|
|
69
|
+
// Pass -1 so we resolve into the node ending at the cursor: when typing,
|
|
70
|
+
// the user is referring to preceding text, not what comes after.
|
|
71
|
+
const currentNode = syntaxTree(ctx.state).resolveInner(ctx.pos, -1);
|
|
70
72
|
if (
|
|
71
73
|
currentNode.type.name === "CommentBlock" ||
|
|
72
74
|
currentNode.type.name === "Link"
|
package/client/plugos/plug.ts
CHANGED
|
@@ -11,6 +11,8 @@ export class Plug<HookT> {
|
|
|
11
11
|
|
|
12
12
|
public manifest!: Manifest<HookT>;
|
|
13
13
|
public assets?: AssetBundle;
|
|
14
|
+
// File path the plug was loaded from; also used as the manifest cache key.
|
|
15
|
+
public path!: string;
|
|
14
16
|
|
|
15
17
|
constructor(
|
|
16
18
|
readonly system: System<HookT>,
|
|
@@ -22,16 +24,17 @@ export class Plug<HookT> {
|
|
|
22
24
|
|
|
23
25
|
static async createLazily<HookT>(
|
|
24
26
|
system: System<HookT>,
|
|
25
|
-
|
|
27
|
+
path: string,
|
|
26
28
|
cacheHash: number,
|
|
27
29
|
sandboxFactory: SandboxFactory<HookT>,
|
|
28
30
|
): Promise<Plug<HookT>> {
|
|
29
31
|
const plug = new Plug(system, sandboxFactory);
|
|
32
|
+
plug.path = path;
|
|
30
33
|
|
|
31
34
|
// Retrieve the manifest, which may either come from a cache or be loaded from the worker
|
|
32
35
|
plug.manifest = await system.options.manifestCache!.getManifest(
|
|
33
36
|
plug,
|
|
34
|
-
|
|
37
|
+
path,
|
|
35
38
|
cacheHash,
|
|
36
39
|
);
|
|
37
40
|
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
copyFile,
|
|
4
|
+
mkdir,
|
|
5
|
+
mkdtemp,
|
|
6
|
+
readFile,
|
|
7
|
+
rm,
|
|
8
|
+
writeFile,
|
|
9
|
+
} from "node:fs/promises";
|
|
3
10
|
import { tmpdir } from "node:os";
|
|
4
11
|
import * as YAML from "js-yaml";
|
|
5
12
|
|
|
6
13
|
import * as esbuild from "esbuild";
|
|
14
|
+
import * as sass from "sass";
|
|
7
15
|
import { bundleAssets } from "../asset_bundle/builder.ts";
|
|
8
|
-
import type { Manifest } from "./types.ts";
|
|
16
|
+
import type { BuildStep, Manifest } from "./types.ts";
|
|
9
17
|
|
|
10
18
|
// When bundled by build_plug_compile.ts, this is replaced with the pre-bundled
|
|
11
19
|
// worker runtime JS. When running from source, it's undefined and esbuild
|
|
@@ -53,6 +61,15 @@ export async function compileManifest(
|
|
|
53
61
|
throw new Error(`Missing 'name' in ${manifestPath}`);
|
|
54
62
|
}
|
|
55
63
|
|
|
64
|
+
// Build steps (run before asset bundling so produced files can be picked
|
|
65
|
+
// up by the `assets` glob).
|
|
66
|
+
if (manifest.build) {
|
|
67
|
+
await Promise.all(
|
|
68
|
+
manifest.build.map((step) => runBuildStep(step, rootPath, options)),
|
|
69
|
+
);
|
|
70
|
+
delete manifest.build;
|
|
71
|
+
}
|
|
72
|
+
|
|
56
73
|
// Assets
|
|
57
74
|
const assetsBundle = await bundleAssets(
|
|
58
75
|
path.resolve(rootPath),
|
|
@@ -177,6 +194,39 @@ export async function compileManifests(
|
|
|
177
194
|
await buildAll();
|
|
178
195
|
}
|
|
179
196
|
|
|
197
|
+
async function runBuildStep(
|
|
198
|
+
step: BuildStep,
|
|
199
|
+
rootPath: string,
|
|
200
|
+
options: CompileOptions,
|
|
201
|
+
) {
|
|
202
|
+
const type = step.type ?? "esbuild";
|
|
203
|
+
const inFile = path.resolve(rootPath, step.in);
|
|
204
|
+
const outFile = path.resolve(rootPath, step.out);
|
|
205
|
+
await mkdir(path.dirname(outFile), { recursive: true });
|
|
206
|
+
if (type === "esbuild") {
|
|
207
|
+
await esbuild.build({
|
|
208
|
+
entryPoints: [inFile],
|
|
209
|
+
bundle: true,
|
|
210
|
+
format: "iife",
|
|
211
|
+
platform: "browser",
|
|
212
|
+
minify: !options.debug,
|
|
213
|
+
outfile: outFile,
|
|
214
|
+
treeShaking: true,
|
|
215
|
+
});
|
|
216
|
+
} else if (type === "sass") {
|
|
217
|
+
const scss = await readFile(inFile, "utf-8");
|
|
218
|
+
const result = sass.compileString(scss, {
|
|
219
|
+
loadPaths: [path.dirname(inFile)],
|
|
220
|
+
style: options.debug ? "expanded" : "compressed",
|
|
221
|
+
});
|
|
222
|
+
await writeFile(outFile, result.css, "utf-8");
|
|
223
|
+
} else if (type === "copy") {
|
|
224
|
+
await copyFile(inFile, outFile);
|
|
225
|
+
} else {
|
|
226
|
+
throw new Error(`Unsupported build step type: ${type}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
180
230
|
export function patchBundledJS(code: string): string {
|
|
181
231
|
// One bundled dependency has a lookbehind regex that WebKit can't parse; replace it with a no-op
|
|
182
232
|
return code.replaceAll("/(?<=\\n)/", "/()/");
|
|
@@ -2,8 +2,9 @@ import type { Manifest } from "../types.ts";
|
|
|
2
2
|
import type { ControllerMessage, WorkerMessage } from "../protocol.ts";
|
|
3
3
|
import type { Plug } from "../plug.ts";
|
|
4
4
|
import { AssetBundle, type AssetJson } from "../../asset_bundle/bundle.ts";
|
|
5
|
-
import type { Sandbox } from "./sandbox.ts";
|
|
5
|
+
import type { Sandbox, SandboxFactory } from "./sandbox.ts";
|
|
6
6
|
import { race, timeout } from "@silverbulletmd/silverbullet/lib/async";
|
|
7
|
+
import { fsEndpoint } from "../../spaces/constants.ts";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Represents a "safe" execution environment for plug code
|
|
@@ -24,6 +25,15 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
|
|
|
24
25
|
private workerOptions = {},
|
|
25
26
|
) {}
|
|
26
27
|
|
|
28
|
+
/** Factory producing a WorkerSandbox for a plug file at a local space path. */
|
|
29
|
+
static forPath<HookT>(path: string): SandboxFactory<HookT> {
|
|
30
|
+
return (plug) =>
|
|
31
|
+
new WorkerSandbox(
|
|
32
|
+
plug,
|
|
33
|
+
new URL(path, `${document.baseURI.slice(0, -1) + fsEndpoint}/`),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
/**
|
|
28
38
|
* Should only invoked lazily (either by invoke, or by a ManifestCache to load the manifest)
|
|
29
39
|
*/
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { SysCallMapping } from "../../plugos/system.ts";
|
|
2
|
+
import type { Client } from "../../client.ts";
|
|
2
3
|
import { reloadAllWidgets } from "../../codemirror/code_widget.ts";
|
|
3
4
|
import { broadcastReload } from "../../components/widget_sandbox_iframe.ts";
|
|
4
5
|
|
|
5
|
-
export function clientCodeWidgetSyscalls(): SysCallMapping {
|
|
6
|
+
export function clientCodeWidgetSyscalls(client: Client): SysCallMapping {
|
|
6
7
|
return {
|
|
7
8
|
"codeWidget.refreshAll": () => {
|
|
9
|
+
client.widgetCache.clearPrewarm();
|
|
8
10
|
broadcastReload();
|
|
9
11
|
return reloadAllWidgets();
|
|
10
12
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SysCallMapping } from "../system.ts";
|
|
2
2
|
import type { Config } from "../../config.ts";
|
|
3
3
|
import { LuaStackFrame, luaValueToJS } from "../../space_lua/runtime.ts";
|
|
4
|
+
import { stripFunctions } from "../util.ts";
|
|
4
5
|
|
|
5
6
|
export function configSyscalls(config: Config): SysCallMapping {
|
|
6
7
|
return {
|
|
@@ -32,11 +33,17 @@ export function configSyscalls(config: Config): SysCallMapping {
|
|
|
32
33
|
"config.define": (_ctx, key: string, schema: any) => {
|
|
33
34
|
config.define(key, schema);
|
|
34
35
|
},
|
|
36
|
+
"config.defineCategory": (_ctx, definition: any) => {
|
|
37
|
+
config.defineCategory(definition);
|
|
38
|
+
},
|
|
35
39
|
"config.getValues": () => {
|
|
36
|
-
return config.values;
|
|
40
|
+
return stripFunctions(config.values);
|
|
37
41
|
},
|
|
38
42
|
"config.getSchemas": () => {
|
|
39
|
-
return config.schemas;
|
|
43
|
+
return stripFunctions(config.schemas);
|
|
44
|
+
},
|
|
45
|
+
"config.getCategories": () => {
|
|
46
|
+
return { ...config.categories };
|
|
40
47
|
},
|
|
41
48
|
};
|
|
42
49
|
}
|
|
@@ -7,15 +7,59 @@ import {
|
|
|
7
7
|
unfoldCode,
|
|
8
8
|
} from "@codemirror/language";
|
|
9
9
|
import {
|
|
10
|
+
cursorCharLeft,
|
|
11
|
+
cursorCharRight,
|
|
12
|
+
cursorDocEnd,
|
|
13
|
+
cursorDocStart,
|
|
14
|
+
cursorGroupLeft,
|
|
15
|
+
cursorGroupRight,
|
|
16
|
+
cursorLineBoundaryLeft,
|
|
17
|
+
cursorLineBoundaryRight,
|
|
18
|
+
cursorLineDown,
|
|
19
|
+
cursorLineEnd,
|
|
20
|
+
cursorLineStart,
|
|
21
|
+
cursorLineUp,
|
|
22
|
+
cursorPageDown,
|
|
23
|
+
cursorPageUp,
|
|
24
|
+
deleteCharBackward,
|
|
25
|
+
deleteCharForward,
|
|
26
|
+
deleteGroupBackward,
|
|
27
|
+
deleteGroupForward,
|
|
10
28
|
deleteLine,
|
|
29
|
+
deleteLineBoundaryBackward,
|
|
30
|
+
deleteLineBoundaryForward,
|
|
31
|
+
indentLess,
|
|
32
|
+
indentMore,
|
|
11
33
|
insertNewline,
|
|
12
34
|
insertNewlineAndIndent,
|
|
13
35
|
moveLineDown,
|
|
14
36
|
moveLineUp,
|
|
15
37
|
redo,
|
|
38
|
+
selectAll,
|
|
39
|
+
selectCharLeft,
|
|
40
|
+
selectCharRight,
|
|
41
|
+
selectDocEnd,
|
|
42
|
+
selectDocStart,
|
|
43
|
+
selectGroupLeft,
|
|
44
|
+
selectGroupRight,
|
|
45
|
+
selectLineBoundaryLeft,
|
|
46
|
+
selectLineBoundaryRight,
|
|
47
|
+
selectLineDown,
|
|
48
|
+
selectLineEnd,
|
|
49
|
+
selectLineStart,
|
|
50
|
+
selectLineUp,
|
|
51
|
+
selectPageDown,
|
|
52
|
+
selectPageUp,
|
|
16
53
|
toggleComment,
|
|
54
|
+
transposeChars,
|
|
17
55
|
undo,
|
|
18
56
|
} from "@codemirror/commands";
|
|
57
|
+
import {
|
|
58
|
+
acceptCompletion,
|
|
59
|
+
closeCompletion,
|
|
60
|
+
moveCompletionSelection,
|
|
61
|
+
startCompletion,
|
|
62
|
+
} from "@codemirror/autocomplete";
|
|
19
63
|
import type { Transaction } from "@codemirror/state";
|
|
20
64
|
import { EditorView } from "@codemirror/view";
|
|
21
65
|
import { getVimModule } from "../../vim_loader.ts";
|
|
@@ -27,6 +71,8 @@ import type {
|
|
|
27
71
|
UploadFile,
|
|
28
72
|
} from "@silverbulletmd/silverbullet/type/client";
|
|
29
73
|
import { openSearchPanel } from "@codemirror/search";
|
|
74
|
+
import { forceLinting } from "@codemirror/lint";
|
|
75
|
+
import { refreshLintEffect } from "../../codemirror/lint.ts";
|
|
30
76
|
import {
|
|
31
77
|
isValidPath,
|
|
32
78
|
parseToRef,
|
|
@@ -205,6 +251,15 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
205
251
|
"editor.reloadPage": async () => {
|
|
206
252
|
await client.reloadEditor();
|
|
207
253
|
},
|
|
254
|
+
"editor.forceLint": () => {
|
|
255
|
+
// `forceLinting` alone is a no-op on unchanged content after the
|
|
256
|
+
// linter's internal "scheduled" flag has been cleared; the effect
|
|
257
|
+
// re-arms it via `needsRefresh`.
|
|
258
|
+
client.editorView.dispatch({
|
|
259
|
+
effects: refreshLintEffect.of(null),
|
|
260
|
+
});
|
|
261
|
+
forceLinting(client.editorView);
|
|
262
|
+
},
|
|
208
263
|
"editor.reloadUI": () => {
|
|
209
264
|
location.reload();
|
|
210
265
|
},
|
|
@@ -324,6 +379,9 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
324
379
|
client.editorView.dispatch({});
|
|
325
380
|
});
|
|
326
381
|
},
|
|
382
|
+
"editor.focus": () => {
|
|
383
|
+
client.focus();
|
|
384
|
+
},
|
|
327
385
|
"editor.hidePanel": (_ctx, id: string) => {
|
|
328
386
|
client.ui.viewDispatch({
|
|
329
387
|
type: "hide-panel",
|
|
@@ -600,6 +658,24 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
600
658
|
"editor.deleteLine": () => {
|
|
601
659
|
deleteLine(client.editorView);
|
|
602
660
|
},
|
|
661
|
+
"editor.selectAll": () => {
|
|
662
|
+
return selectAll({
|
|
663
|
+
state: client.editorView.state,
|
|
664
|
+
dispatch: client.editorView.dispatch,
|
|
665
|
+
});
|
|
666
|
+
},
|
|
667
|
+
"editor.indentMore": () => {
|
|
668
|
+
return indentMore({
|
|
669
|
+
state: client.editorView.state,
|
|
670
|
+
dispatch: client.editorView.dispatch,
|
|
671
|
+
});
|
|
672
|
+
},
|
|
673
|
+
"editor.indentLess": () => {
|
|
674
|
+
return indentLess({
|
|
675
|
+
state: client.editorView.state,
|
|
676
|
+
dispatch: client.editorView.dispatch,
|
|
677
|
+
});
|
|
678
|
+
},
|
|
603
679
|
"editor.toggleComment": () => {
|
|
604
680
|
return toggleComment({
|
|
605
681
|
state: client.editorView.state,
|
|
@@ -640,6 +716,77 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
640
716
|
"editor.redo": () => {
|
|
641
717
|
return redo(client.editorView);
|
|
642
718
|
},
|
|
719
|
+
// Cursor motion syscalls
|
|
720
|
+
"editor.cursorCharLeft": () => cursorCharLeft(client.editorView),
|
|
721
|
+
"editor.cursorCharRight": () => cursorCharRight(client.editorView),
|
|
722
|
+
"editor.cursorGroupLeft": () => cursorGroupLeft(client.editorView),
|
|
723
|
+
"editor.cursorGroupRight": () => cursorGroupRight(client.editorView),
|
|
724
|
+
"editor.cursorLineBoundaryLeft": () =>
|
|
725
|
+
cursorLineBoundaryLeft(client.editorView),
|
|
726
|
+
"editor.cursorLineBoundaryRight": () =>
|
|
727
|
+
cursorLineBoundaryRight(client.editorView),
|
|
728
|
+
"editor.cursorLineStart": () => cursorLineStart(client.editorView),
|
|
729
|
+
"editor.cursorLineEnd": () => cursorLineEnd(client.editorView),
|
|
730
|
+
"editor.cursorDocStart": () => cursorDocStart(client.editorView),
|
|
731
|
+
"editor.cursorDocEnd": () => cursorDocEnd(client.editorView),
|
|
732
|
+
// Cursor motions that also navigate the completion popup if it's open
|
|
733
|
+
"editor.cursorLineUp": () => {
|
|
734
|
+
const view = client.editorView;
|
|
735
|
+
if (moveCompletionSelection(false)(view)) return true;
|
|
736
|
+
return cursorLineUp(view);
|
|
737
|
+
},
|
|
738
|
+
"editor.cursorLineDown": () => {
|
|
739
|
+
const view = client.editorView;
|
|
740
|
+
if (moveCompletionSelection(true)(view)) return true;
|
|
741
|
+
return cursorLineDown(view);
|
|
742
|
+
},
|
|
743
|
+
"editor.cursorPageUp": () => {
|
|
744
|
+
const view = client.editorView;
|
|
745
|
+
if (moveCompletionSelection(false, "page")(view)) return true;
|
|
746
|
+
return cursorPageUp(view);
|
|
747
|
+
},
|
|
748
|
+
"editor.cursorPageDown": () => {
|
|
749
|
+
const view = client.editorView;
|
|
750
|
+
if (moveCompletionSelection(true, "page")(view)) return true;
|
|
751
|
+
return cursorPageDown(view);
|
|
752
|
+
},
|
|
753
|
+
// Selection-extending motions
|
|
754
|
+
"editor.selectCharLeft": () => selectCharLeft(client.editorView),
|
|
755
|
+
"editor.selectCharRight": () => selectCharRight(client.editorView),
|
|
756
|
+
"editor.selectGroupLeft": () => selectGroupLeft(client.editorView),
|
|
757
|
+
"editor.selectGroupRight": () => selectGroupRight(client.editorView),
|
|
758
|
+
"editor.selectLineBoundaryLeft": () =>
|
|
759
|
+
selectLineBoundaryLeft(client.editorView),
|
|
760
|
+
"editor.selectLineBoundaryRight": () =>
|
|
761
|
+
selectLineBoundaryRight(client.editorView),
|
|
762
|
+
"editor.selectLineStart": () => selectLineStart(client.editorView),
|
|
763
|
+
"editor.selectLineEnd": () => selectLineEnd(client.editorView),
|
|
764
|
+
"editor.selectDocStart": () => selectDocStart(client.editorView),
|
|
765
|
+
"editor.selectDocEnd": () => selectDocEnd(client.editorView),
|
|
766
|
+
"editor.selectLineUp": () => selectLineUp(client.editorView),
|
|
767
|
+
"editor.selectLineDown": () => selectLineDown(client.editorView),
|
|
768
|
+
"editor.selectPageUp": () => selectPageUp(client.editorView),
|
|
769
|
+
"editor.selectPageDown": () => selectPageDown(client.editorView),
|
|
770
|
+
// Delete
|
|
771
|
+
"editor.deleteCharBackward": () => deleteCharBackward(client.editorView),
|
|
772
|
+
"editor.deleteCharForward": () => deleteCharForward(client.editorView),
|
|
773
|
+
"editor.deleteGroupBackward": () => deleteGroupBackward(client.editorView),
|
|
774
|
+
"editor.deleteGroupForward": () => deleteGroupForward(client.editorView),
|
|
775
|
+
"editor.deleteLineBoundaryBackward": () =>
|
|
776
|
+
deleteLineBoundaryBackward(client.editorView),
|
|
777
|
+
"editor.deleteLineBoundaryForward": () =>
|
|
778
|
+
deleteLineBoundaryForward(client.editorView),
|
|
779
|
+
"editor.transposeChars": () => transposeChars(client.editorView),
|
|
780
|
+
// Enter: accept completion if popup is open, else newline-and-indent
|
|
781
|
+
"editor.insertNewline": () => {
|
|
782
|
+
const view = client.editorView;
|
|
783
|
+
if (acceptCompletion(view)) return true;
|
|
784
|
+
return insertNewlineAndIndent(view);
|
|
785
|
+
},
|
|
786
|
+
// Completion popup control
|
|
787
|
+
"editor.acceptCompletion": () => acceptCompletion(client.editorView),
|
|
788
|
+
"editor.startCompletion": () => startCompletion(client.editorView),
|
|
789
|
+
"editor.closeCompletion": () => closeCompletion(client.editorView),
|
|
643
790
|
"editor.openSearchPanel": () => {
|
|
644
791
|
openSearchPanel(client.editorView);
|
|
645
792
|
},
|
|
@@ -61,13 +61,20 @@ export function indexSyscalls(
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
|
+
"index.previewProcessedObjects": (
|
|
65
|
+
_ctx,
|
|
66
|
+
page: string,
|
|
67
|
+
objects: ObjectValue[],
|
|
68
|
+
): Promise<{ tag: string; object: ObjectValue }[]> => {
|
|
69
|
+
return objectIndex.previewProcessedObjects(page, objects);
|
|
70
|
+
},
|
|
64
71
|
"index.getObjectByRef": (
|
|
65
72
|
_ctx,
|
|
66
73
|
page: string,
|
|
67
74
|
tag: string,
|
|
68
75
|
ref: string,
|
|
69
76
|
): Promise<ObjectValue | undefined> => {
|
|
70
|
-
return objectIndex.getObjectByRef(
|
|
77
|
+
return objectIndex.getObjectByRef(page, tag, ref);
|
|
71
78
|
},
|
|
72
79
|
"index.queryLuaObjects": (
|
|
73
80
|
_ctx,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SysCallMapping } from "../system.ts";
|
|
2
2
|
import { type OutputUnit, Validator, format } from "@cfworker/json-schema";
|
|
3
|
+
import { stripFunctions } from "../util.ts";
|
|
3
4
|
|
|
4
5
|
// Register custom formats
|
|
5
6
|
format.email = (data: string) => data.includes("@");
|
|
@@ -8,25 +9,6 @@ format["page-ref"] = (data: string) =>
|
|
|
8
9
|
|
|
9
10
|
const schemaCache = new Map<string, Validator>();
|
|
10
11
|
|
|
11
|
-
/**
|
|
12
|
-
* Deep-clone a value, replacing any functions with null.
|
|
13
|
-
* JSON schema can't validate functions, so we strip them before validation.
|
|
14
|
-
*/
|
|
15
|
-
function stripFunctions(value: any): any {
|
|
16
|
-
if (typeof value === "function") return null;
|
|
17
|
-
if (value === null || value === undefined || typeof value !== "object") {
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
|
-
if (Array.isArray(value)) {
|
|
21
|
-
return value.map(stripFunctions);
|
|
22
|
-
}
|
|
23
|
-
const result: Record<string, any> = {};
|
|
24
|
-
for (const key of Object.keys(value)) {
|
|
25
|
-
result[key] = stripFunctions(value[key]);
|
|
26
|
-
}
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
12
|
function formatErrors(errors: OutputUnit[]): string {
|
|
31
13
|
// Filter out "properties" wrapper errors, keep only the specific leaf errors
|
|
32
14
|
const leafErrors = errors.filter((e) => e.keyword !== "properties");
|
|
@@ -60,6 +60,11 @@ export function systemSyscalls(
|
|
|
60
60
|
mac: def.mac,
|
|
61
61
|
hide: def.hide,
|
|
62
62
|
requireMode: def.requireMode,
|
|
63
|
+
requireEditor: def.requireEditor,
|
|
64
|
+
menu: def.menu,
|
|
65
|
+
menuMac: def.menuMac,
|
|
66
|
+
menuWindows: def.menuWindows,
|
|
67
|
+
menuLinux: def.menuLinux,
|
|
63
68
|
};
|
|
64
69
|
}
|
|
65
70
|
return allCommands;
|
|
@@ -82,6 +87,14 @@ export function systemSyscalls(
|
|
|
82
87
|
}
|
|
83
88
|
return client.loadPlugs();
|
|
84
89
|
},
|
|
90
|
+
"system.loadPlug": async (_ctx, path: string) => {
|
|
91
|
+
const meta = await client.space.spacePrimitives.getFileMeta(path);
|
|
92
|
+
await client.clientSystem.loadPlugFromPath(path, meta.lastModified);
|
|
93
|
+
await client.dispatchAppEvent("plugs:loaded");
|
|
94
|
+
},
|
|
95
|
+
"system.unloadPlug": (_ctx, path: string) => {
|
|
96
|
+
return client.clientSystem.system.unloadByPath(path);
|
|
97
|
+
},
|
|
85
98
|
"system.reloadConfig": (): Record<string, any> => {
|
|
86
99
|
console.warn("system.reloadConfig is deprecated, it's now a no-op");
|
|
87
100
|
return client.config.values;
|
package/client/plugos/system.ts
CHANGED
|
@@ -133,26 +133,20 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
/**
|
|
136
|
-
* @param
|
|
136
|
+
* @param path The plug file's space path; also used as the manifest cache key.
|
|
137
137
|
* @param cacheHash Used to determine if the manifest is up to date. Should be `lastModified`
|
|
138
138
|
*/
|
|
139
139
|
async loadPlug(
|
|
140
140
|
sandboxFactory: SandboxFactory<HookT>,
|
|
141
|
-
|
|
141
|
+
path: string,
|
|
142
142
|
cacheHash: number = -1,
|
|
143
143
|
): Promise<Plug<HookT>> {
|
|
144
|
-
const plug = await Plug.createLazily(
|
|
145
|
-
this,
|
|
146
|
-
cacheKey,
|
|
147
|
-
cacheHash,
|
|
148
|
-
sandboxFactory,
|
|
149
|
-
);
|
|
144
|
+
const plug = await Plug.createLazily(this, path, cacheHash, sandboxFactory);
|
|
150
145
|
|
|
151
146
|
const manifest = plug.manifest;
|
|
152
147
|
|
|
153
|
-
// This depends on cacheKey being the file path
|
|
154
148
|
if (
|
|
155
|
-
!builtinPlugPaths.includes(
|
|
149
|
+
!builtinPlugPaths.includes(path) &&
|
|
156
150
|
builtinPlugNames.includes(manifest.name)
|
|
157
151
|
) {
|
|
158
152
|
plug.stop();
|
|
@@ -177,6 +171,16 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|
|
177
171
|
return plug;
|
|
178
172
|
}
|
|
179
173
|
|
|
174
|
+
unloadByPath(path: string): boolean {
|
|
175
|
+
for (const [name, plug] of this.plugs) {
|
|
176
|
+
if (plug.path === path) {
|
|
177
|
+
this.unload(name);
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
180
184
|
unload(name: string) {
|
|
181
185
|
const plug = this.plugs.get(name);
|
|
182
186
|
if (!plug) {
|
package/client/plugos/types.ts
CHANGED
|
@@ -34,6 +34,31 @@ export interface Manifest<HookT> {
|
|
|
34
34
|
* A map of configuration options for the plug (to be merged with the system configuration).
|
|
35
35
|
*/
|
|
36
36
|
config?: any;
|
|
37
|
+
|
|
38
|
+
/** Build steps run before asset bundling. Each step transforms an `in` file
|
|
39
|
+
* into an `out` file, which is then picked up by the `assets` glob.
|
|
40
|
+
*
|
|
41
|
+
* Currently only `type: "esbuild"` is supported (the default when omitted),
|
|
42
|
+
* which runs esbuild in bundle mode on `in` and writes the result to `out`.
|
|
43
|
+
*/
|
|
44
|
+
build?: BuildStep[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** A pre-asset-bundling transformation, e.g. bundling a TypeScript entry point
|
|
48
|
+
* into a single JS file that is then loaded from plug assets.
|
|
49
|
+
*
|
|
50
|
+
* Supported types:
|
|
51
|
+
* - `esbuild` (default): bundle `in` into `out` as a minified browser IIFE.
|
|
52
|
+
* - `sass`: compile an SCSS/SASS file at `in` to CSS at `out`.
|
|
53
|
+
* - `copy`: copy `in` verbatim to `out`.
|
|
54
|
+
*/
|
|
55
|
+
export interface BuildStep {
|
|
56
|
+
/** Transformation type. Defaults to "esbuild" when omitted. */
|
|
57
|
+
type?: "esbuild" | "sass" | "copy";
|
|
58
|
+
/** Input path relative to the manifest. */
|
|
59
|
+
in: string;
|
|
60
|
+
/** Output path relative to the manifest. */
|
|
61
|
+
out: string;
|
|
37
62
|
}
|
|
38
63
|
|
|
39
64
|
/** Associates hooks with a function. This is the generic base structure, that identifies the function. Hooks are defined by the type parameter. */
|