diff --git a/common/space_lua/runtime.ts b/common/space_lua/runtime.ts index 61228a7a..b7214607 100644 --- a/common/space_lua/runtime.ts +++ b/common/space_lua/runtime.ts @@ -315,6 +315,11 @@ export class LuaTable implements ILuaSettable, ILuaGettable { return this.arrayPart.length; } + hasKeys(): boolean { + return !!(Object.keys(this.stringKeys).length > 0 || + this.arrayPart.length > 0 || (this.otherKeys && this.otherKeys.size > 0)); + } + keys(): any[] { const keys: any[] = Object.keys(this.stringKeys); for (let i = 0; i < this.arrayPart.length; i++) { @@ -695,7 +700,7 @@ export function luaTruthy(value: any): boolean { return false; } if (value instanceof LuaTable) { - return value.length > 0; + return value.hasKeys(); } return true; } @@ -773,6 +778,17 @@ export function jsToLuaValue(value: any): any { return value; } else if (value instanceof Uint8Array || value instanceof ArrayBuffer) { return value; + } else if (Array.isArray(value) && "index" in value && "input" in value) { + // This is a RegExpMatchArray + const regexMatch = value as RegExpMatchArray; + const regexMatchTable = new LuaTable(); + for (let i = 0; i < regexMatch.length; i++) { + regexMatchTable.set(i + 1, regexMatch[i]); + } + regexMatchTable.set("index", regexMatch.index); + regexMatchTable.set("input", regexMatch.input); + regexMatchTable.set("groups", regexMatch.groups); + return regexMatchTable; } else if (Array.isArray(value)) { const table = new LuaTable(); for (let i = 0; i < value.length; i++) { diff --git a/common/space_lua/stdlib/string.ts b/common/space_lua/stdlib/string.ts index cd5412e9..ef7e0195 100644 --- a/common/space_lua/stdlib/string.ts +++ b/common/space_lua/stdlib/string.ts @@ -1,4 +1,5 @@ import { + jsToLuaValue, LuaBuiltinFunction, luaCall, LuaMultiRes, @@ -213,4 +214,13 @@ export const stringApi = new LuaTable({ trim_end: new LuaBuiltinFunction((_sf, s: string) => { return s.trimEnd(); }), + match_regex: new LuaBuiltinFunction((_sf, s: string, pattern: string) => { + const regex = new RegExp(pattern); + const result = s.match(regex); + return jsToLuaValue(result); + // if (!result) { + // return new LuaMultiRes([]); + // } + // return new LuaMultiRes(result.slice(1)); + }), }); diff --git a/common/space_lua/stdlib/table.ts b/common/space_lua/stdlib/table.ts index 29d3c994..cb275372 100644 --- a/common/space_lua/stdlib/table.ts +++ b/common/space_lua/stdlib/table.ts @@ -1,6 +1,7 @@ import { type ILuaFunction, LuaBuiltinFunction, + type LuaEnv, luaEquals, LuaRuntimeError, LuaTable, @@ -17,7 +18,10 @@ export const tableApi = new LuaTable({ * @returns The concatenated string. */ concat: new LuaBuiltinFunction( - (_sf, tbl: LuaTable, sep?: string, i?: number, j?: number) => { + (_sf, tbl: LuaTable | any[], sep?: string, i?: number, j?: number) => { + if (Array.isArray(tbl)) { + return tbl.join(sep); + } sep = sep ?? ""; i = i ?? 1; j = j ?? tbl.length; @@ -70,7 +74,8 @@ export const tableApi = new LuaTable({ * @param tbl - The table to get the keys from. * @returns The keys of the table. */ - keys: new LuaBuiltinFunction((_sf, tbl: LuaTable) => { + keys: new LuaBuiltinFunction((_sf, tbl: LuaTable | LuaEnv) => { + console.log("Keys", tbl); return tbl.keys(); }), /** diff --git a/common/syscalls/command.ts b/common/syscalls/command.ts index 491dfeb8..6cd6e2d2 100644 --- a/common/syscalls/command.ts +++ b/common/syscalls/command.ts @@ -38,7 +38,7 @@ export function commandSyscalls( const sf = new LuaStackFrame(tl, null); try { return luaValueToJS( - await luaCall(def.run, args.map(jsToLuaValue), sf), + await luaCall(def.run, args.map(jsToLuaValue), {}, sf), ); } catch (e: any) { await handleLuaError(e, commonSystem.system); @@ -60,7 +60,7 @@ export function commandSyscalls( const sf = new LuaStackFrame(tl, null); try { return luaValueToJS( - await luaCall(def.run, args.map(jsToLuaValue), sf), + await luaCall(def.run, args.map(jsToLuaValue), {}, sf), ); } catch (e: any) { await handleLuaError(e, commonSystem.system); diff --git a/common/syscalls/event.ts b/common/syscalls/event.ts index e474f202..baad7f91 100644 --- a/common/syscalls/event.ts +++ b/common/syscalls/event.ts @@ -35,9 +35,10 @@ export function eventListenerSyscalls( ); const sf = new LuaStackFrame(tl, null); try { - return luaValueToJS( - await luaCall(def.run, args.map(jsToLuaValue), sf), + const val = luaValueToJS( + await luaCall(def.run, args.map(jsToLuaValue), {}, sf), ); + return val; } catch (e: any) { await handleLuaError(e, commonSystem.system); } diff --git a/lib/manifest.ts b/lib/manifest.ts index c2210eda..1f818896 100644 --- a/lib/manifest.ts +++ b/lib/manifest.ts @@ -53,6 +53,8 @@ export type SlashCommandDef = { name: string; description?: string; boost?: number; + // Parent AST nodes in which this slash command is available + contexts?: string[]; }; export type SlashCommandHookT = { slashCommand?: SlashCommandDef; diff --git a/plugs/core/Library/Std/Config.md b/plugs/core/Library/Std/Config.md index e8af163b..96e11f7c 100644 --- a/plugs/core/Library/Std/Config.md +++ b/plugs/core/Library/Std/Config.md @@ -19,7 +19,7 @@ function config.set(key, value) error("Config key not defined: " .. key) end if schema != true then - result = jsonschema.validate_object(schema, value) + local result = jsonschema.validate_object(schema, value) if result != nil then error("Validation error (" .. key .. "): " .. result) end diff --git a/web/client.ts b/web/client.ts index 90cb455c..605a272b 100644 --- a/web/client.ts +++ b/web/client.ts @@ -2,7 +2,7 @@ import type { CompletionContext, CompletionResult, } from "@codemirror/autocomplete"; -import type { Compartment } from "@codemirror/state"; +import type { Compartment, EditorState } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; import { syntaxTree } from "@codemirror/language"; import { compile as gitIgnoreCompiler } from "gitignore-parser"; @@ -892,21 +892,13 @@ export class Client implements ConfigContainer { const linePrefix = line.text.slice(0, selection.from - line.from); // Build up list of parent nodes, some completions need this - const parentNodes: string[] = []; const sTree = syntaxTree(editorState); - const currentNode = sTree.resolveInner(selection.from); - if (currentNode) { - let node: SyntaxNode | null = currentNode; - do { - if (node.name === "FencedCode" || node.name === "FrontMatter") { - const body = editorState.sliceDoc(node.from + 3, node.to - 3); - parentNodes.push(`${node.name}:${body}`); - } else { - parentNodes.push(node.name); - } - node = node.parent; - } while (node); - } + const currentNode = sTree.resolveInner(editorState.selection.main.from); + + const parentNodes: string[] = this.extractParentNodes( + editorState, + currentNode, + ); // Dispatch the event const results = await this.dispatchAppEvent(eventName, { @@ -949,6 +941,23 @@ export class Client implements ConfigContainer { return currentResult; } + public extractParentNodes(editorState: EditorState, currentNode: SyntaxNode) { + const parentNodes: string[] = []; + if (currentNode) { + let node: SyntaxNode | null = currentNode; + do { + if (node.name === "FencedCode" || node.name === "FrontMatter") { + const body = editorState.sliceDoc(node.from + 3, node.to - 3); + parentNodes.push(`${node.name}:${body}`); + } else { + parentNodes.push(node.name); + } + node = node.parent; + } while (node); + } + return parentNodes; + } + editorComplete( context: CompletionContext, ): Promise { diff --git a/web/hooks/slash_command.ts b/web/hooks/slash_command.ts index f2554dbd..2ba648a9 100644 --- a/web/hooks/slash_command.ts +++ b/web/hooks/slash_command.ts @@ -108,7 +108,17 @@ export class SlashCommandHook implements Hook { return null; } + // Check if the slash command is available in the current context + const parentNodes = this.editor.extractParentNodes(ctx.state, currentNode); + // console.log("Parent nodes", parentNodes); for (const def of this.slashCommands.values()) { + if ( + def.slashCommand.contexts && !def.slashCommand.contexts.some( + (context) => parentNodes.some((node) => node.startsWith(context)), + ) + ) { + continue; + } options.push({ label: def.slashCommand.name, detail: def.slashCommand.description,