@silverbulletmd/silverbullet 2.9.0 → 2.10.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.
Files changed (63) hide show
  1. package/README.md +36 -23
  2. package/client/plugos/hooks/syscall.ts +24 -10
  3. package/client/plugos/syscalls/asset.ts +61 -23
  4. package/client/plugos/syscalls/clientStore.ts +32 -6
  5. package/client/plugos/syscalls/client_code_widget.ts +9 -4
  6. package/client/plugos/syscalls/code_widget.ts +26 -11
  7. package/client/plugos/syscalls/config.ts +146 -30
  8. package/client/plugos/syscalls/datastore.ts +146 -40
  9. package/client/plugos/syscalls/editor.ts +1432 -711
  10. package/client/plugos/syscalls/event.ts +46 -13
  11. package/client/plugos/syscalls/fetch.ts +123 -82
  12. package/client/plugos/syscalls/index.ts +242 -116
  13. package/client/plugos/syscalls/jsonschema.ts +107 -8
  14. package/client/plugos/syscalls/language.ts +38 -12
  15. package/client/plugos/syscalls/markdown.ts +166 -50
  16. package/client/plugos/syscalls/mq.ts +93 -20
  17. package/client/plugos/syscalls/schema_introspection.ts +33 -0
  18. package/client/plugos/syscalls/service_registry.ts +74 -20
  19. package/client/plugos/syscalls/shell.ts +51 -20
  20. package/client/plugos/syscalls/space.ts +169 -113
  21. package/client/plugos/syscalls/sync.ts +50 -19
  22. package/client/plugos/syscalls/system.ts +236 -139
  23. package/client/plugos/system.ts +21 -5
  24. package/client/space_lua/api_documentation.ts +107 -0
  25. package/client/space_lua/ast.ts +13 -0
  26. package/client/space_lua/eval.ts +12 -4
  27. package/client/space_lua/parse.ts +243 -108
  28. package/client/space_lua/pretty_print.ts +128 -54
  29. package/client/space_lua/query_collection.ts +4 -4
  30. package/client/space_lua/render_widget.ts +156 -0
  31. package/client/space_lua/runtime.ts +83 -22
  32. package/client/space_lua/stdlib/crypto.ts +8 -3
  33. package/client/space_lua/stdlib/encoding.ts +27 -9
  34. package/client/space_lua/stdlib/js.ts +137 -22
  35. package/client/space_lua/stdlib/load.ts +24 -15
  36. package/client/space_lua/stdlib/math.ts +323 -118
  37. package/client/space_lua/stdlib/net.ts +57 -9
  38. package/client/space_lua/stdlib/os.ts +111 -44
  39. package/client/space_lua/stdlib/space_lua.ts +344 -20
  40. package/client/space_lua/stdlib/string.ts +282 -104
  41. package/client/space_lua/stdlib/string_pack.ts +58 -19
  42. package/client/space_lua/stdlib/table.ts +195 -40
  43. package/client/space_lua/stdlib.ts +335 -93
  44. package/client/space_lua/syscalls.ts +270 -0
  45. package/dist/plug-compile.js +1 -1
  46. package/package.json +3 -3
  47. package/plug-api/lib/ref.ts +96 -0
  48. package/plug-api/lib/shortcut.ts +2 -1
  49. package/plug-api/syscalls/config.ts +5 -3
  50. package/plug-api/syscalls/editor.ts +16 -0
  51. package/plug-api/syscalls/index.ts +17 -1
  52. package/plug-api/syscalls/jsonschema.ts +9 -0
  53. package/plug-api/syscalls/lua.ts +7 -0
  54. package/plug-api/syscalls/shell.ts +0 -1
  55. package/plug-api/syscalls/system.ts +7 -0
  56. package/plug-api/system_mock.ts +2 -2
  57. package/plug-api/types/index.ts +52 -1
  58. package/plug-api/types/manifest.ts +22 -4
  59. package/plug-api/ui/cx.ts +1 -3
  60. package/plug-api/ui/index.ts +3 -0
  61. package/plug-api/ui/panel_styles.ts +4 -3
  62. package/plug-api/ui/slugify.ts +44 -0
  63. package/client/plugos/syscalls/lua.ts +0 -81
@@ -1,6 +1,6 @@
1
1
  import type { SysCallMapping } from "../system.ts";
2
2
  import type { Client } from "../../client.ts";
3
- import { publicVersion } from "../../../public_version.ts";
3
+ import { version as publicVersion } from "../../../version.json";
4
4
  import type { CommandDef } from "@silverbulletmd/silverbullet/type/manifest";
5
5
  import type { SyscallMeta } from "@silverbulletmd/silverbullet/type/index";
6
6
 
@@ -9,153 +9,250 @@ export function systemSyscalls(
9
9
  readOnlyMode: boolean,
10
10
  ): SysCallMapping {
11
11
  const api: SysCallMapping = {
12
- "system.invokeFunction": (
13
- _ctx,
14
- fullName: string, // plug.function
15
- ...args: any[]
16
- ) => {
17
- const [plugName, functionName] = fullName.split(".");
18
- if (!plugName || !functionName) {
19
- throw Error(`Invalid function name ${fullName}`);
20
- }
21
- const plug = client.clientSystem.system.loadedPlugs.get(plugName);
22
- if (!plug) {
23
- throw Error(`Plug ${plugName} not found`);
24
- }
25
- const functionDef = plug.manifest!.functions[functionName];
26
- if (!functionDef) {
27
- throw Error(`Function ${functionName} not found`);
28
- }
29
- return plug.invoke(functionName, args);
30
- },
31
- "system.invokeFunctionOnServer": (
32
- ctx,
33
- fullName: string, // plug.function
34
- ...args: any[]
35
- ) => {
36
- console.warn(
37
- "Calling deprecated system.invokeFunctionOnServer, use system.invokeFunction instead",
38
- );
39
- return api["system.invokeFunction"](ctx, fullName, ...args);
40
- },
41
- "system.serverSyscall": (_ctx, name: string, ...args: any[]) => {
42
- console.warn(
43
- "Calling deprecated system.serverSyscall, use syscall instead",
44
- );
45
- return client.clientSystem.localSyscall(name, args);
46
- },
47
- "system.invokeCommand": (_ctx, name: string, args?: string[]) => {
48
- console.warn("Deprecated, use editor.invokeCommand instead");
49
- return client.runCommandByName(name, args);
50
- },
51
- "system.listCommands": (): { [key: string]: CommandDef } => {
52
- const commandHook = client.clientSystem.commandHook;
53
- const allCommands: { [key: string]: CommandDef } = {};
54
- for (const [cmd, def] of commandHook.buildAllCommands()) {
55
- allCommands[cmd] = {
56
- name: def.name,
57
- contexts: def.contexts,
58
- priority: def.priority,
59
- key: def.key,
60
- mac: def.mac,
61
- hide: def.hide,
62
- requireMode: def.requireMode,
63
- requireEditor: def.requireEditor,
64
- menu: def.menu,
65
- menuMac: def.menuMac,
66
- menuWindows: def.menuWindows,
67
- menuLinux: def.menuLinux,
68
- };
69
- }
70
- return allCommands;
71
- },
72
- "system.listSyscalls": (): SyscallMeta[] => {
73
- const syscalls: SyscallMeta[] = [];
74
- for (const [name, info] of client.clientSystem.system
75
- .registeredSyscalls) {
76
- syscalls.push({
77
- name,
78
- requiredPermissions: info.requiredPermissions,
79
- argCount: Math.max(0, info.callback.length - 1),
80
- });
81
- }
82
- return syscalls;
83
- },
84
- "system.reloadPlugs": () => {
85
- if (!client) {
86
- throw new Error("Not supported");
87
- }
88
- return client.loadPlugs();
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
- },
98
- "system.reloadConfig": (): Record<string, any> => {
99
- console.warn("system.reloadConfig is deprecated, it's now a no-op");
100
- return client.config.values;
101
- },
102
- "system.loadSpaceScripts": async () => {
103
- console.warn("DEPRECATED: used system.loadScripts instead");
104
- await client.clientSystem.loadLuaScripts();
105
- },
106
- "system.loadScripts": async () => {
107
- await client.clientSystem.loadLuaScripts();
108
- },
109
- "system.loadSpaceStyles": async () => {
110
- if (!client) {
111
- throw new Error("Not supported on server");
112
- }
113
- await client.loadCustomStyles();
114
- },
115
- "system.wipeClient": async (_ctx, logout = false) => {
116
- await client.wipeClient();
117
- if (logout) {
118
- location.href = ".logout";
119
- } else {
120
- alert("Client wiped, feel free to navigate elsewhere");
121
- }
12
+ "system.invokeFunction": {
13
+ callback: (
14
+ _ctx,
15
+ fullName: string, // plug.function
16
+ ...args: any[]
17
+ ) => {
18
+ const [plugName, functionName] = fullName.split(".");
19
+ if (!plugName || !functionName) {
20
+ throw Error(`Invalid function name ${fullName}`);
21
+ }
22
+ const plug = client.clientSystem.system.loadedPlugs.get(plugName);
23
+ if (!plug) {
24
+ throw Error(`Plug ${plugName} not found`);
25
+ }
26
+ const functionDef = plug.manifest!.functions[functionName];
27
+ if (!functionDef) {
28
+ throw Error(`Function ${functionName} not found`);
29
+ }
30
+ return plug.invoke(functionName, args);
31
+ },
32
+ description: "Invokes a loaded plug function by its plug-qualified name.",
33
+ signatures: ["system.invokeFunction(name, ...)"],
34
+ },
35
+ "system.invokeFunctionOnServer": {
36
+ callback: (
37
+ ctx,
38
+ fullName: string, // plug.function
39
+ ...args: any[]
40
+ ) => {
41
+ console.warn(
42
+ "Calling deprecated system.invokeFunctionOnServer, use system.invokeFunction instead",
43
+ );
44
+ const registration = api["system.invokeFunction"];
45
+ const invokeFunction =
46
+ typeof registration === "function"
47
+ ? registration
48
+ : registration.callback;
49
+ return invokeFunction(ctx, fullName, ...args);
50
+ },
51
+ description: "Deprecated alias for system.invokeFunction.",
52
+ deprecated: "Use system.invokeFunction instead.",
53
+ signatures: ["system.invokeFunctionOnServer(name, ...)"],
54
+ },
55
+ "system.serverSyscall": {
56
+ callback: (_ctx, name: string, ...args: any[]) => {
57
+ console.warn(
58
+ "Calling deprecated system.serverSyscall, use syscall instead",
59
+ );
60
+ return client.clientSystem.localSyscall(name, args);
61
+ },
62
+ description: "Deprecated helper for invoking a named syscall.",
63
+ deprecated: "Invoke the target syscall directly instead.",
64
+ signatures: ["system.serverSyscall(name, ...)"],
65
+ },
66
+ "system.invokeCommand": {
67
+ callback: (_ctx, name: string, args?: string[]) => {
68
+ console.warn("Deprecated, use editor.invokeCommand instead");
69
+ return client.runCommandByName(name, args);
70
+ },
71
+ description: "Deprecated alias for editor.invokeCommand.",
72
+ deprecated: "Use editor.invokeCommand instead.",
73
+ signatures: ["system.invokeCommand(name, args?)"],
74
+ },
75
+ "system.listCommands": {
76
+ callback: (): { [key: string]: CommandDef } => {
77
+ const commandHook = client.clientSystem.commandHook;
78
+ const allCommands: { [key: string]: CommandDef } = {};
79
+ for (const [cmd, def] of commandHook.buildAllCommands()) {
80
+ allCommands[cmd] = {
81
+ name: def.name,
82
+ contexts: def.contexts,
83
+ priority: def.priority,
84
+ key: def.key,
85
+ mac: def.mac,
86
+ hide: def.hide,
87
+ requireMode: def.requireMode,
88
+ requireEditor: def.requireEditor,
89
+ menu: def.menu,
90
+ menuMac: def.menuMac,
91
+ menuWindows: def.menuWindows,
92
+ menuLinux: def.menuLinux,
93
+ };
94
+ }
95
+ return allCommands;
96
+ },
97
+ description:
98
+ "Returns a map of every currently available command definition.",
99
+ },
100
+ "system.listSyscalls": {
101
+ callback: (): SyscallMeta[] => {
102
+ const syscalls: SyscallMeta[] = [];
103
+ for (const [name, info] of client.clientSystem.system
104
+ .registeredSyscalls) {
105
+ const { callback, requiredPermissions, ...metadata } = info;
106
+ syscalls.push({
107
+ name,
108
+ requiredPermissions,
109
+ argCount: Math.max(0, callback.length - 1),
110
+ ...metadata,
111
+ });
112
+ }
113
+ return syscalls;
114
+ },
115
+ description:
116
+ "Lists registered syscalls with permissions, argument counts, and documentation metadata.",
117
+ },
118
+ "system.reloadPlugs": {
119
+ callback: () => {
120
+ if (!client) {
121
+ throw new Error("Not supported");
122
+ }
123
+ return client.loadPlugs();
124
+ },
125
+ description: "Reloads every plug available to the client.",
126
+ },
127
+ "system.reboot": {
128
+ callback: async () => {
129
+ await client.save(true);
130
+ await client.eventedSpacePrimitives.fetchFileListWhenIdle();
131
+ await client.mq.awaitEmptyQueue("indexQueue");
132
+ await client.clientSystem.reloadState();
133
+ },
134
+ description:
135
+ "Saves the current editor buffer, detects on-disk changes, waits for indexing, and reloads configuration, scripts, styles, and client state. Because the buffer is saved first, an external edit to the currently open page can be overwritten; edit that page through the editor or navigate away first.",
136
+ },
137
+ "system.loadPlug": {
138
+ callback: async (_ctx, path: string) => {
139
+ const meta = await client.space.spacePrimitives.getFileMeta(path);
140
+ await client.clientSystem.loadPlugFromPath(path, meta.lastModified);
141
+ await client.dispatchAppEvent("plugs:loaded");
142
+ },
143
+ description: "Loads or reloads one plug from a space file path.",
144
+ signatures: ["system.loadPlug(path)"],
145
+ },
146
+ "system.unloadPlug": {
147
+ callback: (_ctx, path: string) =>
148
+ client.clientSystem.system.unloadByPath(path),
149
+ description: "Unloads the plug loaded from a space file path.",
150
+ signatures: ["system.unloadPlug(path)"],
151
+ },
152
+ "system.reloadConfig": {
153
+ callback: (): Record<string, any> => {
154
+ console.warn("system.reloadConfig is deprecated, it's now a no-op");
155
+ return client.config.values;
156
+ },
157
+ description: "Deprecated no-op that returns the current configuration.",
158
+ deprecated:
159
+ "Configuration reloads automatically; use system.reboot when needed.",
160
+ },
161
+ "system.loadSpaceScripts": {
162
+ callback: async () => {
163
+ console.warn("DEPRECATED: used system.loadScripts instead");
164
+ await client.clientSystem.loadLuaScripts();
165
+ },
166
+ description: "Deprecated alias for system.loadScripts.",
167
+ deprecated: "Use system.loadScripts instead.",
168
+ },
169
+ "system.loadScripts": {
170
+ callback: async () => {
171
+ await client.clientSystem.loadLuaScripts();
172
+ },
173
+ description: "Reloads Space Lua scripts and configuration.",
174
+ },
175
+ "system.loadSpaceStyles": {
176
+ callback: async () => {
177
+ if (!client) {
178
+ throw new Error("Not supported on server");
179
+ }
180
+ await client.loadCustomStyles();
181
+ },
182
+ description: "Reloads custom Space Style definitions.",
183
+ },
184
+ "system.wipeClient": {
185
+ callback: async (_ctx, logout = false) => {
186
+ await client.wipeClient();
187
+ if (logout) {
188
+ location.href = ".logout";
189
+ } else {
190
+ alert("Client wiped, feel free to navigate elsewhere");
191
+ }
192
+ },
193
+ description:
194
+ "Wipes local client state, cached files, databases, and optionally the login session.",
195
+ signatures: ["system.wipeClient(logout?)"],
122
196
  },
123
197
  // DEPRECATED
124
- "system.cleanDatabases": (): boolean => {
125
- console.warn(
126
- "system.cleanDatabses is deprecated, use Client: Wipe instead",
127
- );
128
- return false;
198
+ "system.cleanDatabases": {
199
+ callback: (): boolean => {
200
+ console.warn(
201
+ "system.cleanDatabses is deprecated, use Client: Wipe instead",
202
+ );
203
+ return false;
204
+ },
205
+ description: "Deprecated no-op retained for compatibility.",
206
+ deprecated: "Use system.wipeClient or the Client: Wipe command instead.",
129
207
  },
130
- "system.getMode": () => {
131
- return readOnlyMode ? "ro" : "rw";
208
+ "system.getMode": {
209
+ callback: () => (readOnlyMode ? "ro" : "rw"),
210
+ description: "Returns rw for read-write mode or ro for read-only mode.",
132
211
  },
133
- "system.getURLPrefix": () => {
134
- const url = new URL(document.baseURI);
135
-
136
- return url.pathname;
212
+ "system.getURLPrefix": {
213
+ callback: () => {
214
+ const url = new URL(document.baseURI);
215
+ return url.pathname;
216
+ },
217
+ description:
218
+ "Returns the configured URL path prefix for this SilverBullet instance.",
137
219
  },
138
- "system.getBaseURI": () => {
139
- return document.baseURI;
220
+ "system.getBaseURI": {
221
+ callback: () => document.baseURI,
222
+ description:
223
+ "Returns the browser base URI for this SilverBullet instance.",
140
224
  },
141
- "system.getVersion": () => {
142
- return publicVersion;
225
+ "system.getVersion": {
226
+ callback: () => publicVersion,
227
+ description: "Returns the running SilverBullet version.",
143
228
  },
144
- "system.getConfig": (_ctx, key: string, defaultValue: any = undefined) => {
145
- return client.config.get(key, defaultValue);
229
+ "system.getConfig": {
230
+ callback: (_ctx, key: string, defaultValue: any = undefined) =>
231
+ client.config.get(key, defaultValue),
232
+ description: "Returns a configuration value, with an optional default.",
233
+ signatures: ["system.getConfig(key, defaultValue?)"],
146
234
  },
147
235
  // DEPRECATED
148
- "system.getEnv": () => {
149
- console.warn(
150
- "system.getEnv is deprecated, you can assume the env to always be the client",
151
- );
152
- return null;
153
- },
154
- "system.getSpaceConfig": (_ctx, key, defaultValue?) => {
155
- console.warn(
156
- "system.getSpaceConfig is deprecated, use system.getConfig instead",
157
- );
158
- return client.config.get(key, defaultValue);
236
+ "system.getEnv": {
237
+ callback: () => {
238
+ console.warn(
239
+ "system.getEnv is deprecated, you can assume the env to always be the client",
240
+ );
241
+ return null;
242
+ },
243
+ description: "Deprecated environment probe that always returns nil.",
244
+ deprecated: "The environment is always the client.",
245
+ },
246
+ "system.getSpaceConfig": {
247
+ callback: (_ctx, key, defaultValue?) => {
248
+ console.warn(
249
+ "system.getSpaceConfig is deprecated, use system.getConfig instead",
250
+ );
251
+ return client.config.get(key, defaultValue);
252
+ },
253
+ description: "Deprecated alias for system.getConfig.",
254
+ deprecated: "Use system.getConfig instead.",
255
+ signatures: ["system.getSpaceConfig(key, defaultValue?)"],
159
256
  },
160
257
  };
161
258
  return api;
@@ -7,9 +7,10 @@ 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]: (ctx: SyscallContext, ...args: any) => Promise<any> | any;
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 Syscall = {
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, Syscall>();
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
 
@@ -68,10 +73,21 @@ export class System<HookT> extends EventEmitter<SystemEvents<HookT>> {
68
73
  ...registrationObjects: SysCallMapping[]
69
74
  ) {
70
75
  for (const registrationObject of registrationObjects) {
71
- for (const [name, callback] of Object.entries(registrationObject)) {
76
+ for (const [name, registration] of Object.entries(registrationObject)) {
77
+ const definition =
78
+ typeof registration === "function"
79
+ ? { callback: registration }
80
+ : registration;
81
+ const cleanName = name.startsWith("lua:")
82
+ ? name.slice("lua:".length)
83
+ : name;
84
+ const namespace = cleanName.split(".")[0];
85
+ const { callback, ...metadata } = definition;
72
86
  this.registeredSyscalls.set(name, {
73
87
  requiredPermissions: requiredCapabilities,
74
88
  callback,
89
+ see: `API/${namespace}`,
90
+ ...metadata,
75
91
  });
76
92
  }
77
93
  }
@@ -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
+ }
@@ -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,6 +30,14 @@ export type LuaBlock = {
26
30
  hasFunctionDef?: boolean;
27
31
  } & ASTContext;
28
32
 
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
+
29
41
  // STATEMENTS
30
42
  export type LuaReturnStatement = {
31
43
  type: "Return";
@@ -128,6 +140,7 @@ export type LuaFunctionBody = {
128
140
  type: "FunctionBody";
129
141
  parameters: string[];
130
142
  block: LuaBlock;
143
+ documentation?: LuaFunctionDocumentation;
131
144
  } & ASTContext;
132
145
 
133
146
  export type LuaAssignmentStatement = {
@@ -1015,7 +1015,8 @@ export function evalExpression(
1015
1015
  case "Variable":
1016
1016
  case "FunctionCall":
1017
1017
  case "TableAccess":
1018
- case "PropertyAccess": {
1018
+ case "PropertyAccess":
1019
+ case "Parenthesized": {
1019
1020
  return evalPrefixExpression(e, env, sf);
1020
1021
  }
1021
1022
  case "TableConstructor": {
@@ -1318,7 +1319,7 @@ function evalPrefixExpression(
1318
1319
 
1319
1320
  case "Parenthesized": {
1320
1321
  const p = asParenthesized(e);
1321
- return evalExpression(p.expression, env, sf);
1322
+ return rpThen(evalExpression(p.expression, env, sf), singleResult);
1322
1323
  }
1323
1324
 
1324
1325
  // <<expr>>[<<expr>>]
@@ -2063,6 +2064,12 @@ export function evalStatement(
2063
2064
  let errSf: LuaStackFrame | undefined;
2064
2065
  const ps: Promise<any>[] = [];
2065
2066
  for (let i = 0; i < lvalues.length; i++) {
2067
+ if (
2068
+ values[i] instanceof LuaFunction &&
2069
+ typeof lvalues[i].key === "string"
2070
+ ) {
2071
+ values[i].info.name ??= lvalues[i].key;
2072
+ }
2066
2073
  const r = luaSet(
2067
2074
  lvalues[i].env,
2068
2075
  lvalues[i].key,
@@ -2115,6 +2122,7 @@ export function evalStatement(
2115
2122
  }
2116
2123
 
2117
2124
  const bindOne = (name: any, v: LuaValue) => {
2125
+ if (v instanceof LuaFunction) v.info.name ??= name.name;
2118
2126
  const isConst = name.attributes?.includes(LuaAttribute.Const) === true;
2119
2127
  const isClose = name.attributes?.includes(LuaAttribute.Close) === true;
2120
2128
 
@@ -2386,13 +2394,13 @@ export function evalStatement(
2386
2394
  }
2387
2395
  (settable as any).set(
2388
2396
  propNames[propNames.length - 1],
2389
- new LuaFunction(body, env),
2397
+ new LuaFunction(body, env, { name: propNames.join(".") }),
2390
2398
  );
2391
2399
  return;
2392
2400
  }
2393
2401
  case "LocalFunction": {
2394
2402
  const lf = asLocalFunction(s);
2395
- env.setLocal(lf.name, new LuaFunction(lf.body, env));
2403
+ env.setLocal(lf.name, new LuaFunction(lf.body, env, { name: lf.name }));
2396
2404
  return;
2397
2405
  }
2398
2406
  case "Return": {