@silverbulletmd/silverbullet 2.6.1 → 2.8.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 (37) hide show
  1. package/README.md +0 -3
  2. package/client/markdown_parser/constants.ts +1 -0
  3. package/client/plugos/hooks/command.ts +46 -16
  4. package/client/plugos/hooks/slash_command.ts +3 -1
  5. package/client/plugos/plug.ts +5 -2
  6. package/client/plugos/plug_compile.ts +52 -2
  7. package/client/plugos/sandboxes/worker_sandbox.ts +11 -1
  8. package/client/plugos/syscalls/client_code_widget.ts +3 -1
  9. package/client/plugos/syscalls/config.ts +9 -2
  10. package/client/plugos/syscalls/editor.ts +147 -0
  11. package/client/plugos/syscalls/index.ts +8 -1
  12. package/client/plugos/syscalls/jsonschema.ts +1 -19
  13. package/client/plugos/syscalls/system.ts +13 -0
  14. package/client/plugos/system.ts +14 -10
  15. package/client/plugos/types.ts +25 -0
  16. package/client/plugos/util.ts +20 -0
  17. package/client/space_lua/aggregates.ts +2 -2
  18. package/client/space_lua/query_collection.ts +4 -4
  19. package/client/space_lua/render_lua_markdown.ts +1 -1
  20. package/client/space_lua/runtime.ts +1 -1
  21. package/client/space_lua/{liq_null.ts → sliq_null.ts} +2 -2
  22. package/client/space_lua/stdlib/space_lua.ts +1 -1
  23. package/client/space_lua/stdlib.ts +1 -1
  24. package/dist/plug-compile.js +44 -2
  25. package/package.json +1 -1
  26. package/plug-api/constants.ts +20 -0
  27. package/plug-api/lib/ref.ts +9 -2
  28. package/plug-api/lib/shortcut.ts +16 -0
  29. package/plug-api/lib/tree.ts +35 -4
  30. package/plug-api/syscalls/config.ts +37 -0
  31. package/plug-api/syscalls/editor.ts +160 -0
  32. package/plug-api/syscalls/index.ts +30 -0
  33. package/plug-api/syscalls/system.ts +15 -0
  34. package/plug-api/types/client.ts +12 -0
  35. package/plug-api/types/manifest.ts +63 -0
  36. package/plugs/builtin_plugs.ts +1 -1
  37. package/client/plugos/sandboxes/web_worker_sandbox.ts +0 -17
@@ -1,3 +1,23 @@
1
+ /**
2
+ * Deep-clone a value, replacing any functions with null.
3
+ * Needed because values containing functions can't be serialized
4
+ * via postMessage or validated with JSON Schema.
5
+ */
6
+ export function stripFunctions(value: any): any {
7
+ if (typeof value === "function") return null;
8
+ if (value === null || value === undefined || typeof value !== "object") {
9
+ return value;
10
+ }
11
+ if (Array.isArray(value)) {
12
+ return value.map(stripFunctions);
13
+ }
14
+ const result: Record<string, any> = {};
15
+ for (const key of Object.keys(value)) {
16
+ result[key] = stripFunctions(value[key]);
17
+ }
18
+ return result;
19
+ }
20
+
1
21
  /**
2
22
  * Checks if an object is sendable across the plugos worker boundary.
3
23
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Aggregate function definitions and execution for LIQ.
2
+ * Aggregate function definitions and execution for SLIQ.
3
3
  *
4
4
  * Builtins implement ILuaFunction via plain objects rather than
5
5
  * LuaBuiltinFunction instances. This avoids ES module TDZ issues:
@@ -16,7 +16,7 @@ import {
16
16
  luaValueToJS,
17
17
  type LuaValue,
18
18
  } from "./runtime.ts";
19
- import { isSqlNull } from "./liq_null.ts";
19
+ import { isSqlNull } from "./sliq_null.ts";
20
20
  import type { LuaExpression, LuaOrderBy } from "./ast.ts";
21
21
  import { buildItemEnv } from "./query_env.ts";
22
22
  import { asyncMergeSort } from "./util.ts";
@@ -26,7 +26,7 @@ import {
26
26
  type LuaValue,
27
27
  singleResult,
28
28
  } from "./runtime.ts";
29
- import { isSqlNull, LIQ_NULL } from "./liq_null.ts";
29
+ import { isSqlNull, SLIQ_NULL } from "./sliq_null.ts";
30
30
  import { evalExpression, luaOp } from "./eval.ts";
31
31
  import { asyncMergeSort } from "./util.ts";
32
32
  import type { DataStore } from "../data/datastore.ts";
@@ -295,7 +295,7 @@ function containsAggregate(expr: LuaExpression, config?: Config): boolean {
295
295
  // Wrap a value for select result tables so that the column key survives
296
296
  // in the `LuaTable`
297
297
  function selectVal(v: LuaValue): LuaValue {
298
- return v === null || v === undefined ? LIQ_NULL : v;
298
+ return v === null || v === undefined ? SLIQ_NULL : v;
299
299
  }
300
300
 
301
301
  /**
@@ -491,7 +491,7 @@ function normalizeSelectResults(results: any[]): any[] {
491
491
  const rebuilt = new LuaTable();
492
492
  for (const k of canonicalKeys) {
493
493
  const v = item.rawGet(k);
494
- void rebuilt.rawSet(k, v === undefined || v === null ? LIQ_NULL : v);
494
+ void rebuilt.rawSet(k, v === undefined || v === null ? SLIQ_NULL : v);
495
495
  }
496
496
  for (const k of luaKeys(item)) {
497
497
  if (typeof k !== "string") {
@@ -675,7 +675,7 @@ async function evalSelectExpression(
675
675
  for (const k of luaKeys(result)) {
676
676
  const v = result.rawGet(k);
677
677
  if (v === null || v === undefined) {
678
- void result.rawSet(k, LIQ_NULL);
678
+ void result.rawSet(k, SLIQ_NULL);
679
679
  }
680
680
  }
681
681
  return result;
@@ -3,7 +3,7 @@ import {
3
3
  escapeRegularPipes,
4
4
  jsonToMDTable,
5
5
  } from "../markdown_renderer/result_render.ts";
6
- import { isSqlNull } from "../space_lua/liq_null.ts";
6
+ import { isSqlNull } from "../space_lua/sliq_null.ts";
7
7
  import { isTaggedFloat } from "../space_lua/numeric.ts";
8
8
  import { LuaTable, luaFormatNumber } from "../space_lua/runtime.ts";
9
9
 
@@ -3,7 +3,7 @@ import { evalStatement } from "./eval.ts";
3
3
  import { asyncQuickSort } from "./util.ts";
4
4
  import { isPromise, rpAll } from "./rp.ts";
5
5
  import { isNegativeZero, isTaggedFloat } from "./numeric.ts";
6
- import { isSqlNull } from "./liq_null.ts";
6
+ import { isSqlNull } from "./sliq_null.ts";
7
7
  import { luaFormat } from "./stdlib/format.ts";
8
8
 
9
9
  export type LuaType =
@@ -1,6 +1,6 @@
1
1
  // Sentinel value representing SQL NULL in query results.
2
- export const LIQ_NULL = Symbol.for("silverbullet.sqlNull");
2
+ export const SLIQ_NULL = Symbol.for("silverbullet.sqlNull");
3
3
 
4
4
  export function isSqlNull(v: any): boolean {
5
- return v === LIQ_NULL;
5
+ return v === SLIQ_NULL;
6
6
  }
@@ -12,7 +12,7 @@ import {
12
12
  luaValueToJS,
13
13
  singleResult,
14
14
  } from "../runtime.ts";
15
- import { isSqlNull } from "../liq_null.ts";
15
+ import { isSqlNull } from "../sliq_null.ts";
16
16
 
17
17
  /**
18
18
  * These are Space Lua specific functions that are available to all scripts, but are not part of the standard Lua language.
@@ -35,7 +35,7 @@ import { cryptoApi } from "./stdlib/crypto.ts";
35
35
  import { netApi } from "./stdlib/net.ts";
36
36
  import { isTaggedFloat, makeLuaFloat } from "./numeric.ts";
37
37
  import { isPromise } from "./rp.ts";
38
- import { isSqlNull } from "./liq_null.ts";
38
+ import { isSqlNull } from "./sliq_null.ts";
39
39
 
40
40
  const printFunction = new LuaBuiltinFunction(async (_sf, ...args) => {
41
41
  console.log("[Lua]", ...(await Promise.all(args.map((v) => luaToString(v)))));
@@ -4,14 +4,22 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // version.ts
7
- var version = "2.6.1";
7
+ var version = "2.8.0";
8
8
 
9
9
  // client/plugos/plug_compile.ts
10
10
  import * as path from "node:path";
11
- import { readFile as readFile2, writeFile, mkdtemp, rm, mkdir } from "node:fs/promises";
11
+ import {
12
+ copyFile,
13
+ mkdir,
14
+ mkdtemp,
15
+ readFile as readFile2,
16
+ rm,
17
+ writeFile
18
+ } from "node:fs/promises";
12
19
  import { tmpdir } from "node:os";
13
20
  import * as YAML from "js-yaml";
14
21
  import * as esbuild from "esbuild";
22
+ import * as sass from "sass";
15
23
 
16
24
  // client/asset_bundle/builder.ts
17
25
  import { readFile, readdir } from "node:fs/promises";
@@ -1339,6 +1347,12 @@ async function compileManifest(manifestPath, destPath, options = {}) {
1339
1347
  if (!manifest.name) {
1340
1348
  throw new Error(`Missing 'name' in ${manifestPath}`);
1341
1349
  }
1350
+ if (manifest.build) {
1351
+ await Promise.all(
1352
+ manifest.build.map((step) => runBuildStep(step, rootPath, options))
1353
+ );
1354
+ delete manifest.build;
1355
+ }
1342
1356
  const assetsBundle = await bundleAssets(
1343
1357
  path.resolve(rootPath),
1344
1358
  manifest.assets || []
@@ -1435,6 +1449,34 @@ async function compileManifests(manifestFiles, dist, options = {}) {
1435
1449
  }
1436
1450
  await buildAll();
1437
1451
  }
1452
+ async function runBuildStep(step, rootPath, options) {
1453
+ const type = step.type ?? "esbuild";
1454
+ const inFile = path.resolve(rootPath, step.in);
1455
+ const outFile = path.resolve(rootPath, step.out);
1456
+ await mkdir(path.dirname(outFile), { recursive: true });
1457
+ if (type === "esbuild") {
1458
+ await esbuild.build({
1459
+ entryPoints: [inFile],
1460
+ bundle: true,
1461
+ format: "iife",
1462
+ platform: "browser",
1463
+ minify: !options.debug,
1464
+ outfile: outFile,
1465
+ treeShaking: true
1466
+ });
1467
+ } else if (type === "sass") {
1468
+ const scss = await readFile2(inFile, "utf-8");
1469
+ const result = sass.compileString(scss, {
1470
+ loadPaths: [path.dirname(inFile)],
1471
+ style: options.debug ? "expanded" : "compressed"
1472
+ });
1473
+ await writeFile(outFile, result.css, "utf-8");
1474
+ } else if (type === "copy") {
1475
+ await copyFile(inFile, outFile);
1476
+ } else {
1477
+ throw new Error(`Unsupported build step type: ${type}`);
1478
+ }
1479
+ }
1438
1480
  function patchBundledJS(code) {
1439
1481
  return code.replaceAll("/(?<=\\n)/", "/()/");
1440
1482
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@silverbulletmd/silverbullet",
3
- "version": "2.6.1",
3
+ "version": "2.8.0",
4
4
  "type": "module",
5
5
  "description": "A self-hosted, web-based note taking app",
6
6
  "publishConfig": {
@@ -8,3 +8,23 @@ export const wrongSpacePathError: Error = new Error(
8
8
  );
9
9
  export const pingTimeout: number = 2000;
10
10
  export const pingInterval: number = 5000;
11
+
12
+ /**
13
+ * Determines if an error thrown by fetch() is a network-level error
14
+ * (as opposed to a server-side or application error).
15
+ *
16
+ * Browser-specific error messages when there is no internet connection:
17
+ * - Firefox: "NetworkError when attempting to fetch resource"
18
+ * - Safari (service worker): "FetchEvent.respondWith received an error: TypeError: Load failed"
19
+ * - Safari (no service worker): "Load failed"
20
+ * - Chrome: "Failed to fetch"
21
+ */
22
+ export function isNetworkError(e: any): boolean {
23
+ const msg = (e?.message || "").toLowerCase();
24
+ return (
25
+ msg.includes("fetch") ||
26
+ msg.includes("load failed") ||
27
+ msg.includes("networkerror") ||
28
+ msg.includes("unavailable")
29
+ );
30
+ }
@@ -23,7 +23,8 @@ export type Ref = {
23
23
  details?:
24
24
  | { type: "position"; pos: number }
25
25
  | { type: "linecolumn"; line: number; column: number }
26
- | { type: "header"; header: string };
26
+ | { type: "header"; header: string }
27
+ | { type: "anchor"; name: string };
27
28
  };
28
29
 
29
30
  /**
@@ -96,7 +97,7 @@ export function isValidPath(path: string): path is Path {
96
97
  * TO THE INNER WORKINGS OF SILVERBULLET AND CHANGES COULD INTRODUCE MAJOR BUGS
97
98
  */
98
99
  const refRegex =
99
- /^(?<meta>\^)?(?<path>(?!.*\.[a-zA-Z0-9]+\.md$)(?!\/?(\.|\^))(?!.*(?:\/|^)\.{1,2}(?:\/|$)|.*\/{2})(?!.*(?:\]\]|\[\[))[^@#|<>]*)(@(?<pos>\d+)|@[Ll](?<line>\d+)(?:[Cc](?<col>\d+))?|#\s*(?<header>.*))?$/;
100
+ /^(?<meta>\^)?(?<path>(?!.*\.[a-zA-Z0-9]+\.md$)(?!\/?(\.|\^))(?!.*(?:\/|^)\.{1,2}(?:\/|$)|.*\/{2})(?!.*(?:\]\]|\[\[))[^@#|<>$]*)(@(?<pos>\d+)|@[Ll](?<line>\d+)(?:[Cc](?<col>\d+))?|#\s*(?<header>.*)|\$(?<anchor>[A-Za-z_][A-Za-z0-9_/:-]*))?$/;
100
101
 
101
102
  /**
102
103
  * Parses a reference string into a ref object.
@@ -132,6 +133,8 @@ export function parseToRef(stringRef: string): Ref | null {
132
133
  type: "header",
133
134
  header: groups.header,
134
135
  };
136
+ } else if (groups.anchor !== undefined) {
137
+ ref.details = { type: "anchor", name: groups.anchor };
135
138
  }
136
139
 
137
140
  return ref;
@@ -158,6 +161,8 @@ export function encodeRef(ref: Ref): string {
158
161
  stringRef += `@${ref.details.pos}`;
159
162
  } else if (ref.details?.type === "header") {
160
163
  stringRef += `#${ref.details.header}`;
164
+ } else if (ref.details?.type === "anchor") {
165
+ stringRef += `$${ref.details.name}`;
161
166
  }
162
167
 
163
168
  return stringRef;
@@ -191,6 +196,8 @@ export function getOffsetFromRef(
191
196
  case "header": {
192
197
  return getOffsetFromHeader(parseTree, ref.details.header);
193
198
  }
199
+ case "anchor":
200
+ return -1;
194
201
  }
195
202
  }
196
203
 
@@ -0,0 +1,16 @@
1
+ export const isMacLike = typeof navigator !== "undefined" &&
2
+ /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
3
+
4
+ export function prettifyShortcut(shortcut: string): string {
5
+ if (!isMacLike) return shortcut;
6
+ const pretty = shortcut
7
+ .replace(/Mod-/g, "⌘")
8
+ .replace(/Cmd-/g, "⌘")
9
+ .replace(/Ctrl-/g, "⌃")
10
+ .replace(/Alt-/g, "⌥")
11
+ .replace(/Shift-/g, "⇧");
12
+ return pretty.replace(
13
+ /([⌘⌃⌥⇧])([a-z])$/,
14
+ (_, mod, key) => mod + key.toUpperCase(),
15
+ );
16
+ }
@@ -191,13 +191,29 @@ export function traverseTree(
191
191
  tree: ParseTree,
192
192
  // Return value = should stop traversal into children?
193
193
  matchFn: (tree: ParseTree) => boolean,
194
+ // Just log errors on exceptions, but don't stop traversal
195
+ catchVisitorErrors = false,
194
196
  ): void {
195
- if (matchFn(tree)) {
197
+ let stop = false;
198
+ if (catchVisitorErrors) {
199
+ try {
200
+ stop = matchFn(tree);
201
+ } catch (e: any) {
202
+ console.error(
203
+ `traverseTree visitor failed at node ${tree.type}@${tree.from}:`,
204
+ e?.stack ?? e,
205
+ );
206
+ return;
207
+ }
208
+ } else {
209
+ stop = matchFn(tree);
210
+ }
211
+ if (stop) {
196
212
  return;
197
213
  }
198
214
  if (tree.children) {
199
215
  for (const child of tree.children) {
200
- traverseTree(child, matchFn);
216
+ traverseTree(child, matchFn, catchVisitorErrors);
201
217
  }
202
218
  }
203
219
  }
@@ -206,13 +222,28 @@ export async function traverseTreeAsync(
206
222
  tree: ParseTree,
207
223
  // Return value = should stop traversal into children?
208
224
  matchFn: (tree: ParseTree) => Promise<boolean>,
225
+ catchVisitorErrors = false,
209
226
  ): Promise<void> {
210
- if (await matchFn(tree)) {
227
+ let stop = false;
228
+ if (catchVisitorErrors) {
229
+ try {
230
+ stop = await matchFn(tree);
231
+ } catch (e: any) {
232
+ console.error(
233
+ `traverseTreeAsync visitor failed at node ${tree.type}@${tree.from}:`,
234
+ e?.stack ?? e,
235
+ );
236
+ return;
237
+ }
238
+ } else {
239
+ stop = await matchFn(tree);
240
+ }
241
+ if (stop) {
211
242
  return;
212
243
  }
213
244
  if (tree.children) {
214
245
  for (const child of tree.children) {
215
- await traverseTreeAsync(child, matchFn);
246
+ await traverseTreeAsync(child, matchFn, catchVisitorErrors);
216
247
  }
217
248
  }
218
249
  }
@@ -53,3 +53,40 @@ export function has(path: string): Promise<boolean> {
53
53
  export function define(key: string, schema: any): Promise<void> {
54
54
  return syscall("config.define", key, schema);
55
55
  }
56
+
57
+ /**
58
+ * Defines (or updates) a UI category for the configuration manager. Categories
59
+ * are referenced by name from a schema field's `ui.category` and are sorted in
60
+ * ascending `order` (default 0); unknown categories fall to the bottom in
61
+ * alphabetical order.
62
+ */
63
+ export function defineCategory(
64
+ definition: { name: string; description?: string; order?: number },
65
+ ): Promise<void> {
66
+ return syscall("config.defineCategory", definition);
67
+ }
68
+
69
+ /**
70
+ * Gets all config values as a single object.
71
+ * @returns The entire config values object
72
+ */
73
+ export function getValues(): Promise<Record<string, any>> {
74
+ return syscall("config.getValues");
75
+ }
76
+
77
+ /**
78
+ * Gets all defined config schemas.
79
+ * @returns The schema definitions object
80
+ */
81
+ export function getSchemas(): Promise<Record<string, any>> {
82
+ return syscall("config.getSchemas");
83
+ }
84
+
85
+ /**
86
+ * Gets all registered UI categories for the configuration manager.
87
+ */
88
+ export function getCategories(): Promise<
89
+ Record<string, { name: string; description?: string; order?: number }>
90
+ > {
91
+ return syscall("config.getCategories");
92
+ }
@@ -153,6 +153,11 @@ export function reloadPage(): Promise<void> {
153
153
  return syscall("editor.reloadPage");
154
154
  }
155
155
 
156
+ /** Useful when a plug toggles a piece of state that lint subscribers consult. */
157
+ export function forceLint(): Promise<void> {
158
+ return syscall("editor.forceLint");
159
+ }
160
+
156
161
  /**
157
162
  * Force reloads the browser UI
158
163
  */
@@ -269,6 +274,13 @@ export function hidePanel(id: "lhs" | "rhs" | "bhs" | "modal"): Promise<void> {
269
274
  return syscall("editor.hidePanel", id);
270
275
  }
271
276
 
277
+ /**
278
+ * Returns focus to the main editor.
279
+ */
280
+ export function focus(): Promise<void> {
281
+ return syscall("editor.focus");
282
+ }
283
+
272
284
  export function showProgress(
273
285
  progressPercentage?: number,
274
286
  progressType?: string,
@@ -466,6 +478,27 @@ export function deleteLine(): Promise<void> {
466
478
  return syscall("editor.deleteLine");
467
479
  }
468
480
 
481
+ /**
482
+ * Select the entire document
483
+ */
484
+ export function selectAll(): Promise<void> {
485
+ return syscall("editor.selectAll");
486
+ }
487
+
488
+ /**
489
+ * Increase indentation of the current line or selection
490
+ */
491
+ export function indentMore(): Promise<void> {
492
+ return syscall("editor.indentMore");
493
+ }
494
+
495
+ /**
496
+ * Decrease indentation of the current line or selection
497
+ */
498
+ export function indentLess(): Promise<void> {
499
+ return syscall("editor.indentLess");
500
+ }
501
+
469
502
  /**
470
503
  * Comment or uncomment the current line in the editor
471
504
  */
@@ -481,6 +514,133 @@ export function moveLineDown(): Promise<void> {
481
514
  return syscall("editor.moveLineDown");
482
515
  }
483
516
 
517
+ // Cursor motion syscalls (wrappers around @codemirror/commands functions)
518
+ export function cursorCharLeft(): Promise<void> {
519
+ return syscall("editor.cursorCharLeft");
520
+ }
521
+ export function cursorCharRight(): Promise<void> {
522
+ return syscall("editor.cursorCharRight");
523
+ }
524
+ export function cursorGroupLeft(): Promise<void> {
525
+ return syscall("editor.cursorGroupLeft");
526
+ }
527
+ export function cursorGroupRight(): Promise<void> {
528
+ return syscall("editor.cursorGroupRight");
529
+ }
530
+ export function cursorLineBoundaryLeft(): Promise<void> {
531
+ return syscall("editor.cursorLineBoundaryLeft");
532
+ }
533
+ export function cursorLineBoundaryRight(): Promise<void> {
534
+ return syscall("editor.cursorLineBoundaryRight");
535
+ }
536
+ export function cursorLineStart(): Promise<void> {
537
+ return syscall("editor.cursorLineStart");
538
+ }
539
+ export function cursorLineEnd(): Promise<void> {
540
+ return syscall("editor.cursorLineEnd");
541
+ }
542
+ export function cursorDocStart(): Promise<void> {
543
+ return syscall("editor.cursorDocStart");
544
+ }
545
+ export function cursorDocEnd(): Promise<void> {
546
+ return syscall("editor.cursorDocEnd");
547
+ }
548
+ export function cursorLineUp(): Promise<void> {
549
+ return syscall("editor.cursorLineUp");
550
+ }
551
+ export function cursorLineDown(): Promise<void> {
552
+ return syscall("editor.cursorLineDown");
553
+ }
554
+ export function cursorPageUp(): Promise<void> {
555
+ return syscall("editor.cursorPageUp");
556
+ }
557
+ export function cursorPageDown(): Promise<void> {
558
+ return syscall("editor.cursorPageDown");
559
+ }
560
+
561
+ // Selection-extending motions
562
+ export function selectCharLeft(): Promise<void> {
563
+ return syscall("editor.selectCharLeft");
564
+ }
565
+ export function selectCharRight(): Promise<void> {
566
+ return syscall("editor.selectCharRight");
567
+ }
568
+ export function selectGroupLeft(): Promise<void> {
569
+ return syscall("editor.selectGroupLeft");
570
+ }
571
+ export function selectGroupRight(): Promise<void> {
572
+ return syscall("editor.selectGroupRight");
573
+ }
574
+ export function selectLineBoundaryLeft(): Promise<void> {
575
+ return syscall("editor.selectLineBoundaryLeft");
576
+ }
577
+ export function selectLineBoundaryRight(): Promise<void> {
578
+ return syscall("editor.selectLineBoundaryRight");
579
+ }
580
+ export function selectLineStart(): Promise<void> {
581
+ return syscall("editor.selectLineStart");
582
+ }
583
+ export function selectLineEnd(): Promise<void> {
584
+ return syscall("editor.selectLineEnd");
585
+ }
586
+ export function selectDocStart(): Promise<void> {
587
+ return syscall("editor.selectDocStart");
588
+ }
589
+ export function selectDocEnd(): Promise<void> {
590
+ return syscall("editor.selectDocEnd");
591
+ }
592
+ export function selectLineUp(): Promise<void> {
593
+ return syscall("editor.selectLineUp");
594
+ }
595
+ export function selectLineDown(): Promise<void> {
596
+ return syscall("editor.selectLineDown");
597
+ }
598
+ export function selectPageUp(): Promise<void> {
599
+ return syscall("editor.selectPageUp");
600
+ }
601
+ export function selectPageDown(): Promise<void> {
602
+ return syscall("editor.selectPageDown");
603
+ }
604
+
605
+ // Delete
606
+ export function deleteCharBackward(): Promise<void> {
607
+ return syscall("editor.deleteCharBackward");
608
+ }
609
+ export function deleteCharForward(): Promise<void> {
610
+ return syscall("editor.deleteCharForward");
611
+ }
612
+ export function deleteGroupBackward(): Promise<void> {
613
+ return syscall("editor.deleteGroupBackward");
614
+ }
615
+ export function deleteGroupForward(): Promise<void> {
616
+ return syscall("editor.deleteGroupForward");
617
+ }
618
+ export function deleteLineBoundaryBackward(): Promise<void> {
619
+ return syscall("editor.deleteLineBoundaryBackward");
620
+ }
621
+ export function deleteLineBoundaryForward(): Promise<void> {
622
+ return syscall("editor.deleteLineBoundaryForward");
623
+ }
624
+ export function transposeChars(): Promise<void> {
625
+ return syscall("editor.transposeChars");
626
+ }
627
+
628
+ // Enter: accepts completion if popup open, else inserts newline
629
+ export function insertNewline(): Promise<void> {
630
+ return syscall("editor.insertNewline");
631
+ }
632
+
633
+ // Completion popup
634
+ export function acceptCompletion(): Promise<boolean> {
635
+ return syscall("editor.acceptCompletion");
636
+ }
637
+ export function startCompletion(): Promise<void> {
638
+ return syscall("editor.startCompletion");
639
+ }
640
+ export function closeCompletion(): Promise<void> {
641
+ return syscall("editor.closeCompletion");
642
+ }
643
+
484
644
  // Vim-mode specific syscalls
485
645
 
486
646
  /**
@@ -27,6 +27,18 @@ export function validateObjects<T>(
27
27
  return syscall("index.validateObjects", page, objects);
28
28
  }
29
29
 
30
+ /**
31
+ * Run the full indexing pipeline (validation, multi-tag expansion,
32
+ * tag transforms) and return the resulting objects each paired with
33
+ * the tag they're indexed under. Read-only — no DB writes.
34
+ */
35
+ export function previewProcessedObjects(
36
+ page: string,
37
+ objects: ObjectValue[],
38
+ ): Promise<{ tag: string; object: ObjectValue }[]> {
39
+ return syscall("index.previewProcessedObjects", page, objects);
40
+ }
41
+
30
42
  /**
31
43
  * Queries objects using a Lua-based collection query
32
44
  * @param tag - The tag to filter objects by
@@ -75,3 +87,21 @@ export function deleteObject(
75
87
  ): Promise<void> {
76
88
  return syscall("index.deleteObject", page, tag, ref);
77
89
  }
90
+
91
+ export type {
92
+ AnchorHit,
93
+ ResolveAnchorResult,
94
+ } from "../../plugs/index/types.ts";
95
+ import type { ResolveAnchorResult } from "../../plugs/index/types.ts";
96
+
97
+ /**
98
+ * Resolves a `$name` anchor to its host. Returns `ok: true` with page +
99
+ * hostTag + range on success, or `ok: false` with `missing`/`duplicate`
100
+ * reason. When `page` is provided, the lookup is filtered to that page.
101
+ */
102
+ export function resolveAnchor(
103
+ name: string,
104
+ page?: string,
105
+ ): Promise<ResolveAnchorResult> {
106
+ return syscall("index.resolveAnchor", name, page);
107
+ }
@@ -51,6 +51,21 @@ export function reloadPlugs(): Promise<void> {
51
51
  return syscall("system.reloadPlugs");
52
52
  }
53
53
 
54
+ /**
55
+ * Load (or reload) a single plug from a space file path.
56
+ */
57
+ export function loadPlug(path: string): Promise<void> {
58
+ return syscall("system.loadPlug", path);
59
+ }
60
+
61
+ /**
62
+ * Unload a plug previously loaded from the given space file path.
63
+ * Returns true if a plug was unloaded.
64
+ */
65
+ export function unloadPlug(path: string): Promise<boolean> {
66
+ return syscall("system.unloadPlug", path);
67
+ }
68
+
54
69
  /**
55
70
  * Returns the current mode of the system, either "ro" (read-only) or "rw" (read-write)
56
71
  */
@@ -54,6 +54,18 @@ export type LintDiagnostic = {
54
54
  to: number;
55
55
  severity: "error" | "warning" | "info" | "hint";
56
56
  message: string;
57
+ /**
58
+ * Optional HTML rendered into the hover tooltip for this diagnostic.
59
+ * When present, takes precedence over `message` for tooltip display.
60
+ * `message` is still used as a plain-text fallback (e.g. in the lint
61
+ * panel) and as the diagnostic's accessible label.
62
+ */
63
+ messageHtml?: string;
64
+ /**
65
+ * Optional CSS class added to the inline range mark for this diagnostic,
66
+ * in addition to CodeMirror's default `.cm-lintRange-<severity>` class.
67
+ */
68
+ markClass?: string;
57
69
  };
58
70
 
59
71
  export type UploadFile = {