@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,8 +1,8 @@
1
1
  import { lezerToParseTree } from "../../client/markdown_parser/parse_tree.ts";
2
2
  import type { SyntaxNode } from "@lezer/common";
3
3
  import {
4
- cleanTree,
5
4
  type ParseTree,
5
+ renderToText,
6
6
  } from "@silverbulletmd/silverbullet/lib/tree";
7
7
  // @ts-expect-error - Local generated JavaScript file without type definitions
8
8
  import { parser } from "./parse-lua.js";
@@ -12,6 +12,7 @@ import type {
12
12
  ASTCtx,
13
13
  LuaAttName,
14
14
  LuaBlock,
15
+ LuaComment,
15
16
  LuaExpression,
16
17
  LuaFunctionBody,
17
18
  LuaFunctionCallExpression,
@@ -25,6 +26,7 @@ import type {
25
26
  LuaStatement,
26
27
  LuaTableField,
27
28
  } from "./ast.ts";
29
+ import type { LuaFunctionDocumentation } from "../../plug-api/types/index.ts";
28
30
  import { LuaAttribute } from "./ast.ts";
29
31
  import { getBlockGotoMeta } from "./labels.ts";
30
32
  import { LuaRuntimeError, LuaStackFrame } from "./runtime.ts";
@@ -67,7 +69,217 @@ function parseChunk(t: ParseTree, ctx: ASTCtx): LuaBlock {
67
69
  if (t.type !== "Chunk") {
68
70
  throw new Error(`Expected Chunk, got ${t.type}`);
69
71
  }
70
- return parseBlockNode(t.children![0], ctx);
72
+ const block = t.children?.find((child) => child.type === "Block");
73
+ if (!block) {
74
+ return { type: "Block", statements: [], ctx: context(t, ctx) };
75
+ }
76
+ return parseBlockNode(block, ctx);
77
+ }
78
+
79
+ function collectLuaComments(t: ParseTree, ctx: ASTCtx): LuaComment[] {
80
+ const comments: LuaComment[] = [];
81
+ const visit = (node: ParseTree) => {
82
+ if (node.type === "Comment") {
83
+ const text = renderToText(node);
84
+ comments.push({
85
+ type: "Comment",
86
+ text,
87
+ kind: /^--\[(=*)\[/.test(text) ? "long" : "line",
88
+ ctx: context(node, ctx),
89
+ });
90
+ return;
91
+ }
92
+ for (const child of node.children ?? []) visit(child);
93
+ };
94
+ visit(t);
95
+ return comments.sort((a, b) => (a.ctx.from ?? 0) - (b.ctx.from ?? 0));
96
+ }
97
+
98
+ function withoutComments(t: ParseTree): ParseTree {
99
+ if (!t.children) return t;
100
+ return {
101
+ ...t,
102
+ children: t.children
103
+ .filter((child) => child.type !== "Comment")
104
+ .map(withoutComments),
105
+ };
106
+ }
107
+
108
+ function assignCommentsToBlocks(
109
+ root: LuaBlock,
110
+ comments: LuaComment[],
111
+ source: string,
112
+ ): void {
113
+ const blocks: LuaBlock[] = [];
114
+ const seen = new WeakSet<object>();
115
+ const visit = (value: unknown, key?: string) => {
116
+ if (!value || typeof value !== "object" || key === "ctx") return;
117
+ if (seen.has(value)) return;
118
+ seen.add(value);
119
+ if ((value as { type?: string }).type === "Block") {
120
+ blocks.push(value as LuaBlock);
121
+ }
122
+ for (const [childKey, child] of Object.entries(value)) {
123
+ if (
124
+ childKey !== "ctx" &&
125
+ childKey !== "comments" &&
126
+ childKey !== "documentation"
127
+ ) {
128
+ visit(child, childKey);
129
+ }
130
+ }
131
+ };
132
+ visit(root);
133
+
134
+ for (const comment of comments) {
135
+ const from = comment.ctx.from ?? 0;
136
+ const to = comment.ctx.to ?? from;
137
+ let owner = root;
138
+ let ownerSpan = Number.POSITIVE_INFINITY;
139
+ for (const block of blocks) {
140
+ if (block === root) continue;
141
+ const blockFrom = block.ctx.from;
142
+ const blockTo = block.ctx.to;
143
+ if (blockFrom === undefined || blockTo === undefined) continue;
144
+ const contains = blockFrom <= from && blockTo >= to;
145
+ const immediatelyBefore =
146
+ to <= blockFrom && !source.slice(to, blockFrom).trim();
147
+ const immediatelyAfter =
148
+ blockTo <= from && !source.slice(blockTo, from).trim();
149
+ if (
150
+ (contains || immediatelyBefore || immediatelyAfter) &&
151
+ blockTo - blockFrom < ownerSpan
152
+ ) {
153
+ owner = block;
154
+ ownerSpan = blockTo - blockFrom;
155
+ }
156
+ }
157
+ (owner.comments ??= []).push(comment);
158
+ }
159
+ }
160
+
161
+ function parseFunctionDocumentation(
162
+ comments: LuaComment[],
163
+ ): LuaFunctionDocumentation | undefined {
164
+ const description: string[] = [];
165
+ const parameters: NonNullable<LuaFunctionDocumentation["parameters"]> = [];
166
+ const returns: NonNullable<LuaFunctionDocumentation["returns"]> = [];
167
+ let deprecated: string | boolean | undefined;
168
+ let see: string | undefined;
169
+
170
+ for (const comment of comments) {
171
+ const line = comment.text.slice(3).replace(/^\s?/, "");
172
+ const param = /^@param\s+(\S+)\s+(\S+)(?:\s+(.*))?$/.exec(line);
173
+ if (param) {
174
+ const optional = param[1].endsWith("?");
175
+ parameters.push({
176
+ name: optional ? param[1].slice(0, -1) : param[1],
177
+ type: param[2],
178
+ description: param[3] || undefined,
179
+ optional,
180
+ });
181
+ continue;
182
+ }
183
+ const returnDoc = /^@return\s+(\S+)(?:\s+(.*))?$/.exec(line);
184
+ if (returnDoc) {
185
+ returns.push({
186
+ type: returnDoc[1],
187
+ description: returnDoc[2] || undefined,
188
+ });
189
+ continue;
190
+ }
191
+ const deprecatedDoc = /^@deprecated(?:\s+(.*))?$/.exec(line);
192
+ if (deprecatedDoc) {
193
+ deprecated = deprecatedDoc[1] || true;
194
+ continue;
195
+ }
196
+ const seeDoc = /^@see\s+(.+)$/.exec(line);
197
+ if (seeDoc) {
198
+ see = seeDoc[1];
199
+ continue;
200
+ }
201
+ if (!line.startsWith("@")) description.push(line);
202
+ }
203
+
204
+ const docs: LuaFunctionDocumentation = {};
205
+ const descriptionText = description.join("\n").trim();
206
+ if (descriptionText) docs.description = descriptionText;
207
+ if (parameters.length) docs.parameters = parameters;
208
+ if (returns.length) docs.returns = returns;
209
+ if (deprecated !== undefined) docs.deprecated = deprecated;
210
+ if (see) docs.see = see;
211
+ return Object.keys(docs).length ? docs : undefined;
212
+ }
213
+
214
+ function attachFunctionDocumentation(
215
+ root: LuaBlock,
216
+ comments: LuaComment[],
217
+ source: string,
218
+ ): void {
219
+ const candidates: { from: number; body: LuaFunctionBody }[] = [];
220
+ const seen = new WeakSet<object>();
221
+ const claimedBodies = new WeakSet<LuaFunctionBody>();
222
+
223
+ const addCandidate = (from: number | undefined, body: LuaFunctionBody) => {
224
+ if (from === undefined || claimedBodies.has(body)) return;
225
+ claimedBodies.add(body);
226
+ candidates.push({ from, body });
227
+ };
228
+
229
+ const visit = (value: unknown, key?: string) => {
230
+ if (!value || typeof value !== "object" || key === "ctx") return;
231
+ if (seen.has(value)) return;
232
+ seen.add(value);
233
+ const node = value as Record<string, any>;
234
+ if (node.type === "Function" || node.type === "LocalFunction") {
235
+ addCandidate(node.ctx?.from, node.body);
236
+ } else if (
237
+ (node.type === "Local" || node.type === "Assignment") &&
238
+ node.expressions?.length === 1 &&
239
+ node.expressions[0]?.type === "FunctionDefinition"
240
+ ) {
241
+ addCandidate(node.ctx?.from, node.expressions[0].body);
242
+ } else if (node.type === "FunctionDefinition") {
243
+ addCandidate(node.ctx?.from, node.body);
244
+ }
245
+ for (const [childKey, child] of Object.entries(node)) {
246
+ if (
247
+ childKey !== "ctx" &&
248
+ childKey !== "comments" &&
249
+ childKey !== "documentation"
250
+ ) {
251
+ visit(child, childKey);
252
+ }
253
+ }
254
+ };
255
+ visit(root);
256
+
257
+ const docComments = comments.filter(
258
+ (comment) => comment.kind === "line" && comment.text.startsWith("---"),
259
+ );
260
+ for (const candidate of candidates) {
261
+ const group: LuaComment[] = [];
262
+ let boundary = candidate.from;
263
+ for (let i = docComments.length - 1; i >= 0; i--) {
264
+ const comment = docComments[i];
265
+ const from = comment.ctx.from ?? 0;
266
+ const to = comment.ctx.to ?? from;
267
+ if (to > boundary) continue;
268
+ const gap = source.slice(to, boundary);
269
+ const lineStart = source.lastIndexOf("\n", from - 1) + 1;
270
+ if (
271
+ gap.trim() ||
272
+ /\r?\n[ \t]*\r?\n/.test(gap) ||
273
+ source.slice(lineStart, from).trim()
274
+ ) {
275
+ break;
276
+ }
277
+ group.unshift(comment);
278
+ boundary = from;
279
+ }
280
+ const documentation = parseFunctionDocumentation(group);
281
+ if (documentation) candidate.body.documentation = documentation;
282
+ }
71
283
  }
72
284
 
73
285
  function hasCloseLocal(names: LuaAttName[] | undefined): boolean {
@@ -1269,7 +1481,11 @@ function parseExpression(t: ParseTree, ctx: ASTCtx): LuaExpression {
1269
1481
  };
1270
1482
 
1271
1483
  case "Parens":
1272
- return parseExpression(t.children![1], ctx);
1484
+ return {
1485
+ type: "Parenthesized",
1486
+ expression: parseExpression(t.children![1], ctx),
1487
+ ctx: context(t, ctx),
1488
+ };
1273
1489
  case "FunctionCall": {
1274
1490
  return parseFunctionCall(t, ctx);
1275
1491
  }
@@ -1585,112 +1801,13 @@ function parseTableField(t: ParseTree, ctx: ASTCtx): LuaTableField {
1585
1801
  }
1586
1802
  }
1587
1803
 
1588
- export function stripLuaComments(s: string): string {
1589
- let result = "";
1590
- let i = 0;
1591
-
1592
- while (i < s.length) {
1593
- // Check for long string
1594
- if (s[i] === "[") {
1595
- let j = i + 1;
1596
- let equalsCount = 0;
1597
- while (s[j] === "=") {
1598
- equalsCount++;
1599
- j++;
1600
- }
1601
- if (s[j] === "[") {
1602
- // Found long string start
1603
- const openBracket = s.substring(i, j + 1);
1604
- const closeBracket = `]${"=".repeat(equalsCount)}]`;
1605
- result += openBracket;
1606
- i = j + 1;
1607
-
1608
- // Find matching closing bracket
1609
- const content = s.substring(i);
1610
- const closeIndex = content.indexOf(closeBracket);
1611
- if (closeIndex !== -1) {
1612
- // Copy string content verbatim, including any comment-like sequences
1613
- result += content.substring(0, closeIndex) + closeBracket;
1614
- i += closeIndex + closeBracket.length;
1615
- continue;
1616
- }
1617
- }
1618
- }
1619
-
1620
- // Check for single quoted string
1621
- if (s[i] === '"' || s[i] === "'") {
1622
- const quote = s[i];
1623
- result += quote;
1624
- i++;
1625
- while (i < s.length && s[i] !== quote) {
1626
- if (s[i] === "\\") {
1627
- result += s[i] + s[i + 1];
1628
- i += 2;
1629
- } else {
1630
- result += s[i];
1631
- i++;
1632
- }
1633
- }
1634
- if (i < s.length) {
1635
- result += s[i]; // closing quote
1636
- i++;
1637
- }
1638
- continue;
1639
- }
1640
-
1641
- // Check for comments
1642
- if (s[i] === "-" && s[i + 1] === "-") {
1643
- // Replace the -- with spaces
1644
- result += " ";
1645
- i += 2;
1646
-
1647
- // Check for long comment
1648
- if (s[i] === "[") {
1649
- let j = i + 1;
1650
- let equalsCount = 0;
1651
- while (s[j] === "=") {
1652
- equalsCount++;
1653
- j++;
1654
- }
1655
- if (s[j] === "[") {
1656
- // Found long comment start
1657
- const closeBracket = `]${"=".repeat(equalsCount)}]`;
1658
- // Replace opening bracket with spaces
1659
- result += " ".repeat(j - i + 1);
1660
- i = j + 1;
1661
-
1662
- // Find matching closing bracket
1663
- const content = s.substring(i);
1664
- const closeIndex = content.indexOf(closeBracket);
1665
- if (closeIndex !== -1) {
1666
- // Replace comment content and closing bracket with spaces
1667
- result += " ".repeat(closeIndex) + " ".repeat(closeBracket.length);
1668
- i += closeIndex + closeBracket.length;
1669
- continue;
1670
- }
1671
- }
1672
- }
1673
-
1674
- // Single line comment - replace rest of line with spaces
1675
- while (i < s.length && s[i] !== "\n") {
1676
- result += " ";
1677
- i++;
1678
- }
1679
- continue;
1680
- }
1681
-
1682
- result += s[i];
1683
- i++;
1684
- }
1685
-
1686
- return result;
1687
- }
1688
-
1689
1804
  export function parseBlock(s: string, ctx: ASTCtx = {}): LuaBlock {
1690
1805
  try {
1691
- const t = parseToAST(stripLuaComments(s));
1692
- // console.log("Clean tree", JSON.stringify(t, null, 2));
1693
- const result = parseChunk(t, ctx);
1806
+ const concreteTree = parseToAST(s);
1807
+ const comments = collectLuaComments(concreteTree, ctx);
1808
+ const result = parseChunk(withoutComments(concreteTree), ctx);
1809
+ assignCommentsToBlocks(result, comments, s);
1810
+ attachFunctionDocumentation(result, comments, s);
1694
1811
  // console.log("Parsed AST", JSON.stringify(result, null, 2));
1695
1812
  getBlockGotoMeta(result);
1696
1813
  return result;
@@ -1716,7 +1833,25 @@ export function parseToAST(t: string): ParseTree {
1716
1833
  }
1717
1834
 
1718
1835
  const n = lezerToParseTree(t, tree.topNode);
1719
- return cleanTree(n, true);
1836
+ return cleanLuaTree(n);
1837
+ }
1838
+
1839
+ function cleanLuaTree(tree: ParseTree): ParseTree {
1840
+ if (tree.type === "⚠") {
1841
+ throw new Error(`Parse error at pos ${tree.from}`);
1842
+ }
1843
+ if (tree.text !== undefined) return tree;
1844
+ const result: ParseTree = {
1845
+ type: tree.type,
1846
+ children: [],
1847
+ from: tree.from,
1848
+ to: tree.to,
1849
+ };
1850
+ for (const node of tree.children ?? []) {
1851
+ if (node.type) result.children!.push(cleanLuaTree(node));
1852
+ if (node.text?.trim()) result.children!.push(node);
1853
+ }
1854
+ return result;
1720
1855
  }
1721
1856
 
1722
1857
  function findFirstParseError(node: SyntaxNode): SyntaxNode | null {