@silverbulletmd/silverbullet 2.9.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 +38 -25
- 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 +24 -14
- 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/asset.ts +61 -23
- package/client/plugos/syscalls/clientStore.ts +32 -6
- package/client/plugos/syscalls/client_code_widget.ts +9 -4
- package/client/plugos/syscalls/code_widget.ts +26 -11
- package/client/plugos/syscalls/config.ts +146 -30
- package/client/plugos/syscalls/datastore.ts +146 -40
- package/client/plugos/syscalls/editor.ts +1518 -722
- package/client/plugos/syscalls/event.ts +44 -13
- package/client/plugos/syscalls/fetch.ts +120 -83
- package/client/plugos/syscalls/icon.ts +46 -0
- package/client/plugos/syscalls/index.ts +267 -121
- package/client/plugos/syscalls/jsonschema.ts +107 -9
- package/client/plugos/syscalls/language.ts +38 -12
- package/client/plugos/syscalls/markdown.ts +166 -50
- package/client/plugos/syscalls/mq.ts +92 -20
- package/client/plugos/syscalls/navigator.ts +143 -0
- package/client/plugos/syscalls/schema_introspection.ts +33 -0
- package/client/plugos/syscalls/search.ts +44 -0
- package/client/plugos/syscalls/service_registry.ts +74 -20
- package/client/plugos/syscalls/shell.ts +51 -21
- package/client/plugos/syscalls/space.ts +412 -113
- package/client/plugos/syscalls/sync.ts +103 -20
- package/client/plugos/syscalls/system.ts +274 -139
- package/client/plugos/system.ts +28 -10
- package/client/plugos/worker_runtime.ts +0 -2
- package/client/space_lua/aggregates.ts +0 -16
- package/client/space_lua/api_documentation.ts +107 -0
- package/client/space_lua/ast.ts +13 -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 +125 -92
- package/client/space_lua/labels.ts +0 -4
- package/client/space_lua/numeric.ts +0 -5
- package/client/space_lua/parse.ts +243 -125
- package/client/space_lua/pretty_print.ts +128 -54
- package/client/space_lua/quarantine.ts +127 -0
- package/client/space_lua/query_collection.ts +7 -51
- 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 +173 -0
- package/client/space_lua/runtime.ts +133 -92
- package/client/space_lua/stdlib/crypto.ts +8 -3
- package/client/space_lua/stdlib/encoding.ts +27 -9
- package/client/space_lua/stdlib/format.ts +0 -12
- package/client/space_lua/stdlib/js.ts +136 -23
- package/client/space_lua/stdlib/load.ts +24 -16
- package/client/space_lua/stdlib/math.ts +331 -133
- package/client/space_lua/stdlib/net.ts +57 -13
- package/client/space_lua/stdlib/os.ts +111 -54
- package/client/space_lua/stdlib/pattern.ts +10 -15
- package/client/space_lua/stdlib/space_lua.ts +344 -21
- package/client/space_lua/stdlib/string.ts +282 -104
- package/client/space_lua/stdlib/string_pack.ts +58 -22
- package/client/space_lua/stdlib/table.ts +195 -45
- package/client/space_lua/stdlib.ts +354 -125
- package/client/space_lua/syscalls.ts +270 -0
- package/client/space_lua/tonumber.ts +0 -9
- package/dist/plug-compile.js +2 -3
- package/package.json +15 -5
- 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/{ui → lib}/panel_styles.ts +4 -3
- package/plug-api/lib/ref.ts +124 -5
- package/plug-api/lib/resolve.ts +0 -1
- package/plug-api/lib/resolve_path.ts +290 -0
- package/plug-api/lib/shortcut.ts +17 -1
- 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 +7 -5
- package/plug-api/syscalls/editor.ts +70 -208
- package/plug-api/syscalls/icon.ts +18 -0
- package/plug-api/syscalls/index.ts +39 -5
- package/plug-api/syscalls/jsonschema.ts +9 -0
- package/plug-api/syscalls/lua.ts +7 -0
- package/plug-api/syscalls/search.ts +11 -0
- package/plug-api/syscalls/shell.ts +0 -1
- package/plug-api/syscalls/space.ts +122 -5
- package/plug-api/syscalls/sync.ts +23 -2
- package/plug-api/syscalls/system.ts +53 -0
- package/plug-api/syscalls.ts +2 -0
- package/plug-api/system_mock.ts +15 -2
- package/plug-api/types/client.ts +4 -3
- package/plug-api/types/datastore.ts +11 -2
- package/plug-api/types/index.ts +62 -1
- package/plug-api/types/manifest.ts +22 -4
- package/plug-api/types/profile.ts +13 -0
- package/plug-api/types/revisions.ts +67 -0
- package/plug-api/ui/cx.ts +1 -3
- package/plug-api/ui/description.ts +64 -0
- package/plug-api/ui/hover.ts +69 -0
- package/plug-api/ui/index.ts +61 -2
- package/plug-api/ui/scroll.ts +30 -0
- package/plug-api/ui/slugify.ts +44 -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/client/plugos/syscalls/lua.ts +0 -81
- package/plug-api/lib/memory_cache.ts +0 -21
package/client/plugos/system.ts
CHANGED
|
@@ -2,14 +2,15 @@ import type { Hook } from "./types.ts";
|
|
|
2
2
|
import { EventEmitter } from "./event.ts";
|
|
3
3
|
import type { SandboxFactory } from "./sandboxes/sandbox.ts";
|
|
4
4
|
import { Plug } from "./plug.ts";
|
|
5
|
-
import {
|
|
5
|
+
import type { ManifestCache } from "./manifest_cache.ts";
|
|
6
6
|
import {
|
|
7
7
|
builtinPlugNames,
|
|
8
8
|
builtinPlugPaths,
|
|
9
9
|
} from "../../plugs/builtin_plugs.ts";
|
|
10
|
+
import type { LuaFunctionDocumentation } from "../../plug-api/types/index.ts";
|
|
10
11
|
|
|
11
12
|
export interface SysCallMapping {
|
|
12
|
-
[key: string]:
|
|
13
|
+
[key: string]: SyscallSignature | SyscallDefinition;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export type SystemEvents<HookT> = {
|
|
@@ -26,10 +27,14 @@ export type SyscallContext = {
|
|
|
26
27
|
|
|
27
28
|
type SyscallSignature = (...args: any[]) => Promise<any> | any;
|
|
28
29
|
|
|
29
|
-
type
|
|
30
|
+
export type SyscallDefinition = {
|
|
31
|
+
callback: SyscallSignature;
|
|
32
|
+
} & LuaFunctionDocumentation;
|
|
33
|
+
|
|
34
|
+
type RegisteredSyscall = {
|
|
30
35
|
requiredPermissions: string[];
|
|
31
36
|
callback: SyscallSignature;
|
|
32
|
-
};
|
|
37
|
+
} & LuaFunctionDocumentation;
|
|
33
38
|
|
|
34
39
|
export type SystemOptions = {
|
|
35
40
|
manifestCache?: ManifestCache<any>;
|
|
@@ -37,7 +42,7 @@ export type SystemOptions = {
|
|
|
37
42
|
};
|
|
38
43
|
|
|
39
44
|
export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|
40
|
-
registeredSyscalls = new Map<string,
|
|
45
|
+
registeredSyscalls = new Map<string, RegisteredSyscall>();
|
|
41
46
|
protected plugs = new Map<string, Plug<HookT>>();
|
|
42
47
|
protected enabledHooks = new Set<Hook<HookT>>();
|
|
43
48
|
|
|
@@ -50,7 +55,12 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|
|
50
55
|
) {
|
|
51
56
|
super();
|
|
52
57
|
if (!options.manifestCache) {
|
|
53
|
-
options.manifestCache =
|
|
58
|
+
options.manifestCache = {
|
|
59
|
+
async getManifest(plug) {
|
|
60
|
+
await plug.sandbox.init();
|
|
61
|
+
return plug.sandbox.manifest!;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
54
64
|
}
|
|
55
65
|
}
|
|
56
66
|
|
|
@@ -68,10 +78,21 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|
|
68
78
|
...registrationObjects: SysCallMapping[]
|
|
69
79
|
) {
|
|
70
80
|
for (const registrationObject of registrationObjects) {
|
|
71
|
-
for (const [name,
|
|
81
|
+
for (const [name, registration] of Object.entries(registrationObject)) {
|
|
82
|
+
const definition =
|
|
83
|
+
typeof registration === "function"
|
|
84
|
+
? { callback: registration }
|
|
85
|
+
: registration;
|
|
86
|
+
const cleanName = name.startsWith("lua:")
|
|
87
|
+
? name.slice("lua:".length)
|
|
88
|
+
: name;
|
|
89
|
+
const namespace = cleanName.split(".")[0];
|
|
90
|
+
const { callback, ...metadata } = definition;
|
|
72
91
|
this.registeredSyscalls.set(name, {
|
|
73
92
|
requiredPermissions: requiredCapabilities,
|
|
74
93
|
callback,
|
|
94
|
+
see: `API/${namespace}`,
|
|
95
|
+
...metadata,
|
|
75
96
|
});
|
|
76
97
|
}
|
|
77
98
|
}
|
|
@@ -83,7 +104,6 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|
|
83
104
|
* @param args an array of arguments to pass to the function
|
|
84
105
|
*/
|
|
85
106
|
invokeFunction(name: string, args: any[]): Promise<any> {
|
|
86
|
-
// Some sanity type checks
|
|
87
107
|
if (typeof name !== "string") {
|
|
88
108
|
throw new Error(
|
|
89
109
|
`invokeFunction: function name should be a string, got ${typeof name}`,
|
|
@@ -96,7 +116,6 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|
|
96
116
|
}
|
|
97
117
|
const [plugName, functionName] = name.split(".");
|
|
98
118
|
if (!functionName) {
|
|
99
|
-
// Sanity check
|
|
100
119
|
throw new Error(`Missing function name: ${name}`);
|
|
101
120
|
}
|
|
102
121
|
const plug = this.loadedPlugs.get(plugName);
|
|
@@ -153,7 +172,6 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
|
|
|
153
172
|
throw new Error("Plug tried to overwrite internal plug");
|
|
154
173
|
}
|
|
155
174
|
|
|
156
|
-
// Validate the manifest
|
|
157
175
|
let errors: string[] = [];
|
|
158
176
|
for (const feature of this.enabledHooks) {
|
|
159
177
|
errors = [...errors, ...feature.validateManifest(manifest)];
|
|
@@ -112,7 +112,6 @@ export function setupMessageListener(
|
|
|
112
112
|
}
|
|
113
113
|
})().catch(console.error);
|
|
114
114
|
});
|
|
115
|
-
// Signal initialization with manifest
|
|
116
115
|
workerPostMessage({
|
|
117
116
|
type: "manifest",
|
|
118
117
|
manifest,
|
|
@@ -139,7 +138,6 @@ export async function sandboxFetch(
|
|
|
139
138
|
|
|
140
139
|
globalThis.nativeFetch = globalThis.fetch;
|
|
141
140
|
|
|
142
|
-
// Monkey patch fetch()
|
|
143
141
|
export function monkeyPatchFetch() {
|
|
144
142
|
// @ts-expect-error: monkey patching fetch
|
|
145
143
|
globalThis.fetch = async (
|
|
@@ -112,7 +112,6 @@ function covarIterate(state: CovarState, x: any, y: any): CovarState {
|
|
|
112
112
|
return state;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
// Quantile interpolation methods
|
|
116
115
|
type QuantileMethod =
|
|
117
116
|
| "linear" // percentile_cont
|
|
118
117
|
| "lower" // percentile_disc
|
|
@@ -126,7 +125,6 @@ interface QuantileState {
|
|
|
126
125
|
method: QuantileMethod;
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
// Default method based on aggregate invocation name
|
|
130
128
|
const quantileNameDefaults: Record<string, QuantileMethod> = {
|
|
131
129
|
percentile_cont: "linear",
|
|
132
130
|
percentile_disc: "lower",
|
|
@@ -159,7 +157,6 @@ function quantileFinish(state: QuantileState): number | null {
|
|
|
159
157
|
}
|
|
160
158
|
}
|
|
161
159
|
|
|
162
|
-
// Shared spec — branching on `ctx.name` for the default method
|
|
163
160
|
function makeQuantileSpec(name: string, description: string): AggregateSpec {
|
|
164
161
|
return {
|
|
165
162
|
name,
|
|
@@ -187,9 +184,7 @@ function makeQuantileSpec(name: string, description: string): AggregateSpec {
|
|
|
187
184
|
};
|
|
188
185
|
}
|
|
189
186
|
|
|
190
|
-
// Built-in aggregate specs
|
|
191
187
|
const builtinAggregates: Record<string, AggregateSpec> = {
|
|
192
|
-
// General purpose
|
|
193
188
|
count: {
|
|
194
189
|
name: "count",
|
|
195
190
|
description:
|
|
@@ -299,7 +294,6 @@ const builtinAggregates: Record<string, AggregateSpec> = {
|
|
|
299
294
|
return value;
|
|
300
295
|
}),
|
|
301
296
|
},
|
|
302
|
-
// Collection and format
|
|
303
297
|
array_agg: {
|
|
304
298
|
name: "array_agg",
|
|
305
299
|
description: "Input values concatenated into an array",
|
|
@@ -365,7 +359,6 @@ const builtinAggregates: Record<string, AggregateSpec> = {
|
|
|
365
359
|
return JSON.stringify(state);
|
|
366
360
|
}),
|
|
367
361
|
},
|
|
368
|
-
// Bitwise and boolean
|
|
369
362
|
bit_and: {
|
|
370
363
|
name: "bit_and",
|
|
371
364
|
description: "Bitwise AND of all non-null input values",
|
|
@@ -447,7 +440,6 @@ const builtinAggregates: Record<string, AggregateSpec> = {
|
|
|
447
440
|
return state.hasValue ? state.result : null;
|
|
448
441
|
}),
|
|
449
442
|
},
|
|
450
|
-
// Statistical
|
|
451
443
|
stddev_pop: {
|
|
452
444
|
name: "stddev_pop",
|
|
453
445
|
description: "Population standard deviation of non-null inputs",
|
|
@@ -558,7 +550,6 @@ const builtinAggregates: Record<string, AggregateSpec> = {
|
|
|
558
550
|
return state.bestCount > 0 ? state.best : null;
|
|
559
551
|
}),
|
|
560
552
|
},
|
|
561
|
-
// Quantile and percentile
|
|
562
553
|
quantile: makeQuantileSpec(
|
|
563
554
|
"quantile",
|
|
564
555
|
"Quantile of ordered set of non-null inputs; arguments: value, quantile (0-1), interpolation ('lower', 'higher', 'nearest', 'midpoint' and default: 'linear')",
|
|
@@ -617,14 +608,12 @@ export function getAggregateSpec(
|
|
|
617
608
|
const spec: any = config.get(`aggregates.${current}`, null);
|
|
618
609
|
if (!spec) break;
|
|
619
610
|
|
|
620
|
-
// Check for alias redirect
|
|
621
611
|
const alias = spec instanceof LuaTable ? spec.rawGet("alias") : spec.alias;
|
|
622
612
|
if (typeof alias === "string") {
|
|
623
613
|
current = alias;
|
|
624
614
|
continue;
|
|
625
615
|
}
|
|
626
616
|
|
|
627
|
-
// Full definition in config
|
|
628
617
|
let candidate: AggregateSpec | null = null;
|
|
629
618
|
if (spec instanceof LuaTable) {
|
|
630
619
|
const init = spec.rawGet("initialize");
|
|
@@ -700,16 +689,13 @@ export async function executeAggregate(
|
|
|
700
689
|
}
|
|
701
690
|
}
|
|
702
691
|
|
|
703
|
-
// Initialize
|
|
704
692
|
let state = await luaCall(spec.initialize, [ctx, ...extraArgs], noCtx, sf);
|
|
705
693
|
|
|
706
|
-
// Collect filtered items
|
|
707
694
|
const filteredItems: LuaValue[] = [];
|
|
708
695
|
const len = items.length;
|
|
709
696
|
for (let i = 1; i <= len; i++) {
|
|
710
697
|
const item = items.rawGet(i);
|
|
711
698
|
|
|
712
|
-
// Filter
|
|
713
699
|
if (filterExpr) {
|
|
714
700
|
const filterEnv = buildItemEnv(objectVariable, item, env, sf);
|
|
715
701
|
const filterResult = await evalExprFn(filterExpr, filterEnv, sf);
|
|
@@ -747,7 +733,6 @@ export async function executeAggregate(
|
|
|
747
733
|
});
|
|
748
734
|
}
|
|
749
735
|
|
|
750
|
-
// Iterate
|
|
751
736
|
for (const item of filteredItems) {
|
|
752
737
|
const itemEnv = buildItemEnv(objectVariable, item, env, sf);
|
|
753
738
|
let value: LuaValue;
|
|
@@ -773,7 +758,6 @@ export async function executeAggregate(
|
|
|
773
758
|
);
|
|
774
759
|
}
|
|
775
760
|
|
|
776
|
-
// Finish
|
|
777
761
|
if (spec.finish) {
|
|
778
762
|
state = await luaCall(spec.finish, [state, ctx, ...extraArgs], noCtx, sf);
|
|
779
763
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
LuaFunctionExampleDocumentation,
|
|
3
|
+
LuaFunctionInfo,
|
|
4
|
+
LuaFunctionParameterDocumentation,
|
|
5
|
+
LuaFunctionReturnDocumentation,
|
|
6
|
+
} from "../../plug-api/types/index.ts";
|
|
7
|
+
|
|
8
|
+
function compareNames(a: LuaFunctionInfo, b: LuaFunctionInfo): number {
|
|
9
|
+
const aName = a.name ?? "";
|
|
10
|
+
const bName = b.name ?? "";
|
|
11
|
+
return aName < bName ? -1 : aName > bName ? 1 : 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function signature(info: LuaFunctionInfo): string {
|
|
15
|
+
const parameters = (info.parameters ?? [])
|
|
16
|
+
.map((parameter) => `${parameter.name}${parameter.optional ? "?" : ""}`)
|
|
17
|
+
.join(", ");
|
|
18
|
+
return `${info.name ?? "<anonymous>"}(${parameters})`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function renderParameter(parameter: LuaFunctionParameterDocumentation): string {
|
|
22
|
+
const type = parameter.type ? ` (\`${parameter.type}\`)` : "";
|
|
23
|
+
const description = parameter.description
|
|
24
|
+
? ` — ${parameter.description}`
|
|
25
|
+
: "";
|
|
26
|
+
return `- \`${parameter.name}${parameter.optional ? "?" : ""}\`${type}${description}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function renderReturn(value: LuaFunctionReturnDocumentation): string {
|
|
30
|
+
const type = value.type ? `\`${value.type}\`` : "Value";
|
|
31
|
+
return `- ${type}${value.description ? ` — ${value.description}` : ""}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function codeFence(example: LuaFunctionExampleDocumentation): string[] {
|
|
35
|
+
const longestFence = Math.max(
|
|
36
|
+
2,
|
|
37
|
+
...[...example.code.matchAll(/`+/g)].map((match) => match[0].length),
|
|
38
|
+
);
|
|
39
|
+
const fence = "`".repeat(longestFence + 1);
|
|
40
|
+
return [`${fence}${example.language ?? "lua"}`, example.code, fence];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function renderFunction(
|
|
44
|
+
info: LuaFunctionInfo,
|
|
45
|
+
apiNamespace?: string,
|
|
46
|
+
): string[] {
|
|
47
|
+
const lines = [`## ${info.name ?? "<anonymous>"}`, ""];
|
|
48
|
+
const signatures = info.signatures?.length
|
|
49
|
+
? info.signatures
|
|
50
|
+
: [signature(info)];
|
|
51
|
+
for (const value of signatures) lines.push(`\`${value}\``);
|
|
52
|
+
|
|
53
|
+
if (info.deprecated) {
|
|
54
|
+
lines.push(
|
|
55
|
+
"",
|
|
56
|
+
`> **Deprecated:** ${
|
|
57
|
+
typeof info.deprecated === "string"
|
|
58
|
+
? info.deprecated
|
|
59
|
+
: "This function is deprecated."
|
|
60
|
+
}`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
if (info.description) lines.push("", info.description);
|
|
64
|
+
if (info.parameters?.length) {
|
|
65
|
+
lines.push("", "**Parameters:**", "");
|
|
66
|
+
lines.push(...info.parameters.map(renderParameter));
|
|
67
|
+
}
|
|
68
|
+
if (info.returns?.length) {
|
|
69
|
+
lines.push("", "**Returns:**", "");
|
|
70
|
+
lines.push(...info.returns.map(renderReturn));
|
|
71
|
+
}
|
|
72
|
+
if (info.examples?.length) {
|
|
73
|
+
lines.push(
|
|
74
|
+
"",
|
|
75
|
+
info.examples.length === 1 ? "**Example:**" : "**Examples:**",
|
|
76
|
+
);
|
|
77
|
+
for (const example of info.examples) {
|
|
78
|
+
if (example.description) lines.push("", example.description);
|
|
79
|
+
lines.push("", ...codeFence(example));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const namespacePage = apiNamespace ? `API/${apiNamespace}` : undefined;
|
|
83
|
+
if (info.see && info.see !== namespacePage) {
|
|
84
|
+
lines.push("", `**See:** [[${info.see}]]`);
|
|
85
|
+
}
|
|
86
|
+
return lines;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Render function metadata as deterministic Markdown for live `${...}` directives. */
|
|
90
|
+
export function renderApiDocumentationMarkdown(
|
|
91
|
+
functions: LuaFunctionInfo[],
|
|
92
|
+
context?: string,
|
|
93
|
+
): string {
|
|
94
|
+
const documented = functions.filter((info) => info.name).sort(compareNames);
|
|
95
|
+
if (documented.length === 0) {
|
|
96
|
+
return context
|
|
97
|
+
? `_No documented API functions found for \`${context}\`._`
|
|
98
|
+
: "_No functions found._";
|
|
99
|
+
}
|
|
100
|
+
return documented
|
|
101
|
+
.flatMap((info, index) => [
|
|
102
|
+
...(index > 0 ? [""] : []),
|
|
103
|
+
...renderFunction(info, context),
|
|
104
|
+
])
|
|
105
|
+
.join("\n")
|
|
106
|
+
.trim();
|
|
107
|
+
}
|
package/client/space_lua/ast.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { LuaFunctionDocumentation } from "../../plug-api/types/index.ts";
|
|
2
|
+
|
|
1
3
|
type ASTContext = {
|
|
2
4
|
ctx: ASTCtx;
|
|
3
5
|
};
|
|
@@ -12,6 +14,8 @@ export type ASTCtx = {
|
|
|
12
14
|
export type LuaBlock = {
|
|
13
15
|
type: "Block";
|
|
14
16
|
statements: LuaStatement[];
|
|
17
|
+
// Comments lexically owned by this block, in source order.
|
|
18
|
+
comments?: LuaComment[];
|
|
15
19
|
hasLabel?: boolean;
|
|
16
20
|
hasGoto?: boolean;
|
|
17
21
|
// the first duplicated label is recorded here for later eval
|
|
@@ -26,7 +30,14 @@ export type LuaBlock = {
|
|
|
26
30
|
hasFunctionDef?: boolean;
|
|
27
31
|
} & ASTContext;
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
export type LuaComment = {
|
|
34
|
+
type: "Comment";
|
|
35
|
+
/** Exact source text, including delimiters. */
|
|
36
|
+
text: string;
|
|
37
|
+
kind: "line" | "long";
|
|
38
|
+
ctx: ASTCtx;
|
|
39
|
+
};
|
|
40
|
+
|
|
30
41
|
export type LuaReturnStatement = {
|
|
31
42
|
type: "Return";
|
|
32
43
|
expressions: LuaExpression[];
|
|
@@ -128,6 +139,7 @@ export type LuaFunctionBody = {
|
|
|
128
139
|
type: "FunctionBody";
|
|
129
140
|
parameters: string[];
|
|
130
141
|
block: LuaBlock;
|
|
142
|
+
documentation?: LuaFunctionDocumentation;
|
|
131
143
|
} & ASTContext;
|
|
132
144
|
|
|
133
145
|
export type LuaAssignmentStatement = {
|
|
@@ -164,7 +176,6 @@ export type LuaFunctionCallStatement = {
|
|
|
164
176
|
call: LuaFunctionCallExpression;
|
|
165
177
|
} & ASTContext;
|
|
166
178
|
|
|
167
|
-
// EXPRESSIONS
|
|
168
179
|
export type LuaExpression =
|
|
169
180
|
| LuaNilLiteral
|
|
170
181
|
| LuaBooleanLiteral
|
|
@@ -281,21 +292,18 @@ export type LuaFunctionDefinition = {
|
|
|
281
292
|
body: LuaFunctionBody;
|
|
282
293
|
} & ASTContext;
|
|
283
294
|
|
|
284
|
-
// Aggregate with per-row filter
|
|
285
295
|
export type LuaFilteredCallExpression = {
|
|
286
296
|
type: "FilteredCall";
|
|
287
297
|
call: LuaFunctionCallExpression;
|
|
288
298
|
filter: LuaExpression;
|
|
289
299
|
} & ASTContext;
|
|
290
300
|
|
|
291
|
-
// Aggregate call with intra-aggregate order by
|
|
292
301
|
export type LuaAggregateCallExpression = {
|
|
293
302
|
type: "AggregateCall";
|
|
294
303
|
call: LuaFunctionCallExpression;
|
|
295
304
|
orderBy: LuaOrderBy[];
|
|
296
305
|
} & ASTContext;
|
|
297
306
|
|
|
298
|
-
// Query stuff
|
|
299
307
|
export type LuaQueryExpression = {
|
|
300
308
|
type: "Query";
|
|
301
309
|
clauses: LuaQueryClause[];
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import type { ASTCtx, LuaExpression, LuaLValue, LuaStatement } from "./ast.ts";
|
|
4
4
|
|
|
5
|
-
// Extract by `type` discriminant
|
|
6
5
|
type NarrowByType<
|
|
7
6
|
U,
|
|
8
7
|
K extends U extends { type: infer T } ? T : never,
|
|
9
8
|
> = Extract<U, { type: K }>;
|
|
10
9
|
|
|
11
|
-
// Expressions
|
|
12
10
|
export const asStringExpr = (e: LuaExpression) =>
|
|
13
11
|
e as NarrowByType<LuaExpression, "String">;
|
|
14
12
|
export const asNumberExpr = (e: LuaExpression) =>
|
|
@@ -38,7 +36,6 @@ export const asFunctionDef = (e: LuaExpression) =>
|
|
|
38
36
|
export const asQueryExpr = (e: LuaExpression) =>
|
|
39
37
|
e as NarrowByType<LuaExpression, "Query">;
|
|
40
38
|
|
|
41
|
-
// L-values
|
|
42
39
|
export const asLValueVariable = (l: LuaLValue) =>
|
|
43
40
|
l as Extract<LuaLValue, { type: "Variable" }>;
|
|
44
41
|
export const asLValueTableAccess = (l: LuaLValue) =>
|
|
@@ -46,7 +43,6 @@ export const asLValueTableAccess = (l: LuaLValue) =>
|
|
|
46
43
|
export const asLValuePropertyAccess = (l: LuaLValue) =>
|
|
47
44
|
l as Extract<LuaLValue, { type: "PropertyAccess" }>;
|
|
48
45
|
|
|
49
|
-
// Statements
|
|
50
46
|
export const asAssignment = (s: LuaStatement) =>
|
|
51
47
|
s as NarrowByType<LuaStatement, "Assignment">;
|
|
52
48
|
export const asLocal = (s: LuaStatement) =>
|
|
@@ -79,5 +75,4 @@ export const asGoto = (s: LuaStatement) =>
|
|
|
79
75
|
export const asSemicolon = (s: LuaStatement) =>
|
|
80
76
|
s as NarrowByType<LuaStatement, "Semicolon">;
|
|
81
77
|
|
|
82
|
-
// Pull ctx with a single shape
|
|
83
78
|
export const ctxOf = (node: { ctx: ASTCtx }): ASTCtx => node.ctx;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export const BUDGET_TICKS = 5000;
|
|
2
|
+
export const YIELD_AFTER_MS = 500;
|
|
3
|
+
export const YIELD_EVERY_MS = 50;
|
|
4
|
+
export const YIELD_COUNT_LIMIT = 200;
|
|
5
|
+
export const BUSY_LIMIT_DEFAULT_MS = 2000;
|
|
6
|
+
export const BUSY_LIMIT_COMMAND_MS = 10000;
|
|
7
|
+
export const KEEP_GOING_MS = 10000;
|
|
8
|
+
|
|
9
|
+
export class LuaBudgetStopped extends Error {
|
|
10
|
+
constructor() {
|
|
11
|
+
super("Script stopped by the user");
|
|
12
|
+
this.name = "LuaBudgetStopped";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type LuaBudget = {
|
|
17
|
+
ticks: number;
|
|
18
|
+
lastCheck: number;
|
|
19
|
+
lastYield: number;
|
|
20
|
+
busyMs: number;
|
|
21
|
+
yieldCount: number;
|
|
22
|
+
awaited: boolean;
|
|
23
|
+
stopped: boolean;
|
|
24
|
+
/** Set once the invocation this budget was created for has completed. */
|
|
25
|
+
finished: boolean;
|
|
26
|
+
limitReported: boolean;
|
|
27
|
+
busyLimitMs: number;
|
|
28
|
+
yieldAfterMs: number;
|
|
29
|
+
onLimit: ((b: LuaBudget) => void) | undefined;
|
|
30
|
+
now: () => number;
|
|
31
|
+
doYield: () => Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function macrotaskYield(): Promise<void> {
|
|
35
|
+
// Do NOT use scheduler.yield() here: it resumes at a *higher* priority than
|
|
36
|
+
// ordinary tasks, so it never lets timers/input/rendering run and defeats
|
|
37
|
+
// the whole point of yielding. MessageChannel posts a genuine macrotask.
|
|
38
|
+
if (typeof MessageChannel === "function") {
|
|
39
|
+
return new Promise<void>((resolve) => {
|
|
40
|
+
const ch = new MessageChannel();
|
|
41
|
+
ch.port1.onmessage = () => {
|
|
42
|
+
ch.port1.close();
|
|
43
|
+
resolve();
|
|
44
|
+
};
|
|
45
|
+
ch.port2.postMessage(undefined);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function makeLuaBudget(
|
|
52
|
+
opts: Partial<
|
|
53
|
+
Pick<
|
|
54
|
+
LuaBudget,
|
|
55
|
+
"busyLimitMs" | "yieldAfterMs" | "onLimit" | "now" | "doYield"
|
|
56
|
+
>
|
|
57
|
+
> = {},
|
|
58
|
+
): LuaBudget {
|
|
59
|
+
const now = opts.now ?? (() => performance.now());
|
|
60
|
+
return {
|
|
61
|
+
ticks: BUDGET_TICKS,
|
|
62
|
+
lastCheck: now(),
|
|
63
|
+
lastYield: now(),
|
|
64
|
+
busyMs: 0,
|
|
65
|
+
yieldCount: 0,
|
|
66
|
+
awaited: false,
|
|
67
|
+
stopped: false,
|
|
68
|
+
finished: false,
|
|
69
|
+
limitReported: false,
|
|
70
|
+
busyLimitMs: opts.busyLimitMs ?? BUSY_LIMIT_DEFAULT_MS,
|
|
71
|
+
yieldAfterMs: opts.yieldAfterMs ?? YIELD_AFTER_MS,
|
|
72
|
+
onLimit: opts.onLimit,
|
|
73
|
+
now,
|
|
74
|
+
doYield: opts.doYield ?? macrotaskYield,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function budgetTick(b: LuaBudget): Promise<void> | undefined {
|
|
79
|
+
if (b.stopped) {
|
|
80
|
+
throw new LuaBudgetStopped();
|
|
81
|
+
}
|
|
82
|
+
b.ticks = BUDGET_TICKS;
|
|
83
|
+
|
|
84
|
+
const now = b.now();
|
|
85
|
+
const sinceCheck = now - b.lastCheck;
|
|
86
|
+
b.lastCheck = now;
|
|
87
|
+
if (b.awaited) {
|
|
88
|
+
b.awaited = false;
|
|
89
|
+
} else {
|
|
90
|
+
b.busyMs += sinceCheck;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
!b.limitReported &&
|
|
95
|
+
(b.busyMs >= b.busyLimitMs || b.yieldCount >= YIELD_COUNT_LIMIT)
|
|
96
|
+
) {
|
|
97
|
+
b.limitReported = true;
|
|
98
|
+
b.onLimit?.(b);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const threshold = b.yieldCount === 0 ? b.yieldAfterMs : YIELD_EVERY_MS;
|
|
102
|
+
if (now - b.lastYield < threshold) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
b.lastYield = now;
|
|
106
|
+
b.yieldCount++;
|
|
107
|
+
// The event-loop turn spent yielding (rendering, sync, other scripts) must
|
|
108
|
+
// not be billed to this script's busyMs at the next tick, so re-read the
|
|
109
|
+
// clock on resume rather than trusting the pre-yield lastCheck.
|
|
110
|
+
return b.doYield().then(() => {
|
|
111
|
+
b.lastCheck = b.now();
|
|
112
|
+
});
|
|
113
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { KEEP_GOING_MS, type LuaBudget } from "./budget.ts";
|
|
2
|
+
|
|
3
|
+
export type BudgetNotifier = {
|
|
4
|
+
flashNotification(
|
|
5
|
+
message: string,
|
|
6
|
+
type?: string,
|
|
7
|
+
options?: {
|
|
8
|
+
timeout?: number;
|
|
9
|
+
actions?: { name: string; run: () => void }[];
|
|
10
|
+
},
|
|
11
|
+
): void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function offerStopNotification(
|
|
15
|
+
ui: BudgetNotifier,
|
|
16
|
+
message: string,
|
|
17
|
+
budget: LuaBudget,
|
|
18
|
+
onStop?: () => void,
|
|
19
|
+
): void {
|
|
20
|
+
ui.flashNotification(message, "warning", {
|
|
21
|
+
timeout: 0,
|
|
22
|
+
actions: [
|
|
23
|
+
{
|
|
24
|
+
name: "Stop",
|
|
25
|
+
run: () => {
|
|
26
|
+
// The script may have finished running (this toast is persistent)
|
|
27
|
+
// between the limit tripping and the user clicking Stop; don't
|
|
28
|
+
// quarantine a script that isn't running anymore.
|
|
29
|
+
if (budget.finished) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
budget.stopped = true;
|
|
33
|
+
onStop?.();
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "Keep going",
|
|
38
|
+
run: () => {
|
|
39
|
+
budget.busyLimitMs = budget.busyMs + KEEP_GOING_MS;
|
|
40
|
+
// Resetting to 0 would restore the 500ms first-yield threshold,
|
|
41
|
+
// making the tab unresponsive again right after the user asked
|
|
42
|
+
// to continue.
|
|
43
|
+
budget.yieldCount = 1;
|
|
44
|
+
budget.limitReported = false;
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
});
|
|
49
|
+
}
|