@silverbulletmd/silverbullet 2.8.1 → 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 (65) hide show
  1. package/README.md +42 -25
  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 -700
  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 +243 -114
  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 +254 -119
  28. package/client/space_lua/pretty_print.ts +465 -0
  29. package/client/space_lua/query_collection.ts +3 -3
  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 +26 -17
  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 +397 -13
  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 +336 -94
  44. package/client/space_lua/syscalls.ts +270 -0
  45. package/dist/plug-compile.js +1 -1
  46. package/package.json +12 -4
  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 +23 -2
  51. package/plug-api/syscalls/index.ts +24 -1
  52. package/plug-api/syscalls/jsonschema.ts +9 -0
  53. package/plug-api/syscalls/lua.ts +28 -1
  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/config.ts +1 -1
  58. package/plug-api/types/index.ts +52 -1
  59. package/plug-api/types/manifest.ts +22 -4
  60. package/plug-api/ui/cx.ts +4 -0
  61. package/plug-api/ui/index.ts +22 -0
  62. package/plug-api/ui/panel_styles.ts +33 -0
  63. package/plug-api/ui/slugify.ts +44 -0
  64. package/plugs/builtin_plugs.ts +1 -0
  65. package/client/plugos/syscalls/lua.ts +0 -58
@@ -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 parseBlock(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 {
@@ -636,7 +848,7 @@ function exprCapturesNames(e: LuaExpression, names: Set<string>): boolean {
636
848
  }
637
849
  }
638
850
 
639
- function parseBlock(t: ParseTree, ctx: ASTCtx): LuaBlock {
851
+ function parseBlockNode(t: ParseTree, ctx: ASTCtx): LuaBlock {
640
852
  if (t.type !== "Block") {
641
853
  throw new Error(`Expected Block, got ${t.type}`);
642
854
  }
@@ -846,20 +1058,20 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
846
1058
  ctx: context(t, ctx),
847
1059
  };
848
1060
  case "Scope":
849
- return parseBlock(t.children![1], ctx);
1061
+ return parseBlockNode(t.children![1], ctx);
850
1062
  case ";":
851
1063
  return { type: "Semicolon", ctx: context(t, ctx) };
852
1064
  case "WhileStatement":
853
1065
  return {
854
1066
  type: "While",
855
1067
  condition: parseExpression(t.children![1], ctx),
856
- block: parseBlock(t.children![3], ctx),
1068
+ block: parseBlockNode(t.children![3], ctx),
857
1069
  ctx: context(t, ctx),
858
1070
  };
859
1071
  case "RepeatStatement":
860
1072
  return {
861
1073
  type: "Repeat",
862
- block: parseBlock(t.children![1], ctx),
1074
+ block: parseBlockNode(t.children![1], ctx),
863
1075
  condition: parseExpression(t.children![3], ctx),
864
1076
  ctx: context(t, ctx),
865
1077
  };
@@ -880,12 +1092,12 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
880
1092
  if (token === "if" || token === "elseif") {
881
1093
  conditions.push({
882
1094
  condition: parseExpression(t.children![i + 1], ctx),
883
- block: parseBlock(t.children![i + 3], ctx),
1095
+ block: parseBlockNode(t.children![i + 3], ctx),
884
1096
  from: child.from,
885
1097
  to: child.to,
886
1098
  });
887
1099
  } else if (token === "else") {
888
- elseBlock = parseBlock(t.children![i + 1], ctx);
1100
+ elseBlock = parseBlockNode(t.children![i + 1], ctx);
889
1101
  } else if (token === "end") {
890
1102
  break;
891
1103
  } else {
@@ -903,7 +1115,7 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
903
1115
  if (t.children![1].type === "ForNumeric") {
904
1116
  const forNumeric = t.children![1];
905
1117
  const name = forNumeric.children![0].children![0].text!;
906
- const block = parseBlock(t.children![3], ctx);
1118
+ const block = parseBlockNode(t.children![3], ctx);
907
1119
  const node: LuaStatement = {
908
1120
  type: "For",
909
1121
  name,
@@ -923,7 +1135,7 @@ function parseStatement(t: ParseTree, ctx: ASTCtx): LuaStatement {
923
1135
  }
924
1136
  const forGeneric = t.children![1];
925
1137
  const names = parseNameList(forGeneric.children![0]);
926
- const block = parseBlock(t.children![3], ctx);
1138
+ const block = parseBlockNode(t.children![3], ctx);
927
1139
  const node: LuaStatement = {
928
1140
  type: "ForIn",
929
1141
  names,
@@ -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
  }
@@ -1512,7 +1728,7 @@ function parseFunctionBody(t: ParseTree, ctx: ASTCtx): LuaFunctionBody {
1512
1728
  (c) => c.type && ["Name", "Ellipsis"].includes(c.type),
1513
1729
  )
1514
1730
  .map((c) => c.children![0].text!),
1515
- block: parseBlock(t.children![3], ctx),
1731
+ block: parseBlockNode(t.children![3], ctx),
1516
1732
  ctx: context(t, ctx),
1517
1733
  };
1518
1734
  }
@@ -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
- export function parse(s: string, ctx: ASTCtx = {}): LuaBlock {
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 {
@@ -1743,6 +1878,6 @@ function luaUnexpectedSymbolMessage(src: string, from: number): string {
1743
1878
  * Helper function to parse a Lua expression string
1744
1879
  */
1745
1880
  export function parseExpressionString(expr: string): LuaExpression {
1746
- const parsedLua = parse(`_(${expr})`) as LuaBlock;
1881
+ const parsedLua = parseBlock(`_(${expr})`) as LuaBlock;
1747
1882
  return (parsedLua.statements[0] as LuaFunctionCallStatement).call.args[0];
1748
1883
  }