diff --git a/plugs/core/Library/Std/Lua.md b/plugs/core/Library/Std/Lua.md index d9b81fe0..c24b987d 100644 --- a/plugs/core/Library/Std/Lua.md +++ b/plugs/core/Library/Std/Lua.md @@ -187,45 +187,87 @@ event.listen { } ``` -# Slash templates -Various useful slash templates. +# Slash commands +Useful Lua slash commands for editing Lua files. +## In-lua slash commands ```space-lua -template.defineSlashCommand { - name = "function", - description = "Lua function", - onlyContexts = {"FencedCode:space-lua"}, - template = template.new [==[function |^|() -end]==] -} - -template.defineSlashCommand { - name = "tpl", - description = "Lua template", - onlyContexts = {"FencedCode:space-lua"}, - template = template.new "template.new[==[|^|]==]" -} - -template.defineSlashCommand { - name = "lua-query", - description = "Lua query", +slashcommand.define { + name = "func", + description = "Create a new function", onlyContexts = {"FencedCode:space-lua", "LuaDirective"}, - template = template.new 'query[[from index.tag "|^|"]]' + run = function() + editor.insertAtCursor([==[function |^|() +end]==], false, true) + end } -template.defineSlashCommand { +slashcommand.define { + name = "tpl", + description = "Create a new template", + onlyContexts = {"FencedCode:space-lua", "LuaDirective"}, + run = function() + editor.insertAtCursor("template.new[==[|^|]==]", false, true) + end +} + +slashcommand.define { + name = "lua-query", + description = "Perform a query", + onlyContexts = {"FencedCode:space-lua", "LuaDirective"}, + run = function() + editor.insertAtCursor('query[[from index.tag "|^|"]]', false, true) + end +} + +slashcommand.define { + name = "command", + description = "Define a command", + onlyContexts = {"FencedCode:space-lua", "LuaDirective"}, + run = function() + editor.insertAtCursor([==[command.define { + name = "|^|", + run = function() + editor.flashNotification "Hello world!" + end +}]==], false, true) + end +} + +slashcommand.define { + name = "slash-command", + description = "Define a slash command", + onlyContexts = {"FencedCode:space-lua", "LuaDirective"}, + run = function() + editor.insertAtCursor([==[slashcommand.define { + name = "|^|", + run = function() + editor.insertAtCursor("Hello |^| world!", false, true) + end +}]==], false, true) + end +} +``` + +## Lua slash commands +```space-lua +-- Outside Space Lua contexts +slashcommand.define { name = "space-lua", - description = "Space Lua block", exceptContexts = {"FencedCode:space-lua", "LuaDirective"}, - template = template.new [==[```space-lua + run = function() + editor.insertAtCursor([==[```space-lua |^| -```]==] +```]==], false, true) + end } --- A query embedded in ${} -template.defineSlashCommand { +slashcommand.define { name = "query", - description = "Lua query", exceptContexts = {"FencedCode:space-lua", "LuaDirective"}, - template = function() return '${query[[from index.tag "|^|"]]}' end + run = function() + editor.insertAtCursor('${query[[from index.tag "|^|"]]}', false, true) + end } +``` + diff --git a/plugs/core/Library/Std/Slash.md b/plugs/core/Library/Std/Slash.md index 55113fb7..ade2a507 100644 --- a/plugs/core/Library/Std/Slash.md +++ b/plugs/core/Library/Std/Slash.md @@ -1,3 +1,5 @@ +#meta + Some convenient slash commands for editing Markdown files. ```space-lua diff --git a/plugs/core/Library/Std/Template.md b/plugs/core/Library/Std/Template.md index e2162c3b..b378ea2c 100644 --- a/plugs/core/Library/Std/Template.md +++ b/plugs/core/Library/Std/Template.md @@ -42,28 +42,3 @@ function template.fromPage(name) return template.new(string.trimStart(fm.text)) end - --- Creates a template-based slash command, keys for def are: --- name: name of the slash command --- description: description of the slash command --- onlyContexts: parent AST nodes in which this slash command is available, defaults to everywhere --- exceptContexts: parent AST nodes in which this slash command is not available --- template: template function to apply --- insertAt: position to insert the template into --- match: match string to apply the template to --- matchRegex: match regex to apply the template to -function template.defineSlashCommand(def) - slashcommand.define { - name = def.name, - description = def.description, - onlyContexts = def.onlyContexts, - exceptContexts = def.exceptContexts, - run = function() - system.invokeFunction("template.applySnippetTemplate", def.template(), { - insertAt = def.insertAt, - match = def.match, - matchRegex = def.matchRegex - }) - end - } -end diff --git a/plugs/core/Library/Std/Widgets.md b/plugs/core/Library/Std/Widgets.md new file mode 100644 index 00000000..2a81c37b --- /dev/null +++ b/plugs/core/Library/Std/Widgets.md @@ -0,0 +1,82 @@ +#meta + +This implements the TOC and Linked Mentions widgets. + +## Table of contents +```space-lua +widgets = widgets or {} + +local tocSchema = { + type = "object", + properties = { + minHeaders = { type = "number" }, + } +} + +function widgets.toc(options) + options = options or {} + local validationResult = jsonschema.validateObject(tocSchema, options) + if validationResult then + error(validationResult) + end + options.minHeaders = options.minHeaders or 3 + local text = editor.getText() + local pageName = editor.getCurrentPage() + local lines = string.split(text, "\n") + local tocTexts = {} + for _, line in ipairs(lines) do + local headerStart, headerEnd = string.find(line, "^(%#)+") + if headerStart then + local headerText = line:sub(headerEnd+2) + table.insert(tocTexts, string.rep(" ", (headerEnd-1) * 2) + .. "* [[" .. pageName .. "#" .. headerText .. "|" .. headerText .. "]]") + end + end + if #tocTexts >= options.minHeaders then + local fullText = "# Table of Contents\n" .. table.concat(tocTexts, "\n") + return widget.new { + markdown = fullText + } + else + return "" + end +end + +event.listen { + name = "hooks:renderTopWidgets", + run = function(e) + return widgets.toc() + end +} +``` + +# Linked mentions +```space-lua +widgets = widgets or {} + +local mentionTemplate = template.new [==[ +* [[${_.ref}]]: “${_.snippet}” +]==] + +function widgets.linkedMentions() + local pageName = editor.getCurrentPage() + local linkedMentions = query[[ + from index.tag "link" + where _.page != pageName and _.toPage == pageName + order by page + ]] + if #linkedMentions > 0 then + return widget.new { + markdown = "# Linked Mentions" + .. template.each(linkedMentions, mentionTemplate) + } + end +end + +event.listen { + name = "hooks:renderBottomWidgets", + run = function(e) + return widgets.linkedMentions() + end +} +``` diff --git a/plugs/editor/complete.ts b/plugs/editor/complete.ts index 2a0056ad..9c8f10e6 100644 --- a/plugs/editor/complete.ts +++ b/plugs/editor/complete.ts @@ -7,7 +7,7 @@ import { folderName } from "@silverbulletmd/silverbullet/lib/resolve"; import type { AspiringPageObject } from "../index/page_links.ts"; import type { LuaCollectionQuery } from "$common/space_lua/query_collection.ts"; import { queryLuaObjects } from "../index/api.ts"; -import { lua } from "@silverbulletmd/silverbullet/syscalls"; +import { language, lua } from "@silverbulletmd/silverbullet/syscalls"; // Queries all meta pages (#template tagged or #meta prefixed) let isMetaPageQuery: LuaCollectionQuery | undefined; @@ -200,3 +200,23 @@ export async function pageComplete(completeEvent: CompleteEvent) { }).flat(), }; } + +export async function languageComplete(completeEvent: CompleteEvent) { + const languagePrefix = /^(?:```+|~~~+)(\w*)$/.exec( + completeEvent.linePrefix, + ); + if (!languagePrefix) { + return null; + } + + const allLanguages = await language.listLanguages(); + return { + from: completeEvent.pos - languagePrefix[1].length, + options: allLanguages.map( + (lang) => ({ + label: lang, + type: "language", + }), + ), + }; +} diff --git a/plugs/editor/editor.plug.yaml b/plugs/editor/editor.plug.yaml index 73bfea72..f2c01788 100644 --- a/plugs/editor/editor.plug.yaml +++ b/plugs/editor/editor.plug.yaml @@ -157,6 +157,18 @@ functions: name: "System: Reload" key: Ctrl-Alt-r + refreshAllWidgets: + path: editor.ts:refreshAllWidgets + command: + name: "Widgets: Refresh All" + key: "Alt-q" + requireEditor: page + + languageComplete: + path: complete.ts:languageComplete + events: + - editor:complete + # Navigation linkNavigate: path: "./navigate.ts:linkNavigate" @@ -175,13 +187,6 @@ functions: name: "Navigate: Home" key: "Alt-h" page: "" - navigateToSettings: - path: "./navigate.ts:navigateCommand" - command: - name: "Navigate: Open SETTINGS" - key: "Ctrl-," - mac: "Cmd-," - page: "SETTINGS" moveToPos: path: "./editor.ts:moveToPosCommand" command: diff --git a/plugs/editor/editor.ts b/plugs/editor/editor.ts index 92ff8324..e2a42451 100644 --- a/plugs/editor/editor.ts +++ b/plugs/editor/editor.ts @@ -91,6 +91,10 @@ export async function reloadSystem() { await editor.flashNotification("System and widgets reloaded!"); } +export function refreshAllWidgets() { + codeWidget.refreshAll(); +} + export async function findInPageCommand() { await editor.openSearchPanel(); return false; diff --git a/plugs/index/api.ts b/plugs/index/api.ts index 73a2cd04..7c2466e6 100644 --- a/plugs/index/api.ts +++ b/plugs/index/api.ts @@ -1,5 +1,5 @@ import { datastore, markdown } from "@silverbulletmd/silverbullet/syscalls"; -import type { KV, KvKey, ObjectValue } from "../../plug-api/types.ts"; +import type { KV, KvKey, KvQuery, ObjectValue } from "../../plug-api/types.ts"; import { ttlCache } from "$lib/memory_cache.ts"; import type { LuaCollectionQuery } from "$common/space_lua/query_collection.ts"; import { @@ -131,14 +131,14 @@ export function deleteObject( return datastore.del([tag, cleanKey(object.ref, page)]); } -// export async function query( -// query: KvQuery, -// ): Promise { -// return (await datastore.query({ -// ...query, -// prefix: [indexKey, ...query.prefix ? query.prefix : []], -// })).map(({ key, value }) => ({ key: key.slice(1), value })); -// } +export async function query( + query: KvQuery, +): Promise { + return (await datastore.query({ + ...query, + prefix: [indexKey, ...query.prefix ? query.prefix : []], + })).map(({ key, value }) => ({ key: key.slice(1), value })); +} export function getObjectByRef( page: string, diff --git a/plugs/index/config.ts b/plugs/index/config.ts deleted file mode 100644 index aed4bb6f..00000000 --- a/plugs/index/config.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { IndexTreeEvent, ObjectValue } from "../../plug-api/types.ts"; -import { - findNodeOfType, - traverseTreeAsync, -} from "@silverbulletmd/silverbullet/lib/tree"; -import { indexObjects } from "./api.ts"; -import { YAML } from "@silverbulletmd/silverbullet/syscalls"; - -export type ConfigObject = ObjectValue<{ - key: string; - value: any; -}>; - -export async function indexSpaceConfig({ name, tree }: IndexTreeEvent) { - const allConfigs: ConfigObject[] = []; - - // Collect configs from all `space-config` fenced code blocks - await traverseTreeAsync(tree, async (t): Promise => { - if (t.type !== "FencedCode") { - return false; - } - const codeInfoNode = findNodeOfType(t, "CodeInfo"); - if (!codeInfoNode) { - return true; - } - - const fenceType = codeInfoNode.children![0].text!; - - // If this is not a space-config nor a YAML block in SETTINGS, skip - if ( - !(fenceType === "space-config" || - (name === "SETTINGS" && fenceType === "yaml")) - ) { - return true; - } - - const codeTextNode = findNodeOfType(t, "CodeText"); - if (!codeTextNode) { - // Honestly, this shouldn't happen - return true; - } - const codeText = codeTextNode.children![0].text!; - - try { - const parsedYaml = await YAML.parse(codeText); - if (!parsedYaml) { - return true; - } - // Check if parseYAML contains key-value style data - for (const [key, value] of Object.entries(parsedYaml)) { - allConfigs.push({ - ref: `${name}@${t.from!}:${key}`, - tag: "space-config", - key, - value, - }); - } - } catch (e: any) { - console.error("Error parsing config", codeText, e); - } - return true; - }); - - await indexObjects(name, allConfigs); -} diff --git a/plugs/index/index.plug.yaml b/plugs/index/index.plug.yaml index 21a005d3..ba58e4a7 100644 --- a/plugs/index/index.plug.yaml +++ b/plugs/index/index.plug.yaml @@ -8,10 +8,12 @@ functions: path: api.ts:indexObjects syscall: index.indexObjects env: server + query: + path: api.ts:query + syscall: index.query queryLuaObjects: path: api.ts:queryLuaObjects syscall: index.queryLuaObjects - # Note: not setting env: server to allow for client-side datastore query caching getObjectByRef: path: api.ts:getObjectByRef syscall: index.getObjectByRef @@ -119,12 +121,6 @@ functions: events: - page:index - # Config - indexSpaceConfig: - path: config.ts:indexSpaceConfig - events: - - page:index - # Hashtags indexTags: path: tags.ts:indexTags diff --git a/scripts/generate.sh b/scripts/generate.sh index 290d2218..30fe462c 100755 --- a/scripts/generate.sh +++ b/scripts/generate.sh @@ -3,8 +3,4 @@ LUA_GRAMMAR=common/space_lua/lua.grammar LEZER_GENERATOR_VERSION=1.5.1 -# Generate a patched grammer for just expressions -echo "@top Program { Expression }" > $EXPRESSION_GRAMMAR -tail -n +2 $QUERY_GRAMMAR >> $EXPRESSION_GRAMMAR - deno run -A npm:@lezer/generator@$LEZER_GENERATOR_VERSION $LUA_GRAMMAR -o common/space_lua/parse-lua.js \ No newline at end of file diff --git a/web/client.ts b/web/client.ts index 26922fed..7072db15 100644 --- a/web/client.ts +++ b/web/client.ts @@ -1224,15 +1224,6 @@ export class Client { perm: "rw", } as PageMeta, }; - // Create new page based on a template - this.clientSystem.system.invokeFunction("template.newPage", [pageName]) - .then( - () => { - this.focus(); - }, - ).catch( - console.error, - ); } else { this.flashNotification( `Could not load page ${pageName}: ${e.message}`, diff --git a/web/editor_state.ts b/web/editor_state.ts index 02bdd348..32d2e72d 100644 --- a/web/editor_state.ts +++ b/web/editor_state.ts @@ -120,7 +120,10 @@ export function createEditorState( }), extendedMarkdownLanguage.data.of({ closeBrackets: { - brackets: client.config.get("autoCloseBrackets", "").split( + brackets: client.config.get( + "autoCloseBrackets", + undefined, + )?.split( "", ), }, diff --git a/web/syscalls/editor.ts b/web/syscalls/editor.ts index 78a1f99e..e2f3fb14 100644 --- a/web/syscalls/editor.ts +++ b/web/syscalls/editor.ts @@ -309,16 +309,28 @@ export function editorSyscalls(client: Client): SysCallMapping { }); }, - "editor.insertAtCursor": (_ctx, text: string, scrollIntoView = false) => { + "editor.insertAtCursor": ( + _ctx, + text: string, + scrollIntoView = false, + cursorPlaceHolder = false, + ) => { const editorView = client.editorView; const from = editorView.state.selection.main.from; + const cursorPlaceholderPos = text.indexOf("|^|"); + if (cursorPlaceHolder) { + text = text.slice(0, cursorPlaceholderPos) + + text.slice(cursorPlaceholderPos + 3); + } editorView.dispatch({ changes: { insert: text, from: from, }, selection: { - anchor: from + text.length, + anchor: cursorPlaceHolder + ? from + cursorPlaceholderPos + : from + text.length, }, scrollIntoView, }); diff --git a/website/Library/Core/Page/Space Overview.md b/website/Library/Core/Page/Space Overview.md index 40363fcb..662f3bbe 100644 --- a/website/Library/Core/Page/Space Overview.md +++ b/website/Library/Core/Page/Space Overview.md @@ -9,13 +9,6 @@ This page compiles some useful things about your space and may also be useful fo **Total tags:** {{count({tag select name})}} ``` -# Active [[!silverbullet.md/Space Script]] -```template -{{#each {space-script}}} -* [[{{ref}}]] -{{/each}} -``` - # Active [[!silverbullet.md/Space Lua]] ${template.each(query[[ from index.tag "space-lua"