@silverbulletmd/silverbullet 2.10.0 → 2.11.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 +4 -4
- package/client/asset_bundle/bundle.ts +0 -1
- package/client/config.ts +343 -0
- package/client/markdown_parser/constants.ts +3 -1
- package/client/plugos/hooks/command.ts +0 -5
- package/client/plugos/hooks/event.ts +0 -6
- package/client/plugos/hooks/mq.ts +0 -3
- package/client/plugos/hooks/slash_command.ts +1 -7
- package/client/plugos/hooks/syscall.ts +0 -4
- package/client/plugos/manifest_cache.ts +0 -32
- package/client/plugos/plug.ts +0 -3
- package/client/plugos/plug_compile.ts +3 -13
- package/client/plugos/sandboxes/worker_sandbox.ts +19 -9
- package/client/plugos/syscalls/editor.ts +128 -53
- package/client/plugos/syscalls/event.ts +0 -2
- package/client/plugos/syscalls/fetch.ts +0 -4
- package/client/plugos/syscalls/icon.ts +46 -0
- package/client/plugos/syscalls/index.ts +33 -13
- package/client/plugos/syscalls/jsonschema.ts +0 -1
- package/client/plugos/syscalls/mq.ts +0 -1
- package/client/plugos/syscalls/navigator.ts +143 -0
- package/client/plugos/syscalls/search.ts +44 -0
- package/client/plugos/syscalls/shell.ts +0 -1
- package/client/plugos/syscalls/space.ts +250 -7
- package/client/plugos/syscalls/sync.ts +56 -4
- package/client/plugos/syscalls/system.ts +38 -0
- package/client/plugos/system.ts +7 -5
- package/client/plugos/worker_runtime.ts +0 -2
- package/client/space_lua/aggregates.ts +0 -16
- package/client/space_lua/ast.ts +0 -5
- package/client/space_lua/ast_narrow.ts +0 -5
- package/client/space_lua/budget.ts +113 -0
- package/client/space_lua/budget_ui.ts +49 -0
- package/client/space_lua/eval.ts +113 -88
- package/client/space_lua/labels.ts +0 -4
- package/client/space_lua/numeric.ts +0 -5
- package/client/space_lua/parse.ts +0 -17
- package/client/space_lua/quarantine.ts +127 -0
- package/client/space_lua/query_collection.ts +3 -47
- package/client/space_lua/query_env.ts +0 -2
- package/client/space_lua/render_lua_markdown.ts +2 -7
- package/client/space_lua/render_widget.ts +18 -1
- package/client/space_lua/runtime.ts +50 -70
- package/client/space_lua/stdlib/format.ts +0 -12
- package/client/space_lua/stdlib/js.ts +0 -2
- package/client/space_lua/stdlib/load.ts +0 -1
- package/client/space_lua/stdlib/math.ts +0 -7
- package/client/space_lua/stdlib/net.ts +0 -4
- package/client/space_lua/stdlib/os.ts +0 -10
- package/client/space_lua/stdlib/pattern.ts +10 -15
- package/client/space_lua/stdlib/space_lua.ts +1 -2
- package/client/space_lua/stdlib/string_pack.ts +0 -3
- package/client/space_lua/stdlib/table.ts +0 -5
- package/client/space_lua/stdlib.ts +18 -31
- package/client/space_lua/tonumber.ts +0 -9
- package/dist/plug-compile.js +2 -3
- package/package.json +13 -3
- package/plug-api/lib/async.ts +1 -6
- package/plug-api/lib/collation.ts +22 -0
- package/plug-api/lib/crypto.ts +16 -15
- package/plug-api/lib/dates.ts +33 -0
- package/plug-api/lib/fuzzy.ts +282 -0
- package/plug-api/lib/json.ts +0 -6
- package/plug-api/lib/limited_map.ts +0 -2
- package/plug-api/lib/link_write.ts +27 -0
- package/plug-api/lib/ref.ts +28 -5
- package/plug-api/lib/resolve.ts +0 -1
- package/plug-api/lib/resolve_path.ts +290 -0
- package/plug-api/lib/shortcut.ts +15 -0
- package/plug-api/lib/tags.ts +0 -3
- package/plug-api/lib/transclusion.ts +28 -1
- package/plug-api/lib/tree.ts +0 -5
- package/plug-api/lib/yaml.ts +3 -42
- package/plug-api/syscall.ts +1 -5
- package/plug-api/syscalls/config.ts +2 -2
- package/plug-api/syscalls/editor.ts +59 -213
- package/plug-api/syscalls/icon.ts +18 -0
- package/plug-api/syscalls/index.ts +22 -4
- package/plug-api/syscalls/search.ts +11 -0
- package/plug-api/syscalls/space.ts +122 -5
- package/plug-api/syscalls/sync.ts +23 -2
- package/plug-api/syscalls/system.ts +46 -0
- package/plug-api/syscalls.ts +2 -0
- package/plug-api/system_mock.ts +13 -0
- package/plug-api/types/client.ts +4 -3
- package/plug-api/types/datastore.ts +11 -2
- package/plug-api/types/index.ts +10 -0
- package/plug-api/types/profile.ts +13 -0
- package/plug-api/types/revisions.ts +67 -0
- package/plug-api/ui/description.ts +64 -0
- package/plug-api/ui/hover.ts +69 -0
- package/plug-api/ui/index.ts +58 -2
- package/plug-api/ui/scroll.ts +30 -0
- package/plug-api/ui/tree_model.ts +263 -0
- package/plug-api/ui/tree_types.ts +66 -0
- package/plug-api/ui/use_fit_collapse.ts +67 -0
- package/plugs/builtin_plugs.ts +0 -1
- package/plugs/index/types.ts +9 -0
- package/plug-api/lib/memory_cache.ts +0 -21
- /package/plug-api/{ui → lib}/panel_styles.ts +0 -0
|
@@ -24,7 +24,6 @@ const workerRuntimePlugin: esbuild.Plugin = {
|
|
|
24
24
|
name: "worker-runtime",
|
|
25
25
|
setup(build) {
|
|
26
26
|
if (typeof __EMBEDDED_WORKER_RUNTIME_JS__ !== "undefined") {
|
|
27
|
-
// Bundled CLI: serve pre-bundled JS from the embedded constant
|
|
28
27
|
build.onResolve({ filter: /^worker-runtime$/ }, () => ({
|
|
29
28
|
path: "worker-runtime",
|
|
30
29
|
namespace: "worker-runtime",
|
|
@@ -34,7 +33,6 @@ const workerRuntimePlugin: esbuild.Plugin = {
|
|
|
34
33
|
loader: "js",
|
|
35
34
|
}));
|
|
36
35
|
} else {
|
|
37
|
-
// From source: point directly at the .ts file, esbuild handles it
|
|
38
36
|
build.onResolve({ filter: /^worker-runtime$/ }, () => ({
|
|
39
37
|
path: path.join(import.meta.dirname, "worker_runtime.ts"),
|
|
40
38
|
}));
|
|
@@ -70,14 +68,12 @@ export async function compileManifest(
|
|
|
70
68
|
delete manifest.build;
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
// Assets
|
|
74
71
|
const assetsBundle = await bundleAssets(
|
|
75
72
|
path.resolve(rootPath),
|
|
76
73
|
(manifest.assets as string[]) || [],
|
|
77
74
|
);
|
|
78
75
|
manifest.assets = assetsBundle.toJSON();
|
|
79
76
|
|
|
80
|
-
// Normalize the edge case of a plug with no functions
|
|
81
77
|
if (!manifest.functions) {
|
|
82
78
|
manifest.functions = {};
|
|
83
79
|
}
|
|
@@ -92,13 +88,11 @@ ${Object.entries(manifest.functions)
|
|
|
92
88
|
return "";
|
|
93
89
|
}
|
|
94
90
|
let [filePath, jsFunctionName] = def.path.split(":");
|
|
95
|
-
// Resolve path
|
|
96
91
|
filePath = path.join(rootPath, filePath);
|
|
97
92
|
|
|
98
|
-
return `import {${jsFunctionName} as ${funcName}} from "${
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}";\n`;
|
|
93
|
+
return `import {${jsFunctionName} as ${funcName}} from "${path
|
|
94
|
+
.resolve(filePath)
|
|
95
|
+
.replaceAll("\\", "\\\\")}";\n`;
|
|
102
96
|
})
|
|
103
97
|
.join("")}
|
|
104
98
|
|
|
@@ -122,8 +116,6 @@ export const plug = {manifest, functionMapping};
|
|
|
122
116
|
setupMessageListener(functionMapping, manifest, self.postMessage);
|
|
123
117
|
`;
|
|
124
118
|
|
|
125
|
-
// console.log("Code:", jsFile);
|
|
126
|
-
|
|
127
119
|
const tempDir = await mkdtemp(path.join(tmpdir(), "plug-compile-"));
|
|
128
120
|
const inFile = path.join(tempDir, "input.js");
|
|
129
121
|
const outFile = `${destPath}/${manifest.name}.plug.js`;
|
|
@@ -152,7 +144,6 @@ setupMessageListener(functionMapping, manifest, self.postMessage);
|
|
|
152
144
|
jsCode = patchBundledJS(jsCode);
|
|
153
145
|
await writeFile(outFile, jsCode, "utf-8");
|
|
154
146
|
|
|
155
|
-
// Clean up temp directory
|
|
156
147
|
await rm(tempDir, { recursive: true, force: true });
|
|
157
148
|
|
|
158
149
|
console.log(`Plug ${manifest.name} written to ${outFile}.`);
|
|
@@ -175,7 +166,6 @@ export async function compileManifests(
|
|
|
175
166
|
building = true;
|
|
176
167
|
await mkdir(dist, { recursive: true });
|
|
177
168
|
const startTime = Date.now();
|
|
178
|
-
// Build all plugs in parallel
|
|
179
169
|
await Promise.all(
|
|
180
170
|
manifestFiles.map(async (plugManifestPath) => {
|
|
181
171
|
const manifestPath = plugManifestPath as string;
|
|
@@ -7,7 +7,6 @@ import { race, timeout } from "@silverbulletmd/silverbullet/lib/async";
|
|
|
7
7
|
import { fsEndpoint } from "../../spaces/constants.ts";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Represents a "safe" execution environment for plug code
|
|
11
10
|
* Effectively this wraps a web worker, the reason to have this split from Plugs is to allow plugs to manage multiple sandboxes, e.g. for performance in the future
|
|
12
11
|
*/
|
|
13
12
|
export class WorkerSandbox<HookT> implements Sandbox<HookT> {
|
|
@@ -25,7 +24,6 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
|
|
|
25
24
|
private workerOptions = {},
|
|
26
25
|
) {}
|
|
27
26
|
|
|
28
|
-
/** Factory producing a WorkerSandbox for a plug file at a local space path. */
|
|
29
27
|
static forPath<HookT>(path: string): SandboxFactory<HookT> {
|
|
30
28
|
return (plug) =>
|
|
31
29
|
new WorkerSandbox(
|
|
@@ -40,7 +38,6 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
|
|
|
40
38
|
init(): Promise<void> {
|
|
41
39
|
console.log("Booting up worker from", this.workerUrl.pathname);
|
|
42
40
|
if (this.worker) {
|
|
43
|
-
// Race condition
|
|
44
41
|
console.warn("Double init of sandbox, ignoring");
|
|
45
42
|
return Promise.resolve();
|
|
46
43
|
}
|
|
@@ -54,14 +51,12 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
|
|
|
54
51
|
timeout(5000).catch((_) =>
|
|
55
52
|
Promise.reject(new Error("Plug timed out during creation")),
|
|
56
53
|
),
|
|
57
|
-
new Promise((resolve) => {
|
|
54
|
+
new Promise<void>((resolve) => {
|
|
58
55
|
this.worker!.onmessage = (ev) => {
|
|
59
56
|
if (ev.data.type === "manifest") {
|
|
60
57
|
this.manifest = ev.data.manifest;
|
|
61
|
-
// Set manifest in the plug
|
|
62
58
|
this.plug.manifest = this.manifest!;
|
|
63
59
|
|
|
64
|
-
// Set assets in the plug
|
|
65
60
|
this.plug.assets = new AssetBundle(
|
|
66
61
|
this.manifest?.assets ? (this.manifest.assets as AssetJson) : {},
|
|
67
62
|
);
|
|
@@ -72,7 +67,15 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
|
|
|
72
67
|
void this.onMessage(ev.data);
|
|
73
68
|
};
|
|
74
69
|
}),
|
|
75
|
-
])
|
|
70
|
+
]).catch((e) => {
|
|
71
|
+
// A worker that failed to come up must not linger: its script fetch may
|
|
72
|
+
// still be in flight (e.g. on a slow link), and a late boot would
|
|
73
|
+
// resolve into a sandbox the system already gave up on. Terminating it
|
|
74
|
+
// also lets a retry start from a clean slate.
|
|
75
|
+
this.worker?.terminate();
|
|
76
|
+
this.worker = undefined;
|
|
77
|
+
throw e;
|
|
78
|
+
});
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
async onMessage(data: ControllerMessage) {
|
|
@@ -92,7 +95,6 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
|
|
|
92
95
|
result: result,
|
|
93
96
|
} as WorkerMessage);
|
|
94
97
|
} catch (e: any) {
|
|
95
|
-
// console.error("Syscall fail", e);
|
|
96
98
|
this.worker &&
|
|
97
99
|
this.worker!.postMessage({
|
|
98
100
|
type: "sysr",
|
|
@@ -118,7 +120,6 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
|
|
|
118
120
|
|
|
119
121
|
async invoke(name: string, args: any[]): Promise<any> {
|
|
120
122
|
if (!this.worker) {
|
|
121
|
-
// Lazy initialization
|
|
122
123
|
await this.init();
|
|
123
124
|
}
|
|
124
125
|
this.reqId++;
|
|
@@ -138,5 +139,14 @@ export class WorkerSandbox<HookT> implements Sandbox<HookT> {
|
|
|
138
139
|
this.worker.terminate();
|
|
139
140
|
this.worker = undefined;
|
|
140
141
|
}
|
|
142
|
+
// A terminated worker will never post the `invr` that would otherwise
|
|
143
|
+
// settle these.
|
|
144
|
+
if (this.outstandingInvocations.size > 0) {
|
|
145
|
+
const error = new Error("Plug sandbox stopped");
|
|
146
|
+
for (const { reject } of this.outstandingInvocations.values()) {
|
|
147
|
+
reject(error);
|
|
148
|
+
}
|
|
149
|
+
this.outstandingInvocations.clear();
|
|
150
|
+
}
|
|
141
151
|
}
|
|
142
152
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { copyToClipboard } from "../../clipboard.ts";
|
|
1
2
|
import {
|
|
2
3
|
acceptCompletion,
|
|
3
4
|
closeCompletion,
|
|
@@ -72,6 +73,7 @@ import type {
|
|
|
72
73
|
FilterOption,
|
|
73
74
|
NotificationAction,
|
|
74
75
|
NotificationType,
|
|
76
|
+
PanelMode,
|
|
75
77
|
UploadFile,
|
|
76
78
|
} from "@silverbulletmd/silverbullet/type/client";
|
|
77
79
|
import type { VimConfig } from "@silverbulletmd/silverbullet/type/config";
|
|
@@ -79,6 +81,9 @@ import type { PageMeta } from "@silverbulletmd/silverbullet/type/index";
|
|
|
79
81
|
import { updateBakedSections } from "../../baked_sections/bake.ts";
|
|
80
82
|
import type { Client } from "../../client.ts";
|
|
81
83
|
import { refreshLintEffect } from "../../codemirror/lint.ts";
|
|
84
|
+
import { isMobileDevice, isNarrowScreen } from "../../lib/mobile.ts";
|
|
85
|
+
import { hide as hideNavigatorSlot } from "../../navigator/navigator.ts";
|
|
86
|
+
import type { PanelSlot } from "../../types/ui.ts";
|
|
82
87
|
import { getVimModule } from "../../vim_loader.ts";
|
|
83
88
|
import type { SysCallMapping } from "../system.ts";
|
|
84
89
|
|
|
@@ -137,6 +142,40 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
137
142
|
"Returns page metadata ordered from most to least recently opened.",
|
|
138
143
|
returns: [{ type: "PageMeta[]", description: "Recently opened pages." }],
|
|
139
144
|
},
|
|
145
|
+
"editor.getLastOpenedMap": {
|
|
146
|
+
callback: (): Record<string, number> => {
|
|
147
|
+
const out: Record<string, number> = {};
|
|
148
|
+
for (const pageMeta of client.ui.viewState.allPages) {
|
|
149
|
+
if (pageMeta.lastOpened) out[pageMeta.name] = pageMeta.lastOpened;
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
},
|
|
153
|
+
description: `Returns a map of page name to the time it was last opened (epoch milliseconds), for the pages that have ever been opened on this client. This lives outside the object index.`,
|
|
154
|
+
returns: [
|
|
155
|
+
{
|
|
156
|
+
type: "Record<string, number>",
|
|
157
|
+
description: "Page name to last-opened timestamp.",
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
"editor.getViewableExtensions": {
|
|
162
|
+
callback: (): string[] => {
|
|
163
|
+
return [
|
|
164
|
+
...new Set(
|
|
165
|
+
Array.from(
|
|
166
|
+
client.clientSystem.documentEditorHook.documentEditors.values(),
|
|
167
|
+
).flatMap(({ extensions }) => extensions),
|
|
168
|
+
),
|
|
169
|
+
];
|
|
170
|
+
},
|
|
171
|
+
description: `Returns the file extensions that have a document editor registered, i.e. the documents this client can actually open. Extensions carry no leading dot. Which editors are loaded depends on the plugs installed, so this is a property of the client rather than of the space.`,
|
|
172
|
+
returns: [
|
|
173
|
+
{
|
|
174
|
+
type: "string[]",
|
|
175
|
+
description: "Extensions with a registered document editor.",
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
},
|
|
140
179
|
"editor.getText": {
|
|
141
180
|
callback: () => {
|
|
142
181
|
return client.editorView.state.sliceDoc();
|
|
@@ -353,9 +392,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
353
392
|
},
|
|
354
393
|
],
|
|
355
394
|
},
|
|
356
|
-
// Re-evaluate every baked section on the current page and rewrite each body
|
|
357
|
-
// with its latest output (same as the "Baked Sections: Update" command),
|
|
358
|
-
// exposed for programmatic use.
|
|
359
395
|
"editor.updateBakedSections": {
|
|
360
396
|
callback: (): Promise<void> => {
|
|
361
397
|
return updateBakedSections(client);
|
|
@@ -594,13 +630,20 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
594
630
|
],
|
|
595
631
|
},
|
|
596
632
|
"editor.showPanel": {
|
|
597
|
-
callback: (
|
|
633
|
+
callback: async (
|
|
598
634
|
_ctx,
|
|
599
635
|
id: string,
|
|
600
|
-
mode:
|
|
636
|
+
mode: PanelMode,
|
|
601
637
|
html: HTMLElement | HTMLElement[] | string,
|
|
602
638
|
script: string,
|
|
603
639
|
) => {
|
|
640
|
+
// The other half of the shared-slot arbitration in editor_ui.tsx: a
|
|
641
|
+
// plug opening one of these slots closes the navigator's occupant.
|
|
642
|
+
if (id === "modal" || id === "bhs") {
|
|
643
|
+
await hideNavigatorSlot(id, undefined, {
|
|
644
|
+
restoreDisplaced: false,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
604
647
|
client.ui.viewDispatch({
|
|
605
648
|
type: "show-panel",
|
|
606
649
|
id: id as any,
|
|
@@ -620,7 +663,7 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
620
663
|
},
|
|
621
664
|
{
|
|
622
665
|
name: "mode",
|
|
623
|
-
type: "number",
|
|
666
|
+
type: "number | string",
|
|
624
667
|
description: "The panel display mode or size.",
|
|
625
668
|
},
|
|
626
669
|
{
|
|
@@ -641,8 +684,33 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
641
684
|
},
|
|
642
685
|
description: "Returns focus to the main editor.",
|
|
643
686
|
},
|
|
687
|
+
"editor.getFocusedPanelSlot": {
|
|
688
|
+
callback: (): PanelSlot | undefined => {
|
|
689
|
+
const active = document.activeElement;
|
|
690
|
+
if (!(active instanceof HTMLIFrameElement)) return undefined;
|
|
691
|
+
const slot = active.dataset.slot;
|
|
692
|
+
return slot === "lhs" ||
|
|
693
|
+
slot === "rhs" ||
|
|
694
|
+
slot === "bhs" ||
|
|
695
|
+
slot === "modal"
|
|
696
|
+
? slot
|
|
697
|
+
: undefined;
|
|
698
|
+
},
|
|
699
|
+
description:
|
|
700
|
+
"Returns the slot of the panel (keyed or legacy) whose iframe currently holds focus, or undefined if none does.",
|
|
701
|
+
returns: [
|
|
702
|
+
{
|
|
703
|
+
type: '"lhs" | "rhs" | "bhs" | "modal" | undefined',
|
|
704
|
+
description:
|
|
705
|
+
"The focused panel's slot, or undefined if no panel iframe has focus.",
|
|
706
|
+
},
|
|
707
|
+
],
|
|
708
|
+
},
|
|
644
709
|
"editor.hidePanel": {
|
|
645
|
-
callback: (_ctx, id: string) => {
|
|
710
|
+
callback: async (_ctx, id: string) => {
|
|
711
|
+
// The navigator owns this slot whenever it is the thing showing
|
|
712
|
+
// there, so "hide the panel at this location" has to reach it too.
|
|
713
|
+
await hideNavigatorSlot(id);
|
|
646
714
|
client.ui.viewDispatch({
|
|
647
715
|
type: "hide-panel",
|
|
648
716
|
id: id as any,
|
|
@@ -664,14 +732,19 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
664
732
|
"editor.showProgress": {
|
|
665
733
|
callback: (
|
|
666
734
|
_ctx,
|
|
735
|
+
progressType: "sync" | "index",
|
|
667
736
|
progressPercentage?: number,
|
|
668
|
-
progressType?: "sync" | "index",
|
|
669
737
|
) => {
|
|
670
|
-
client.ui.showProgress(
|
|
738
|
+
client.ui.showProgress(progressType, progressPercentage);
|
|
671
739
|
},
|
|
672
740
|
description:
|
|
673
741
|
"Shows, updates, or hides a sync or indexing progress indicator.",
|
|
674
742
|
parameters: [
|
|
743
|
+
{
|
|
744
|
+
name: "progressType",
|
|
745
|
+
type: "sync | index",
|
|
746
|
+
description: "The operation represented by the indicator.",
|
|
747
|
+
},
|
|
675
748
|
{
|
|
676
749
|
name: "progressPercentage",
|
|
677
750
|
type: "number",
|
|
@@ -679,12 +752,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
679
752
|
"Completion percentage, or undefined to hide the indicator.",
|
|
680
753
|
optional: true,
|
|
681
754
|
},
|
|
682
|
-
{
|
|
683
|
-
name: "progressType",
|
|
684
|
-
type: "sync | index",
|
|
685
|
-
description: "The operation represented by the indicator.",
|
|
686
|
-
optional: true,
|
|
687
|
-
},
|
|
688
755
|
],
|
|
689
756
|
},
|
|
690
757
|
"editor.insertAtPos": {
|
|
@@ -828,11 +895,8 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
828
895
|
},
|
|
829
896
|
"editor.moveCursorToLine": {
|
|
830
897
|
callback: (_ctx, line: number, column = 1, center = false) => {
|
|
831
|
-
// CodeMirror already keeps information about lines
|
|
832
898
|
const cmLine = client.editorView.state.doc.line(line);
|
|
833
|
-
// How much to move inside the line, column number starts from 1
|
|
834
899
|
const offset = Math.max(0, Math.min(cmLine.length, column - 1));
|
|
835
|
-
// Just reuse the implementation above
|
|
836
900
|
const moveCursor = syscalls["editor.moveCursor"];
|
|
837
901
|
const moveCursorCallback =
|
|
838
902
|
typeof moveCursor === "function" ? moveCursor : moveCursor.callback;
|
|
@@ -1068,44 +1132,30 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1068
1132
|
throw new Error("Vim module not loaded.");
|
|
1069
1133
|
}
|
|
1070
1134
|
const { Vim } = vimMod;
|
|
1071
|
-
// Override the default "o" binding to be more intelligent and follow the markdown editor's behavior
|
|
1072
1135
|
Vim.mapCommand("o", "action", "newline-continue-markup", {}, {});
|
|
1073
1136
|
Vim.mapCommand("O", "action", "back-newline-continue-markup", {}, {});
|
|
1074
1137
|
Vim.unmap("<C-q>", undefined as any);
|
|
1075
1138
|
Vim.defineAction("newline-continue-markup", (cm) => {
|
|
1076
|
-
// Append at end of line
|
|
1077
1139
|
Vim.handleKey(cm, "A", "+input");
|
|
1078
|
-
// Insert newline continuing markup where appropriate
|
|
1079
1140
|
insertNewlineContinueMarkup(client.editorView) ||
|
|
1080
1141
|
insertNewlineAndIndent(client.editorView);
|
|
1081
1142
|
});
|
|
1082
1143
|
Vim.defineAction("back-newline-continue-markup", (cm) => {
|
|
1083
|
-
// Determine current line
|
|
1084
1144
|
const pos = client.editorView.state.selection.main.from;
|
|
1085
1145
|
const line = client.editorView.state.doc.lineAt(pos).number;
|
|
1086
1146
|
if (line === 1) {
|
|
1087
|
-
// We're on the top line
|
|
1088
|
-
// Go to 0:0
|
|
1089
1147
|
Vim.handleKey(cm, "0", "+input");
|
|
1090
|
-
// Insert a newline
|
|
1091
1148
|
insertNewline(client.editorView);
|
|
1092
|
-
// Go up to the new line
|
|
1093
1149
|
Vim.handleKey(cm, "k", "+input");
|
|
1094
|
-
// Into insert mode
|
|
1095
1150
|
Vim.handleKey(cm, "i", "+input");
|
|
1096
1151
|
} else {
|
|
1097
|
-
// We're elsewhere in the document
|
|
1098
|
-
// Go up
|
|
1099
1152
|
Vim.handleKey(cm, "k", "+input");
|
|
1100
|
-
// Append mode at the end of the line
|
|
1101
1153
|
Vim.handleKey(cm, "A", "+input");
|
|
1102
|
-
// Insert a newline using the continue markup thing
|
|
1103
1154
|
insertNewlineContinueMarkup(client.editorView) ||
|
|
1104
1155
|
insertNewlineAndIndent(client.editorView);
|
|
1105
1156
|
}
|
|
1106
1157
|
});
|
|
1107
1158
|
|
|
1108
|
-
// Load the config if any
|
|
1109
1159
|
const config = client.config.get<VimConfig>("vim", {});
|
|
1110
1160
|
if (config) {
|
|
1111
1161
|
config.unmap?.forEach((binding) => {
|
|
@@ -1143,9 +1193,9 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1143
1193
|
},
|
|
1144
1194
|
"editor.openPageNavigator": {
|
|
1145
1195
|
callback: (_ctx, mode: "page" | "meta" | "document" | "all" = "page") => {
|
|
1146
|
-
client.startPageNavigate(mode);
|
|
1196
|
+
void client.startPageNavigate(mode);
|
|
1147
1197
|
},
|
|
1148
|
-
description:
|
|
1198
|
+
description: `Opens the page picker in the requested browsing mode. Each mode maps to a segment of the \`std.pages\` navigator view.`,
|
|
1149
1199
|
parameters: [
|
|
1150
1200
|
{
|
|
1151
1201
|
name: "mode",
|
|
@@ -1161,6 +1211,30 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1161
1211
|
},
|
|
1162
1212
|
description: "Opens the command palette.",
|
|
1163
1213
|
},
|
|
1214
|
+
"editor.openNavigator": {
|
|
1215
|
+
callback: (
|
|
1216
|
+
_ctx,
|
|
1217
|
+
name: string,
|
|
1218
|
+
opts?: {
|
|
1219
|
+
segment?: string;
|
|
1220
|
+
phrase?: string;
|
|
1221
|
+
dropdown?: unknown;
|
|
1222
|
+
focus?: boolean;
|
|
1223
|
+
},
|
|
1224
|
+
) => client.openNavigatorView(name, opts),
|
|
1225
|
+
description: `Opens a navigator view, returning whether it opened. False means the view isn't there to open -- typically because it's defined in Space Lua that hasn't been indexed yet -- so a caller can fall back to something else.`,
|
|
1226
|
+
parameters: [
|
|
1227
|
+
{ name: "name", type: "string", description: "The view's name." },
|
|
1228
|
+
{
|
|
1229
|
+
name: "opts",
|
|
1230
|
+
type: "table",
|
|
1231
|
+
description:
|
|
1232
|
+
"Optional `segment` (segment label), `phrase`, `dropdown` (dropdown value to select), and `focus` (`false` opens without taking focus).",
|
|
1233
|
+
optional: true,
|
|
1234
|
+
},
|
|
1235
|
+
],
|
|
1236
|
+
returns: [{ type: "boolean", description: "Whether a view opened." }],
|
|
1237
|
+
},
|
|
1164
1238
|
"editor.deleteLine": {
|
|
1165
1239
|
callback: () => {
|
|
1166
1240
|
deleteLine(client.editorView);
|
|
@@ -1222,7 +1296,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1222
1296
|
},
|
|
1223
1297
|
description: "Moves the current line or selected lines downward.",
|
|
1224
1298
|
},
|
|
1225
|
-
// Folding
|
|
1226
1299
|
"editor.fold": {
|
|
1227
1300
|
callback: () => {
|
|
1228
1301
|
foldCode(client.editorView);
|
|
@@ -1265,7 +1338,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1265
1338
|
},
|
|
1266
1339
|
description: "Redoes the most recently undone editor change.",
|
|
1267
1340
|
},
|
|
1268
|
-
// Cursor motion syscalls
|
|
1269
1341
|
"editor.cursorCharLeft": {
|
|
1270
1342
|
callback: () => cursorCharLeft(client.editorView),
|
|
1271
1343
|
description:
|
|
@@ -1310,7 +1382,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1310
1382
|
callback: () => cursorDocEnd(client.editorView),
|
|
1311
1383
|
description: "Moves the cursor to the end of the document.",
|
|
1312
1384
|
},
|
|
1313
|
-
// Cursor motions that also navigate the completion popup if it's open
|
|
1314
1385
|
"editor.cursorLineUp": {
|
|
1315
1386
|
callback: () => {
|
|
1316
1387
|
const view = client.editorView;
|
|
@@ -1347,7 +1418,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1347
1418
|
description:
|
|
1348
1419
|
"Moves completion selection down one page when open, otherwise moves the cursor down one viewport page.",
|
|
1349
1420
|
},
|
|
1350
|
-
// Selection-extending motions
|
|
1351
1421
|
"editor.selectCharLeft": {
|
|
1352
1422
|
callback: () => selectCharLeft(client.editorView),
|
|
1353
1423
|
description:
|
|
@@ -1411,7 +1481,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1411
1481
|
callback: () => selectPageDown(client.editorView),
|
|
1412
1482
|
description: "Extends the selection downward by one viewport page.",
|
|
1413
1483
|
},
|
|
1414
|
-
// Delete
|
|
1415
1484
|
"editor.deleteCharBackward": {
|
|
1416
1485
|
callback: () => deleteCharBackward(client.editorView),
|
|
1417
1486
|
description: "Deletes the selection or the character before the cursor.",
|
|
@@ -1444,7 +1513,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1444
1513
|
callback: () => transposeChars(client.editorView),
|
|
1445
1514
|
description: "Transposes the characters around the cursor.",
|
|
1446
1515
|
},
|
|
1447
|
-
// Enter: accept completion if popup is open, else newline-and-indent
|
|
1448
1516
|
"editor.insertNewline": {
|
|
1449
1517
|
callback: () => {
|
|
1450
1518
|
const view = client.editorView;
|
|
@@ -1454,7 +1522,6 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1454
1522
|
description:
|
|
1455
1523
|
"Accepts the active completion, or inserts a newline with appropriate indentation.",
|
|
1456
1524
|
},
|
|
1457
|
-
// Completion popup control
|
|
1458
1525
|
"editor.acceptCompletion": {
|
|
1459
1526
|
callback: () => acceptCompletion(client.editorView),
|
|
1460
1527
|
description:
|
|
@@ -1483,20 +1550,14 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1483
1550
|
"editor.copyToClipboard": {
|
|
1484
1551
|
callback: async (_ctx, data: string | Blob) => {
|
|
1485
1552
|
try {
|
|
1486
|
-
|
|
1487
|
-
await navigator.clipboard.writeText(data);
|
|
1488
|
-
} else {
|
|
1489
|
-
await navigator.clipboard.write([
|
|
1490
|
-
new ClipboardItem({ [data.type]: data }),
|
|
1491
|
-
]);
|
|
1492
|
-
}
|
|
1553
|
+
await copyToClipboard(data);
|
|
1493
1554
|
} catch (e) {
|
|
1494
1555
|
console.error(e);
|
|
1495
1556
|
client.ui.flashNotification(`Could not copy to clipboard: ${e}`);
|
|
1496
1557
|
}
|
|
1497
1558
|
},
|
|
1498
1559
|
description:
|
|
1499
|
-
"Copies text or binary Blob data to the system clipboard.
|
|
1560
|
+
"Copies text or binary Blob data to the system clipboard. Binary clipboard access requires HTTPS or localhost; text copying also works over HTTP when supported by the browser.",
|
|
1500
1561
|
parameters: [
|
|
1501
1562
|
{
|
|
1502
1563
|
name: "data",
|
|
@@ -1529,10 +1590,7 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1529
1590
|
},
|
|
1530
1591
|
|
|
1531
1592
|
"editor.isMobile": {
|
|
1532
|
-
callback: () =>
|
|
1533
|
-
const mouseDetected = globalThis.matchMedia("(pointer:fine)").matches;
|
|
1534
|
-
return !mouseDetected;
|
|
1535
|
-
},
|
|
1593
|
+
callback: () => isMobileDevice(),
|
|
1536
1594
|
description:
|
|
1537
1595
|
"Checks whether the current device lacks a fine pointer and should be treated as mobile.",
|
|
1538
1596
|
returns: [
|
|
@@ -1543,6 +1601,23 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
|
|
1543
1601
|
},
|
|
1544
1602
|
],
|
|
1545
1603
|
},
|
|
1604
|
+
|
|
1605
|
+
// Deliberately separate from `editor.isMobile`: that one answers "what
|
|
1606
|
+
// kind of pointer is this", which is the right question for touch
|
|
1607
|
+
// affordances and the wrong one for layout. A narrow window on a desktop
|
|
1608
|
+
// gets the narrow layout with a mouse attached; a tablet in landscape gets
|
|
1609
|
+
// the wide one without.
|
|
1610
|
+
"editor.isNarrowScreen": {
|
|
1611
|
+
callback: () => isNarrowScreen(),
|
|
1612
|
+
description:
|
|
1613
|
+
"Checks whether the client is currently laid out for a narrow screen, i.e. below the breakpoint where sidebar panels become full-width drawers.",
|
|
1614
|
+
returns: [
|
|
1615
|
+
{
|
|
1616
|
+
type: "boolean",
|
|
1617
|
+
description: "Whether the narrow-screen layout is in effect.",
|
|
1618
|
+
},
|
|
1619
|
+
],
|
|
1620
|
+
},
|
|
1546
1621
|
};
|
|
1547
1622
|
|
|
1548
1623
|
return syscalls;
|
|
@@ -38,11 +38,9 @@ export function eventSyscalls(
|
|
|
38
38
|
*/
|
|
39
39
|
"event.listen": {
|
|
40
40
|
callback: (_ctx, def: EventSubscription) => {
|
|
41
|
-
// console.log("Registering Lua event listener: ", def.name);
|
|
42
41
|
client.config.insert(
|
|
43
42
|
["eventListeners", def.name],
|
|
44
43
|
async (...args: any[]) => {
|
|
45
|
-
// Convert return value to JS
|
|
46
44
|
const val = await def.run(...args);
|
|
47
45
|
return luaValueToJS(val, LuaStackFrame.lostFrame);
|
|
48
46
|
},
|
|
@@ -22,7 +22,6 @@ export function sandboxFetchSyscalls(client: Client): SysCallMapping {
|
|
|
22
22
|
options: ProxyFetchRequest = {},
|
|
23
23
|
): Promise<ProxyFetchResponse> => {
|
|
24
24
|
console.warn("Deprecated: use net.proxyFetch() instead");
|
|
25
|
-
// JSONify any non-serializable body
|
|
26
25
|
if (
|
|
27
26
|
options?.body &&
|
|
28
27
|
typeof options.body !== "string" &&
|
|
@@ -44,7 +43,6 @@ export function sandboxFetchSyscalls(client: Client): SysCallMapping {
|
|
|
44
43
|
buildProxyUrl(client, url),
|
|
45
44
|
fetchOptions,
|
|
46
45
|
);
|
|
47
|
-
// Do sensible things with the body based on the content type
|
|
48
46
|
// Read as ArrayBuffer first to safely handle empty responses (e.g.
|
|
49
47
|
// PUT/DELETE returning 204 with Content-Type: application/json).
|
|
50
48
|
// resp.arrayBuffer() never throws on an empty body, whereas
|
|
@@ -100,7 +98,6 @@ export function sandboxFetchSyscalls(client: Client): SysCallMapping {
|
|
|
100
98
|
url: string,
|
|
101
99
|
options?: ProxyFetchRequest64,
|
|
102
100
|
): Promise<ProxyFetchResponse64> => {
|
|
103
|
-
// console.log("Got sandbox fetch ", url, op);
|
|
104
101
|
const fetchOptions = options
|
|
105
102
|
? {
|
|
106
103
|
method: options.method,
|
|
@@ -147,7 +144,6 @@ export function sandboxFetchSyscalls(client: Client): SysCallMapping {
|
|
|
147
144
|
|
|
148
145
|
function buildProxyUrl(client: Client, url: string) {
|
|
149
146
|
url = url.replace(/^https?:\/\//, "");
|
|
150
|
-
// Strip off the /.fs and replace with /.proxy
|
|
151
147
|
return (
|
|
152
148
|
client.httpSpacePrimitives.url.slice(0, -fsEndpoint.length) +
|
|
153
149
|
"/.proxy/" +
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { resolveFeatherIcons } from "../../lib/feather_icons.ts";
|
|
2
|
+
import type { SysCallMapping } from "../system.ts";
|
|
3
|
+
|
|
4
|
+
export function iconSyscalls(): SysCallMapping {
|
|
5
|
+
return {
|
|
6
|
+
"icon.feather": {
|
|
7
|
+
callback: (_ctx, name: string): string | null => {
|
|
8
|
+
if (!name) return null;
|
|
9
|
+
return resolveFeatherIcons([name])[name] ?? null;
|
|
10
|
+
},
|
|
11
|
+
description:
|
|
12
|
+
"Renders a Feather icon to SVG markup by name, or nil when the name isn't a Feather icon. The markup carries no color of its own (`currentColor`) and a default 24x24 size, both of which CSS at the injection site can override.",
|
|
13
|
+
parameters: [
|
|
14
|
+
{
|
|
15
|
+
name: "name",
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "A Feather icon name, e.g. `trash-2`.",
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
returns: [
|
|
21
|
+
{
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "The icon's SVG markup, or nil when unknown.",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
examples: [{ code: 'local lock = icon.feather("lock")' }],
|
|
27
|
+
},
|
|
28
|
+
"icon.resolveFeather": {
|
|
29
|
+
callback: (_ctx, names: string[]): Record<string, string> => {
|
|
30
|
+
return resolveFeatherIcons(names ?? []);
|
|
31
|
+
},
|
|
32
|
+
description:
|
|
33
|
+
"Renders a batch of Feather icons to SVG markup in one round trip, so panels can show icons without bundling the icon set themselves. Unknown names are omitted from the result. The markup carries no color of its own (`currentColor`) and a default 24x24 size, both of which CSS at the injection site can override.",
|
|
34
|
+
parameters: [
|
|
35
|
+
{
|
|
36
|
+
name: "names",
|
|
37
|
+
type: "table",
|
|
38
|
+
description: "Feather icon names, e.g. `trash-2`.",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
returns: [
|
|
42
|
+
{ type: "table", description: "Map of icon name to SVG markup." },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|