@silverbulletmd/silverbullet 2.4.1

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 (117) hide show
  1. package/LICENSE.md +18 -0
  2. package/README.md +98 -0
  3. package/client/asset_bundle/bundle.ts +95 -0
  4. package/client/data/datastore.ts +85 -0
  5. package/client/data/kv_primitives.ts +25 -0
  6. package/client/markdown_parser/constants.ts +13 -0
  7. package/client/plugos/event.ts +36 -0
  8. package/client/plugos/eventhook.ts +8 -0
  9. package/client/plugos/hooks/code_widget.ts +59 -0
  10. package/client/plugos/hooks/command.ts +104 -0
  11. package/client/plugos/hooks/document_editor.ts +77 -0
  12. package/client/plugos/hooks/event.ts +187 -0
  13. package/client/plugos/hooks/mq.ts +154 -0
  14. package/client/plugos/hooks/plug_namespace.ts +85 -0
  15. package/client/plugos/hooks/slash_command.ts +192 -0
  16. package/client/plugos/hooks/syscall.ts +66 -0
  17. package/client/plugos/manifest_cache.ts +67 -0
  18. package/client/plugos/plug.ts +99 -0
  19. package/client/plugos/plug_compile.ts +202 -0
  20. package/client/plugos/protocol.ts +40 -0
  21. package/client/plugos/proxy_fetch.ts +53 -0
  22. package/client/plugos/sandboxes/deno_worker_sandbox.ts +6 -0
  23. package/client/plugos/sandboxes/sandbox.ts +14 -0
  24. package/client/plugos/sandboxes/web_worker_sandbox.ts +17 -0
  25. package/client/plugos/sandboxes/worker_sandbox.ts +132 -0
  26. package/client/plugos/syscalls/asset.ts +35 -0
  27. package/client/plugos/syscalls/clientStore.ts +21 -0
  28. package/client/plugos/syscalls/client_code_widget.ts +12 -0
  29. package/client/plugos/syscalls/code_widget.ts +24 -0
  30. package/client/plugos/syscalls/config.ts +46 -0
  31. package/client/plugos/syscalls/datastore.ts +89 -0
  32. package/client/plugos/syscalls/editor.ts +673 -0
  33. package/client/plugos/syscalls/event.ts +36 -0
  34. package/client/plugos/syscalls/fetch.ts +128 -0
  35. package/client/plugos/syscalls/index.ts +102 -0
  36. package/client/plugos/syscalls/jsonschema.ts +69 -0
  37. package/client/plugos/syscalls/language.ts +23 -0
  38. package/client/plugos/syscalls/lua.ts +58 -0
  39. package/client/plugos/syscalls/markdown.ts +84 -0
  40. package/client/plugos/syscalls/mq.ts +52 -0
  41. package/client/plugos/syscalls/service_registry.ts +43 -0
  42. package/client/plugos/syscalls/shell.ts +39 -0
  43. package/client/plugos/syscalls/space.ts +139 -0
  44. package/client/plugos/syscalls/sync.ts +77 -0
  45. package/client/plugos/syscalls/system.ts +150 -0
  46. package/client/plugos/system.ts +201 -0
  47. package/client/plugos/types.ts +60 -0
  48. package/client/plugos/util.ts +14 -0
  49. package/client/plugos/worker_runtime.ts +195 -0
  50. package/client/space_lua/ast.ts +328 -0
  51. package/client/space_lua/ast_narrow.ts +81 -0
  52. package/client/space_lua/eval.ts +2478 -0
  53. package/client/space_lua/labels.ts +416 -0
  54. package/client/space_lua/numeric.ts +240 -0
  55. package/client/space_lua/parse.ts +1522 -0
  56. package/client/space_lua/query_collection.ts +232 -0
  57. package/client/space_lua/rp.ts +27 -0
  58. package/client/space_lua/runtime.ts +1702 -0
  59. package/client/space_lua/stdlib/crypto.ts +10 -0
  60. package/client/space_lua/stdlib/encoding.ts +19 -0
  61. package/client/space_lua/stdlib/format.ts +770 -0
  62. package/client/space_lua/stdlib/js.ts +73 -0
  63. package/client/space_lua/stdlib/load.ts +52 -0
  64. package/client/space_lua/stdlib/math.ts +193 -0
  65. package/client/space_lua/stdlib/net.ts +113 -0
  66. package/client/space_lua/stdlib/os.ts +368 -0
  67. package/client/space_lua/stdlib/space_lua.ts +153 -0
  68. package/client/space_lua/stdlib/string.ts +286 -0
  69. package/client/space_lua/stdlib/table.ts +401 -0
  70. package/client/space_lua/stdlib.ts +489 -0
  71. package/client/space_lua/tonumber.ts +501 -0
  72. package/client/space_lua/util.ts +96 -0
  73. package/dist/plug-compile.js +1513 -0
  74. package/package.json +120 -0
  75. package/plug-api/constants.ts +42 -0
  76. package/plug-api/lib/async.ts +162 -0
  77. package/plug-api/lib/crypto.ts +202 -0
  78. package/plug-api/lib/dates.ts +13 -0
  79. package/plug-api/lib/json.ts +136 -0
  80. package/plug-api/lib/limited_map.ts +72 -0
  81. package/plug-api/lib/memory_cache.ts +21 -0
  82. package/plug-api/lib/native_fetch.ts +6 -0
  83. package/plug-api/lib/ref.ts +275 -0
  84. package/plug-api/lib/resolve.ts +90 -0
  85. package/plug-api/lib/tags.ts +15 -0
  86. package/plug-api/lib/transclusion.ts +122 -0
  87. package/plug-api/lib/tree.ts +232 -0
  88. package/plug-api/lib/yaml.ts +284 -0
  89. package/plug-api/syscall.ts +15 -0
  90. package/plug-api/syscalls/asset.ts +36 -0
  91. package/plug-api/syscalls/client_store.ts +33 -0
  92. package/plug-api/syscalls/code_widget.ts +8 -0
  93. package/plug-api/syscalls/config.ts +58 -0
  94. package/plug-api/syscalls/datastore.ts +96 -0
  95. package/plug-api/syscalls/editor.ts +517 -0
  96. package/plug-api/syscalls/event.ts +47 -0
  97. package/plug-api/syscalls/index.ts +77 -0
  98. package/plug-api/syscalls/jsonschema.ts +25 -0
  99. package/plug-api/syscalls/language.ts +23 -0
  100. package/plug-api/syscalls/lua.ts +20 -0
  101. package/plug-api/syscalls/markdown.ts +38 -0
  102. package/plug-api/syscalls/mq.ts +79 -0
  103. package/plug-api/syscalls/shell.ts +14 -0
  104. package/plug-api/syscalls/space.ts +212 -0
  105. package/plug-api/syscalls/sync.ts +28 -0
  106. package/plug-api/syscalls/system.ts +102 -0
  107. package/plug-api/syscalls/yaml.ts +28 -0
  108. package/plug-api/syscalls.ts +21 -0
  109. package/plug-api/system_mock.ts +89 -0
  110. package/plug-api/types/client.ts +116 -0
  111. package/plug-api/types/config.ts +22 -0
  112. package/plug-api/types/datastore.ts +28 -0
  113. package/plug-api/types/event.ts +27 -0
  114. package/plug-api/types/index.ts +56 -0
  115. package/plug-api/types/manifest.ts +98 -0
  116. package/plug-api/types/namespace.ts +6 -0
  117. package/plugs/builtin_plugs.ts +14 -0
@@ -0,0 +1,673 @@
1
+ import type { Client } from "../../client.ts";
2
+ import {
3
+ foldAll,
4
+ foldCode,
5
+ toggleFold,
6
+ unfoldAll,
7
+ unfoldCode,
8
+ } from "@codemirror/language";
9
+ import {
10
+ deleteLine,
11
+ insertNewline,
12
+ insertNewlineAndIndent,
13
+ moveLineDown,
14
+ moveLineUp,
15
+ redo,
16
+ toggleComment,
17
+ undo,
18
+ } from "@codemirror/commands";
19
+ import type { Transaction } from "@codemirror/state";
20
+ import { EditorView } from "@codemirror/view";
21
+ import { getCM as vimGetCm, Vim } from "@replit/codemirror-vim";
22
+ import type { SysCallMapping } from "../system.ts";
23
+ import type {
24
+ FilterOption,
25
+ UploadFile,
26
+ } from "@silverbulletmd/silverbullet/type/client";
27
+ import { openSearchPanel } from "@codemirror/search";
28
+ import {
29
+ isValidPath,
30
+ parseToRef,
31
+ type Path,
32
+ type Ref,
33
+ } from "@silverbulletmd/silverbullet/lib/ref";
34
+ import { insertNewlineContinueMarkup } from "@codemirror/lang-markdown";
35
+ import type { VimConfig } from "@silverbulletmd/silverbullet/type/config";
36
+ import type { PageMeta } from "@silverbulletmd/silverbullet/type/index";
37
+
38
+ export function editorSyscalls(client: Client): SysCallMapping {
39
+ const syscalls: SysCallMapping = {
40
+ "editor.getCurrentPage": (): string => {
41
+ return client.currentName();
42
+ },
43
+ "editor.getCurrentPageMeta": (): Promise<PageMeta | undefined> => {
44
+ const name = client.currentName();
45
+ return client.objectIndex.getObjectByRef(name, "page", name);
46
+ },
47
+ "editor.getCurrentPath": (_ctx): string => {
48
+ return client.currentPath();
49
+ },
50
+ "editor.getCurrentEditor": (): string => {
51
+ return client.documentEditor?.name || "page";
52
+ },
53
+ "editor.getRecentlyOpenedPages": (): PageMeta[] => {
54
+ return client.ui.viewState.allPages.sort((a, b) =>
55
+ (b.lastOpened || 0) - (a.lastOpened || 0)
56
+ );
57
+ },
58
+ "editor.getText": () => {
59
+ return client.editorView.state.sliceDoc();
60
+ },
61
+ "editor.getCurrentLine": (): {
62
+ from: number;
63
+ to: number;
64
+ text: string;
65
+ textWithCursor: string;
66
+ } => {
67
+ const pos = client.editorView.state.selection.main.from;
68
+ const line = client.editorView.state.doc.lineAt(pos);
69
+ return {
70
+ ...line,
71
+ textWithCursor: line.text.slice(0, pos - line.from) + "|^|" +
72
+ line.text.slice(pos - line.from),
73
+ };
74
+ },
75
+ "editor.setText": (_ctx, newText: string, shouldIsolateHistory = false) => {
76
+ client.setEditorText(newText, shouldIsolateHistory);
77
+ },
78
+ "editor.getCursor": (): number => {
79
+ return client.editorView.state.selection.main.from;
80
+ },
81
+ "editor.getSelection": (): { from: number; to: number; text: string } => {
82
+ const selection = client.editorView.state.selection.main;
83
+ const text = client.editorView.state.sliceDoc(
84
+ selection.from,
85
+ selection.to,
86
+ );
87
+ return {
88
+ from: selection.from,
89
+ to: selection.to,
90
+ text,
91
+ };
92
+ },
93
+ "editor.save": () => {
94
+ return client.save(true);
95
+ },
96
+ "editor.navigate": async (
97
+ _ctx,
98
+ ref: Ref | string,
99
+ replaceState = false,
100
+ newWindow = false,
101
+ ) => {
102
+ if (typeof ref === "string") {
103
+ const parsedRef = parseToRef(ref);
104
+ if (!parsedRef) {
105
+ throw new Error(
106
+ "Unable to parse string provided to `editor.navigate` as ref",
107
+ );
108
+ }
109
+ ref = parsedRef;
110
+ }
111
+
112
+ if (
113
+ // @ts-ignore: Legacy support
114
+ ref.page !== undefined
115
+ ) {
116
+ console.warn(
117
+ "You are using legacy navigation syntax with `editor.navigate()`, this will be phased out in the future",
118
+ );
119
+
120
+ const legacyRef = ref as unknown as {
121
+ kind: "page" | "document";
122
+ page: string;
123
+ pos?: number | { line: number; column: number };
124
+ header?: string;
125
+ meta?: boolean;
126
+ };
127
+
128
+ legacyRef.kind ??= "page";
129
+
130
+ let details: Ref["details"] = undefined;
131
+
132
+ if (typeof legacyRef.pos === "number") {
133
+ details = {
134
+ type: "position",
135
+ pos: legacyRef.pos,
136
+ };
137
+ } else if (legacyRef.pos) {
138
+ details = {
139
+ type: "linecolumn",
140
+ line: legacyRef.pos.line,
141
+ column: legacyRef.pos.column,
142
+ };
143
+ } else if (legacyRef.header) {
144
+ details = {
145
+ type: "header",
146
+ header: legacyRef.header,
147
+ };
148
+ }
149
+
150
+ ref = {
151
+ path: (legacyRef.kind === "page"
152
+ ? `${legacyRef.page}.md`
153
+ : legacyRef.page) as Path,
154
+ details,
155
+ meta: legacyRef.meta,
156
+ };
157
+ }
158
+
159
+ // This validation code should be connected to the Ref type. Ideally using
160
+ // some validation library. Didn't want to use jsonschemas here tho.
161
+ // Ideally this would be moved into a function too
162
+ if (!isValidPath(ref.path) && ref.path !== "") {
163
+ throw new Error(
164
+ "Path passed in ref to `editor.navigate` is invalid",
165
+ );
166
+ } else if (typeof ref.meta !== "boolean" && ref.meta !== undefined) {
167
+ throw new Error(
168
+ "ref.meta has to be of type `boolean`",
169
+ );
170
+ } else if (ref.details !== undefined && typeof ref.details !== "object") {
171
+ throw new Error(
172
+ "ref.details has to be of type `object` or `undefined`",
173
+ );
174
+ } else if (
175
+ ref.details &&
176
+ !["position", "linecolumn", "header"].includes(ref.details.type)
177
+ ) {
178
+ throw new Error(
179
+ "ref.details.type has to be 'position', 'linecolumn' or 'header'",
180
+ );
181
+ }
182
+
183
+ if (
184
+ ref.details?.type === "position" && typeof ref.details.pos !== "number"
185
+ ) {
186
+ throw new Error(
187
+ "ref.details.pos has to be of type `number`",
188
+ );
189
+ } else if (
190
+ ref.details?.type === "header" && typeof ref.details.header !== "string"
191
+ ) {
192
+ throw new Error(
193
+ "ref.details.header has to be of type `string`",
194
+ );
195
+ } else if (
196
+ ref.details?.type === "linecolumn" &&
197
+ typeof ref.details.line !== "number" &&
198
+ typeof ref.details.column !== "number"
199
+ ) {
200
+ throw new Error(
201
+ "ref.details.line and ref.details.column has to be of type `number`",
202
+ );
203
+ }
204
+
205
+ await client.navigate(ref, replaceState, newWindow);
206
+ },
207
+ "editor.reloadPage": async () => {
208
+ await client.reloadEditor();
209
+ },
210
+ "editor.reloadUI": () => {
211
+ location.reload();
212
+ },
213
+ "editor.rebuildEditorState": () => {
214
+ client.rebuildEditorState();
215
+ },
216
+ "editor.reloadConfigAndCommands": async () => {
217
+ await client.clientSystem.system.localSyscall(
218
+ "system.loadScripts",
219
+ [],
220
+ );
221
+ await client.clientSystem.system.localSyscall(
222
+ "system.loadSpaceStyles",
223
+ [],
224
+ );
225
+ },
226
+ "editor.invokeCommand": (_ctx, name: string, args?: string[]) => {
227
+ return client.runCommandByName(name, args);
228
+ },
229
+ "editor.openUrl": (_ctx, url: string, existingWindow = false) => {
230
+ client.openUrl(url, existingWindow);
231
+ },
232
+ "editor.newWindow": () => {
233
+ globalThis.open(
234
+ location.href,
235
+ "rnd" + Math.random(),
236
+ `width=${globalThis.innerWidth},heigh=${globalThis.innerHeight}`,
237
+ );
238
+ },
239
+ "editor.goHistory": (_ctx, delta: number) => {
240
+ globalThis.history.go(delta);
241
+ },
242
+ "editor.downloadFile": (_ctx, filename: string, dataUrl: string) => {
243
+ const link = document.createElement("a");
244
+ link.href = dataUrl;
245
+ link.download = filename;
246
+ link.click();
247
+ },
248
+ "editor.uploadFile": (
249
+ _ctx,
250
+ accept?: string,
251
+ capture?: string,
252
+ ): Promise<UploadFile> => {
253
+ return new Promise<UploadFile>((resolve, reject) => {
254
+ const input = document.createElement("input");
255
+ input.type = "file";
256
+ if (accept) {
257
+ input.accept = accept;
258
+ }
259
+ if (capture) {
260
+ input.capture = capture;
261
+ }
262
+
263
+ input.onchange = () => {
264
+ const file = input.files?.item(0);
265
+ if (!file) {
266
+ reject(new Error("No file found"));
267
+ } else {
268
+ const reader = new FileReader();
269
+ reader.readAsArrayBuffer(file);
270
+ reader.onloadend = async (evt) => {
271
+ if (evt.target?.readyState == FileReader.DONE) {
272
+ resolve({
273
+ name: file.name,
274
+ contentType: file.type,
275
+ content: new Uint8Array(await file.arrayBuffer()),
276
+ });
277
+ }
278
+ };
279
+ reader.onabort = (e) => {
280
+ reject(e);
281
+ };
282
+ reader.onerror = (e) => {
283
+ reject(e);
284
+ };
285
+ }
286
+ };
287
+ input.onabort = (e) => {
288
+ reject(e);
289
+ };
290
+
291
+ input.click();
292
+ });
293
+ },
294
+ "editor.flashNotification": (
295
+ _ctx,
296
+ message: string,
297
+ type: "error" | "info" = "info",
298
+ ) => {
299
+ client.flashNotification(message, type);
300
+ },
301
+ "editor.filterBox": (
302
+ _ctx,
303
+ label: string,
304
+ options: FilterOption[],
305
+ helpText = "",
306
+ placeHolder = "",
307
+ ): Promise<FilterOption | undefined> => {
308
+ return client.filterBox(label, options, helpText, placeHolder);
309
+ },
310
+ "editor.showPanel": (
311
+ _ctx,
312
+ id: string,
313
+ mode: number,
314
+ html: string,
315
+ script: string,
316
+ ) => {
317
+ client.ui.viewDispatch({
318
+ type: "show-panel",
319
+ id: id as any,
320
+ config: { html, script, mode },
321
+ });
322
+ setTimeout(() => {
323
+ // Dummy dispatch to rerender the editor and toggle the panel
324
+ client.editorView.dispatch({});
325
+ });
326
+ },
327
+ "editor.hidePanel": (_ctx, id: string) => {
328
+ client.ui.viewDispatch({
329
+ type: "hide-panel",
330
+ id: id as any,
331
+ });
332
+ setTimeout(() => {
333
+ // Dummy dispatch to rerender the editor and toggle the panel
334
+ client.editorView.dispatch({});
335
+ });
336
+ },
337
+ "editor.showProgress": (
338
+ _ctx,
339
+ progressPercentage?: number,
340
+ progressType?: "sync" | "index",
341
+ ) => {
342
+ client.showProgress(progressPercentage, progressType);
343
+ },
344
+ "editor.insertAtPos": (
345
+ _ctx,
346
+ text: string,
347
+ pos: number,
348
+ cursorPlaceHolder = false,
349
+ ) => {
350
+ let cursorPlaceholderPos = -1;
351
+ if (cursorPlaceHolder) {
352
+ cursorPlaceholderPos = text.indexOf("|^|");
353
+ if (cursorPlaceholderPos !== -1) {
354
+ text = text.slice(0, cursorPlaceholderPos) +
355
+ text.slice(cursorPlaceholderPos + 3);
356
+ } else {
357
+ cursorPlaceHolder = false;
358
+ }
359
+ }
360
+ client.editorView.dispatch({
361
+ changes: {
362
+ insert: text,
363
+ from: pos,
364
+ },
365
+ });
366
+ if (cursorPlaceHolder) {
367
+ const cursorPos = pos + cursorPlaceholderPos;
368
+ client.editorView.dispatch({
369
+ selection: {
370
+ anchor: cursorPos,
371
+ },
372
+ effects: [
373
+ EditorView.scrollIntoView(cursorPos),
374
+ ],
375
+ });
376
+ }
377
+ },
378
+ "editor.replaceRange": (
379
+ _ctx,
380
+ from: number,
381
+ to: number,
382
+ text: string,
383
+ cursorPlaceHolder = false,
384
+ ) => {
385
+ let cursorPlaceholderPos = -1;
386
+ if (cursorPlaceHolder) {
387
+ cursorPlaceholderPos = text.indexOf("|^|");
388
+ text = text.slice(0, cursorPlaceholderPos) +
389
+ text.slice(cursorPlaceholderPos + 3);
390
+ }
391
+ client.editorView.dispatch({
392
+ changes: {
393
+ insert: text,
394
+ from: from,
395
+ to: to,
396
+ },
397
+ });
398
+ if (cursorPlaceHolder) {
399
+ const cursorPos = from + cursorPlaceholderPos;
400
+ client.editorView.dispatch({
401
+ selection: {
402
+ anchor: cursorPos,
403
+ },
404
+ effects: [
405
+ EditorView.scrollIntoView(cursorPos),
406
+ ],
407
+ });
408
+ }
409
+ },
410
+ "editor.moveCursor": (_ctx, pos: number, center = false) => {
411
+ client.editorView.dispatch({
412
+ selection: {
413
+ anchor: pos,
414
+ },
415
+ });
416
+ if (center) {
417
+ client.editorView.dispatch({
418
+ effects: [
419
+ EditorView.scrollIntoView(
420
+ pos,
421
+ {
422
+ y: "center",
423
+ },
424
+ ),
425
+ ],
426
+ });
427
+ }
428
+ client.editorView.focus();
429
+ },
430
+ "editor.moveCursorToLine": (
431
+ _ctx,
432
+ line: number,
433
+ column = 1,
434
+ center = false,
435
+ ) => {
436
+ // CodeMirror already keeps information about lines
437
+ const cmLine = client.editorView.state.doc.line(line);
438
+ // How much to move inside the line, column number starts from 1
439
+ const offset = Math.max(0, Math.min(cmLine.length, column - 1));
440
+ // Just reuse the implementation above
441
+ syscalls["editor.moveCursor"](_ctx, cmLine.from + offset, center);
442
+ },
443
+ "editor.setSelection": (_ctx, from: number, to: number) => {
444
+ client.editorView.dispatch({
445
+ selection: {
446
+ anchor: from,
447
+ head: to,
448
+ },
449
+ });
450
+ },
451
+
452
+ "editor.insertAtCursor": (
453
+ _ctx,
454
+ text: string,
455
+ scrollIntoView = false,
456
+ cursorPlaceHolder = false,
457
+ ) => {
458
+ const editorView = client.editorView;
459
+ const from = editorView.state.selection.main.from;
460
+ const cursorPlaceholderPos = text.indexOf("|^|");
461
+ if (cursorPlaceHolder && cursorPlaceholderPos !== -1) {
462
+ text = text.slice(0, cursorPlaceholderPos) +
463
+ text.slice(cursorPlaceholderPos + 3);
464
+ } else {
465
+ cursorPlaceHolder = false;
466
+ }
467
+ editorView.dispatch({
468
+ changes: {
469
+ insert: text,
470
+ from: from,
471
+ },
472
+ selection: {
473
+ anchor: cursorPlaceHolder
474
+ ? from + cursorPlaceholderPos
475
+ : from + text.length,
476
+ },
477
+ scrollIntoView,
478
+ });
479
+ },
480
+ "editor.dispatch": (_ctx, change: Transaction) => {
481
+ client.editorView.dispatch(change);
482
+ },
483
+ "editor.prompt": (
484
+ _ctx,
485
+ message: string,
486
+ defaultValue = "",
487
+ ): Promise<string | undefined> => {
488
+ return client.prompt(message, defaultValue);
489
+ },
490
+ "editor.confirm": (_ctx, message: string): Promise<boolean> => {
491
+ return client.confirm(message);
492
+ },
493
+ "editor.alert": (_ctx, message: string) => {
494
+ alert(message);
495
+ },
496
+ "editor.getUiOption": (_ctx, key: string): any => {
497
+ return (client.ui.viewState.uiOptions as any)[key];
498
+ },
499
+ "editor.setUiOption": (_ctx, key: string, value: any) => {
500
+ client.ui.viewDispatch({
501
+ type: "set-ui-option",
502
+ key,
503
+ value,
504
+ });
505
+ client.reloadEditor();
506
+ },
507
+ "editor.vimEx": (_ctx, exCommand: string) => {
508
+ const cm = vimGetCm(client.editorView);
509
+ if (cm && cm.state.vim) {
510
+ return Vim.handleEx(cm as any, exCommand);
511
+ } else {
512
+ throw new Error("Vim mode not active or not initialized.");
513
+ }
514
+ },
515
+ "editor.configureVimMode": () => {
516
+ // Override the default "o" binding to be more intelligent and follow the markdown editor's behavior
517
+ Vim.mapCommand("o", "action", "newline-continue-markup", {}, {});
518
+ Vim.mapCommand("O", "action", "back-newline-continue-markup", {}, {});
519
+ Vim.unmap("<C-q>", undefined as any);
520
+ Vim.defineAction("newline-continue-markup", (cm) => {
521
+ // Append at end of line
522
+ Vim.handleKey(cm, "A", "+input");
523
+ // Insert newline continuing markup where appropriate
524
+ insertNewlineContinueMarkup(client.editorView) ||
525
+ insertNewlineAndIndent(client.editorView);
526
+ });
527
+ Vim.defineAction("back-newline-continue-markup", (cm) => {
528
+ // Determine current line
529
+ const pos = client.editorView.state.selection.main.from;
530
+ const line = client.editorView.state.doc.lineAt(pos).number;
531
+ if (line === 1) {
532
+ // We're on the top line
533
+ // Go to 0:0
534
+ Vim.handleKey(cm, "0", "+input");
535
+ // Insert a newline
536
+ insertNewline(client.editorView);
537
+ // Go up to the new line
538
+ Vim.handleKey(cm, "k", "+input");
539
+ // Into insert mode
540
+ Vim.handleKey(cm, "i", "+input");
541
+ } else {
542
+ // We're elsewhere in the document
543
+ // Go up
544
+ Vim.handleKey(cm, "k", "+input");
545
+ // Append mode at the end of the line
546
+ Vim.handleKey(cm, "A", "+input");
547
+ // Insert a newline using the continue markup thing
548
+ insertNewlineContinueMarkup(client.editorView) ||
549
+ insertNewlineAndIndent(client.editorView);
550
+ }
551
+ });
552
+
553
+ // Load the config if any
554
+ const config = client.config.get<VimConfig>("vim", {});
555
+ if (config) {
556
+ config.unmap?.forEach((binding) => {
557
+ if (typeof binding === "string") {
558
+ console.log("Unmapping " + binding);
559
+ // @ts-ignore: unmap expects a string for the mode, this is problematic with Ex mappings which requires undefined or false
560
+ Vim.unmap(binding, undefined);
561
+ } else if (binding.key) {
562
+ console.log(
563
+ "Unmapping " + binding.key + " in " + (binding.mode ?? "normal"),
564
+ );
565
+ Vim.unmap(binding.key, binding.mode ?? "normal");
566
+ }
567
+ });
568
+ config.map?.forEach(({ map, to, mode }) => {
569
+ console.log(
570
+ "Mapping " + map + " to " + to + " for " + (mode ?? "normal"),
571
+ );
572
+ Vim.map(map, to, mode ?? "normal");
573
+ });
574
+ config.noremap?.forEach(({ map, to, mode }) => {
575
+ console.log(
576
+ "Noremapping " + map + " to " + to + " for " + (mode ?? "normal"),
577
+ );
578
+ Vim.noremap(map, to, mode ?? "normal");
579
+ });
580
+ config.commands?.forEach(({ ex, command }) => {
581
+ console.log("Mapping command '" + command + "' to Ex " + ex);
582
+ Vim.defineEx(ex, "", () => client.runCommandByName(command));
583
+ });
584
+ } else {
585
+ console.log("No vim config found");
586
+ }
587
+ },
588
+ "editor.openPageNavigator": (
589
+ _ctx,
590
+ mode: "page" | "meta" | "document" | "all" = "page",
591
+ ) => {
592
+ client.startPageNavigate(mode);
593
+ },
594
+ "editor.openCommandPalette": () => {
595
+ client.startCommandPalette();
596
+ },
597
+ "editor.deleteLine": () => {
598
+ deleteLine(client.editorView);
599
+ },
600
+ "editor.toggleComment": () => {
601
+ return toggleComment({
602
+ state: client.editorView.state,
603
+ dispatch: client.editorView.dispatch,
604
+ });
605
+ },
606
+ "editor.moveLineUp": () => {
607
+ return moveLineUp({
608
+ state: client.editorView.state,
609
+ dispatch: client.editorView.dispatch,
610
+ });
611
+ },
612
+ "editor.moveLineDown": () => {
613
+ return moveLineDown({
614
+ state: client.editorView.state,
615
+ dispatch: client.editorView.dispatch,
616
+ });
617
+ },
618
+ // Folding
619
+ "editor.fold": () => {
620
+ foldCode(client.editorView);
621
+ },
622
+ "editor.unfold": () => {
623
+ unfoldCode(client.editorView);
624
+ },
625
+ "editor.toggleFold": () => {
626
+ toggleFold(client.editorView);
627
+ },
628
+ "editor.foldAll": () => {
629
+ foldAll(client.editorView);
630
+ },
631
+ "editor.unfoldAll": () => {
632
+ unfoldAll(client.editorView);
633
+ },
634
+ "editor.undo": () => {
635
+ return undo(client.editorView);
636
+ },
637
+ "editor.redo": () => {
638
+ return redo(client.editorView);
639
+ },
640
+ "editor.openSearchPanel": () => {
641
+ openSearchPanel(client.editorView);
642
+ },
643
+ "editor.copyToClipboard": async (_ctx, data: string | Blob) => {
644
+ try {
645
+ if (typeof data === "string") {
646
+ await navigator.clipboard.writeText(data);
647
+ } else {
648
+ await navigator.clipboard.write([
649
+ new ClipboardItem({ [data.type]: data }),
650
+ ]);
651
+ }
652
+ } catch (e) {
653
+ console.error(e);
654
+ client.flashNotification(`Could not copy to clipboard: ${e}`);
655
+ }
656
+ },
657
+ "editor.sendMessage": (_ctx, type: string, data: any) => {
658
+ if (!client.isDocumentEditor()) return;
659
+
660
+ client.documentEditor.sendPublicMessage({
661
+ type,
662
+ data,
663
+ });
664
+ },
665
+
666
+ "editor.isMobile": () => {
667
+ const mouseDetected = globalThis.matchMedia("(pointer:fine)").matches;
668
+ return !mouseDetected;
669
+ },
670
+ };
671
+
672
+ return syscalls;
673
+ }
@@ -0,0 +1,36 @@
1
+ import type { SysCallMapping } from "../system.ts";
2
+ import type { EventHookI } from "../eventhook.ts";
3
+ import type { EventSubscription } from "@silverbulletmd/silverbullet/type/event";
4
+ import { LuaStackFrame, luaValueToJS } from "../../space_lua/runtime.ts";
5
+ import type { Client } from "../../client.ts";
6
+
7
+ export function eventSyscalls(
8
+ eventHook: EventHookI,
9
+ client: Client,
10
+ ): SysCallMapping {
11
+ return {
12
+ "event.dispatch": (_ctx, eventName: string, data: any) => {
13
+ return eventHook.dispatchEvent(eventName, data);
14
+ },
15
+ "event.listEvents": () => {
16
+ return eventHook.listEvents();
17
+ },
18
+ /**
19
+ * Define a Lua event listener
20
+ */
21
+ "event.listen": (
22
+ _ctx,
23
+ def: EventSubscription,
24
+ ) => {
25
+ // console.log("Registering Lua event listener: ", def.name);
26
+ client.config.insert([
27
+ "eventListeners",
28
+ def.name,
29
+ ], async (...args: any[]) => {
30
+ // Convert return value to JS
31
+ const val = await def.run(...args);
32
+ return luaValueToJS(val, LuaStackFrame.lostFrame);
33
+ });
34
+ },
35
+ };
36
+ }