Breaking Lua API changes:

* tpl -> template.new
* define_command -> command.define
* define_event_listener -> event.listen
* tag -> index.tag

Updated in the docs
pull/1224/head
Zef Hemel 2025-01-19 14:32:11 +01:00
parent 72b4ecdc36
commit 6078452a6c
22 changed files with 275 additions and 237 deletions

View File

@ -62,7 +62,7 @@ export abstract class CommonSystem {
Object.keys(this.scriptEnv.eventHandlers).length, Object.keys(this.scriptEnv.eventHandlers).length,
"event handlers from space-script", "event handlers from space-script",
); );
await this.spaceLuaEnv.reload(this.system, this.scriptEnv); await this.spaceLuaEnv.reload(this.system);
} catch (e: any) { } catch (e: any) {
console.error("Error loading space-script:", e.message); console.error("Error loading space-script:", e.message);
} }

View File

@ -11,7 +11,6 @@ import {
type PageRef, type PageRef,
parsePageRef, parsePageRef,
} from "@silverbulletmd/silverbullet/lib/page_ref"; } from "@silverbulletmd/silverbullet/lib/page_ref";
import type { ScriptEnvironment } from "$common/space_script.ts";
import type { ASTCtx } from "$common/space_lua/ast.ts"; import type { ASTCtx } from "$common/space_lua/ast.ts";
import { buildLuaEnv } from "$common/space_lua_api.ts"; import { buildLuaEnv } from "$common/space_lua_api.ts";
@ -24,7 +23,6 @@ export class SpaceLuaEnvironment {
*/ */
async reload( async reload(
system: System<any>, system: System<any>,
scriptEnv: ScriptEnvironment,
) { ) {
const allScripts: ScriptObject[] = await system.invokeFunction( const allScripts: ScriptObject[] = await system.invokeFunction(
"index.queryObjects", "index.queryObjects",
@ -36,7 +34,7 @@ export class SpaceLuaEnvironment {
}], }],
); );
try { try {
this.env = buildLuaEnv(system, scriptEnv); this.env = buildLuaEnv(system);
const tl = new LuaEnv(); const tl = new LuaEnv();
tl.setLocal("_GLOBAL", this.env); tl.setLocal("_GLOBAL", this.env);
for (const script of allScripts) { for (const script of allScripts) {

View File

@ -267,6 +267,7 @@ export function evalExpression(
sf.withCtx(e.ctx), sf.withCtx(e.ctx),
); );
} }
collection = luaValueToJS(collection);
// Check if collection is a queryable collection // Check if collection is a queryable collection
if (!collection.query) { if (!collection.query) {
// If not, try to convert it to JS and see if it's an array // If not, try to convert it to JS and see if it's an array

View File

@ -104,10 +104,12 @@ Deno.test("Test comment handling", () => {
}); });
Deno.test("Test query parsing", () => { Deno.test("Test query parsing", () => {
parse(`_(query[[from p = tag("page") where p.name == "John" limit 10, 3]])`);
parse(`_(query[[from tag("page") select {name="hello", age=10}]])`);
parse( parse(
`_(query[[from p = tag("page") order by p.lastModified desc, p.name]])`, `_(query[[from p = index.tag("page") where p.name == "John" limit 10, 3]])`,
); );
parse(`_(query[[from p = tag("page") order by p.lastModified]])`); parse(`_(query[[from index.tag("page") select {name="hello", age=10}]])`);
parse(
`_(query[[from p = index.tag("page") order by p.lastModified desc, p.name]])`,
);
parse(`_(query[[from p = index.tag("page") order by p.lastModified]])`);
}); });

View File

@ -1,31 +1,21 @@
import { import {
type ILuaFunction, type ILuaFunction,
jsToLuaValue,
LuaBuiltinFunction, LuaBuiltinFunction,
luaCall, luaCall,
LuaEnv, LuaEnv,
luaGet, luaGet,
LuaMultiRes, LuaMultiRes,
LuaRuntimeError, LuaRuntimeError,
LuaTable, type LuaTable,
luaToString, luaToString,
luaTypeOf, luaTypeOf,
type LuaValue, type LuaValue,
luaValueToJS,
} from "$common/space_lua/runtime.ts"; } from "$common/space_lua/runtime.ts";
import { stringApi } from "$common/space_lua/stdlib/string.ts"; import { stringApi } from "$common/space_lua/stdlib/string.ts";
import { tableApi } from "$common/space_lua/stdlib/table.ts"; import { tableApi } from "$common/space_lua/stdlib/table.ts";
import { osApi } from "$common/space_lua/stdlib/os.ts"; import { osApi } from "$common/space_lua/stdlib/os.ts";
import { jsApi } from "$common/space_lua/stdlib/js.ts"; import { jsApi } from "$common/space_lua/stdlib/js.ts";
import { import { spaceLuaApi } from "$common/space_lua/stdlib/space_lua.ts";
interpolateLuaString,
spaceLuaApi,
} from "$common/space_lua/stdlib/space_lua.ts";
import {
findAllQueryVariables,
type LuaCollectionQuery,
type LuaQueryCollection,
} from "$common/space_lua/query_collection.ts";
import { templateApi } from "$common/space_lua/stdlib/template.ts"; import { templateApi } from "$common/space_lua/stdlib/template.ts";
import { mathApi } from "$common/space_lua/stdlib/math.ts"; import { mathApi } from "$common/space_lua/stdlib/math.ts";
@ -149,68 +139,6 @@ const getmetatableFunction = new LuaBuiltinFunction((_sf, table: LuaTable) => {
return table.metatable; return table.metatable;
}); });
// Non-standard
const tagFunction = new LuaBuiltinFunction(
(sf, tagName: LuaValue): LuaQueryCollection => {
const global = sf.threadLocal.get("_GLOBAL");
if (!global) {
throw new LuaRuntimeError("Global not found", sf);
}
return {
query: async (query: LuaCollectionQuery, env: LuaEnv): Promise<any[]> => {
const localVars = findAllQueryVariables(query).filter((v) =>
!global.has(v) && v !== "_"
);
const scopedVariables: Record<string, any> = {};
for (const v of localVars) {
try {
const jsonValue = await luaValueToJS(env.get(v));
// Ensure this is JSON serializable
JSON.stringify(jsonValue);
scopedVariables[v] = jsonValue;
} catch (e: any) {
console.error(
"Failed to JSON serialize variable",
v,
e,
);
throw new LuaRuntimeError(
`Failed to JSON serialize variable ${v} in query`,
sf,
);
}
}
return (await global.get("datastore").get("query_lua").call(
sf,
[
"idx",
tagName,
],
query,
scopedVariables,
)).toJSArray();
},
};
},
);
const tplFunction = new LuaBuiltinFunction(
(_sf, template: string): ILuaFunction => {
const lines = template.split("\n").map((line) =>
line.replace(/^\s{4}/, "")
);
const processed = lines.join("\n");
return new LuaBuiltinFunction(
async (sf, env: LuaTable | any) => {
if (!(env instanceof LuaTable)) {
env = jsToLuaValue(env);
}
return await interpolateLuaString(sf, processed, env);
},
);
},
);
export function luaBuildStandardEnv() { export function luaBuildStandardEnv() {
const env = new LuaEnv(); const env = new LuaEnv();
// Top-level builtins // Top-level builtins
@ -231,9 +159,6 @@ export function luaBuildStandardEnv() {
env.set("error", errorFunction); env.set("error", errorFunction);
env.set("pcall", pcallFunction); env.set("pcall", pcallFunction);
env.set("xpcall", xpcallFunction); env.set("xpcall", xpcallFunction);
// Non-standard
env.set("tag", tagFunction);
env.set("tpl", tplFunction);
// APIs // APIs
env.set("string", stringApi); env.set("string", stringApi);
env.set("table", tableApi); env.set("table", tableApi);

View File

@ -1,8 +1,10 @@
import { import {
type ILuaFunction, type ILuaFunction,
jsToLuaValue,
LuaBuiltinFunction, LuaBuiltinFunction,
LuaTable, LuaTable,
} from "$common/space_lua/runtime.ts"; } from "$common/space_lua/runtime.ts";
import { interpolateLuaString } from "$common/space_lua/stdlib/space_lua.ts";
export const templateApi = new LuaTable({ export const templateApi = new LuaTable({
each: new LuaBuiltinFunction( each: new LuaBuiltinFunction(
@ -17,4 +19,20 @@ export const templateApi = new LuaTable({
return result.join(""); return result.join("");
}, },
), ),
new: new LuaBuiltinFunction(
(_sf, template: string): ILuaFunction => {
const lines = template.split("\n").map((line) =>
line.replace(/^\s{4}/, "")
);
const processed = lines.join("\n");
return new LuaBuiltinFunction(
async (sf, env: LuaTable | any) => {
if (!(env instanceof LuaTable)) {
env = jsToLuaValue(env);
}
return await interpolateLuaString(sf, processed, env);
},
);
},
),
}); });

View File

@ -1,24 +1,18 @@
import { luaBuildStandardEnv } from "$common/space_lua/stdlib.ts"; import { luaBuildStandardEnv } from "$common/space_lua/stdlib.ts";
import { parsePageRef } from "@silverbulletmd/silverbullet/lib/page_ref"; import { parsePageRef } from "@silverbulletmd/silverbullet/lib/page_ref";
import { import {
jsToLuaValue,
LuaBuiltinFunction,
LuaEnv, LuaEnv,
LuaNativeJSFunction, LuaNativeJSFunction,
LuaStackFrame, LuaStackFrame,
LuaTable, LuaTable,
} from "$common/space_lua/runtime.ts"; } from "$common/space_lua/runtime.ts";
import type { System } from "$lib/plugos/system.ts"; import type { System } from "$lib/plugos/system.ts";
import type { ScriptEnvironment } from "$common/space_script.ts";
import type { CommandDef } from "$lib/command.ts";
export function buildLuaEnv(system: System<any>, scriptEnv: ScriptEnvironment) { export function buildLuaEnv(system: System<any>) {
const env = new LuaEnv(luaBuildStandardEnv()); const env = new LuaEnv(luaBuildStandardEnv());
// Expose all syscalls to Lua // Expose all syscalls to Lua
exposeSyscalls(env, system); exposeSyscalls(env, system);
// Support defining commands and subscriptions from Lua
exposeDefinitions(env, system, scriptEnv);
return env; return env;
} }
@ -45,82 +39,7 @@ function exposeSyscalls(env: LuaEnv, system: System<any>) {
} }
} }
function exposeDefinitions( export function buildThreadLocalEnv(_system: System<any>, globalEnv: LuaEnv) {
env: LuaEnv,
system: System<any>,
scriptEnv: ScriptEnvironment,
) {
// Expose the command registration function to Lua via define_command({name="foo", function() ... end})
env.set(
"define_command",
new LuaBuiltinFunction(
(_sf, def: LuaTable) => {
if (def.get(1) === undefined) {
throw new Error("Callback is required");
}
if (!def.get("name")) {
throw new Error("Name is required");
}
const fn = def.get(1);
console.log(
`[Lua] Registering command '${
def.get("name")
}' (source: ${fn.body.ctx.ref})`,
);
scriptEnv.registerCommand(
{
name: def.get("name"),
key: def.get("key"),
mac: def.get("mac"),
priority: def.get("priority"),
requireMode: def.get("require_mode"),
hide: def.get("hide"),
} as CommandDef,
async (...args: any[]) => {
const tl = await buildThreadLocalEnv(system, env);
const sf = new LuaStackFrame(tl, null);
try {
return await fn.call(sf, ...args.map(jsToLuaValue));
} catch (e: any) {
await handleLuaError(e, system);
}
},
);
},
),
);
env.set(
"define_event_listener",
new LuaBuiltinFunction((_sf, def: LuaTable) => {
if (def.get(1) === undefined) {
throw new Error("Callback is required");
}
if (!def.get("event")) {
throw new Error("Event is required");
}
const fn = def.get(1);
console.log(
`[Lua] Subscribing to event '${
def.get("event")
}' (source: ${fn.body.ctx.ref})`,
);
scriptEnv.registerEventListener(
{ name: def.get("event") },
async (...args: any[]) => {
const tl = await buildThreadLocalEnv(system, env);
const sf = new LuaStackFrame(tl, null);
try {
return await fn.call(sf, ...args.map(jsToLuaValue));
} catch (e: any) {
await handleLuaError(e, system);
}
},
);
}),
);
}
function buildThreadLocalEnv(_system: System<any>, globalEnv: LuaEnv) {
const tl = new LuaEnv(); const tl = new LuaEnv();
// const currentPageMeta = await system.localSyscall( // const currentPageMeta = await system.localSyscall(
// "editor.getCurrentPageMeta", // "editor.getCurrentPageMeta",
@ -131,7 +50,7 @@ function buildThreadLocalEnv(_system: System<any>, globalEnv: LuaEnv) {
return Promise.resolve(tl); return Promise.resolve(tl);
} }
async function handleLuaError(e: any, system: System<any>) { export async function handleLuaError(e: any, system: System<any>) {
console.error( console.error(
"Lua eval exception", "Lua eval exception",
e.message, e.message,

View File

@ -20,7 +20,7 @@ type AttributeExtractorDef = {
tags: string[]; tags: string[];
}; };
type EventListenerDef = { export type EventListenerDef = {
name: string; name: string;
}; };

View File

@ -0,0 +1,50 @@
import type { SysCallMapping } from "$lib/plugos/system.ts";
import type { CommandDef } from "$lib/command.ts";
import { buildThreadLocalEnv, handleLuaError } from "$common/space_lua_api.ts";
import {
type ILuaFunction,
jsToLuaValue,
luaCall,
LuaStackFrame,
luaValueToJS,
} from "$common/space_lua/runtime.ts";
import type { CommonSystem } from "$common/common_system.ts";
export type CallbackCommandDef = CommandDef & {
run: ILuaFunction;
};
export function commandSyscalls(
commonSystem: CommonSystem,
): SysCallMapping {
return {
/**
* Define a Lua command
* @param def - The command definition
* @param luaCallback - The Lua callback
*/
"command.define": (
_ctx,
def: CallbackCommandDef,
) => {
console.log("Registering Lua command: ", def.name);
commonSystem.scriptEnv.registerCommand(
def,
async (...args: any[]) => {
const tl = await buildThreadLocalEnv(
commonSystem.system,
commonSystem.spaceLuaEnv.env,
);
const sf = new LuaStackFrame(tl, null);
try {
return luaValueToJS(
await luaCall(def.run, args.map(jsToLuaValue), sf),
);
} catch (e: any) {
await handleLuaError(e, commonSystem.system);
}
},
);
},
};
}

48
common/syscalls/event.ts Normal file
View File

@ -0,0 +1,48 @@
import type { SysCallMapping } from "$lib/plugos/system.ts";
import type { EventListenerDef } from "$common/space_script.ts";
import { buildThreadLocalEnv, handleLuaError } from "$common/space_lua_api.ts";
import {
type ILuaFunction,
jsToLuaValue,
luaCall,
LuaStackFrame,
luaValueToJS,
} from "$common/space_lua/runtime.ts";
import type { CommonSystem } from "$common/common_system.ts";
export type CallbackEventListener = EventListenerDef & {
run: ILuaFunction;
};
export function eventListenerSyscalls(
commonSystem: CommonSystem,
): SysCallMapping {
return {
/**
* Define a Lua event listener
*/
"event.listen": (
_ctx,
def: CallbackEventListener,
) => {
console.log("Registering Lua event listener: ", def.name);
commonSystem.scriptEnv.registerEventListener(
def,
async (...args: any[]) => {
const tl = await buildThreadLocalEnv(
commonSystem.system,
commonSystem.spaceLuaEnv.env,
);
const sf = new LuaStackFrame(tl, null);
try {
return luaValueToJS(
await luaCall(def.run, args.map(jsToLuaValue), sf),
);
} catch (e: any) {
await handleLuaError(e, commonSystem.system);
}
},
);
},
};
}

View File

@ -3,15 +3,26 @@ import type {
ObjectQuery, ObjectQuery,
ObjectValue, ObjectValue,
} from "@silverbulletmd/silverbullet/types"; } from "@silverbulletmd/silverbullet/types";
import type { SysCallMapping, System } from "$lib/plugos/system.ts"; import type { SysCallMapping } from "$lib/plugos/system.ts";
import type { LuaCollectionQuery } from "$common/space_lua/query_collection.ts"; import {
findAllQueryVariables,
type LuaCollectionQuery,
type LuaQueryCollection,
} from "$common/space_lua/query_collection.ts";
import {
type LuaEnv,
LuaRuntimeError,
type LuaStackFrame,
luaValueToJS,
} from "$common/space_lua/runtime.ts";
import type { CommonSystem } from "$common/common_system.ts";
// These are just wrappers around the system.invokeFunction calls, but they make it easier to use the index // These are just wrappers around the system.invokeFunction calls, but they make it easier to use the index
export function indexSyscalls(system: System<any>): SysCallMapping { export function indexSyscalls(commonSystem: CommonSystem): SysCallMapping {
return { return {
"index.indexObjects": (ctx, page: string, objects: ObjectValue<any>[]) => { "index.indexObjects": (ctx, page: string, objects: ObjectValue<any>[]) => {
return system.syscall(ctx, "system.invokeFunction", [ return commonSystem.system.syscall(ctx, "system.invokeFunction", [
"index.indexObjects", "index.indexObjects",
page, page,
objects, objects,
@ -23,7 +34,7 @@ export function indexSyscalls(system: System<any>): SysCallMapping {
query: ObjectQuery, query: ObjectQuery,
ttlSecs?: number, ttlSecs?: number,
) => { ) => {
return system.syscall(ctx, "system.invokeFunction", [ return commonSystem.system.syscall(ctx, "system.invokeFunction", [
"index.queryObjects", "index.queryObjects",
tag, tag,
query, query,
@ -36,7 +47,7 @@ export function indexSyscalls(system: System<any>): SysCallMapping {
query: LuaCollectionQuery, query: LuaCollectionQuery,
scopedVariables?: Record<string, any>, scopedVariables?: Record<string, any>,
) => { ) => {
return system.syscall(ctx, "system.invokeFunction", [ return commonSystem.system.syscall(ctx, "system.invokeFunction", [
"index.queryLuaObjects", "index.queryLuaObjects",
tag, tag,
query, query,
@ -44,26 +55,68 @@ export function indexSyscalls(system: System<any>): SysCallMapping {
]); ]);
}, },
"index.queryDeleteObjects": (ctx, tag: string, query: ObjectQuery) => { "index.queryDeleteObjects": (ctx, tag: string, query: ObjectQuery) => {
return system.syscall(ctx, "system.invokeFunction", [ return commonSystem.system.syscall(ctx, "system.invokeFunction", [
"index.queryDeleteObjects", "index.queryDeleteObjects",
tag, tag,
query, query,
]); ]);
}, },
"index.query": (ctx, query: KvQuery, variables?: Record<string, any>) => { "index.query": (ctx, query: KvQuery, variables?: Record<string, any>) => {
return system.syscall(ctx, "system.invokeFunction", [ return commonSystem.system.syscall(ctx, "system.invokeFunction", [
"index.query", "index.query",
query, query,
variables, variables,
]); ]);
}, },
"index.getObjectByRef": (ctx, page: string, tag: string, ref: string) => { "index.getObjectByRef": (ctx, page: string, tag: string, ref: string) => {
return system.syscall(ctx, "system.invokeFunction", [ return commonSystem.system.syscall(ctx, "system.invokeFunction", [
"index.getObjectByRef", "index.getObjectByRef",
page, page,
tag, tag,
ref, ref,
]); ]);
}, },
"index.tag": (_ctx, tagName: string): LuaQueryCollection => {
return {
query: async (
query: LuaCollectionQuery,
env: LuaEnv,
sf: LuaStackFrame,
): Promise<any[]> => {
const global = commonSystem.spaceLuaEnv.env;
const localVars = findAllQueryVariables(query).filter((v) =>
!global.has(v) && v !== "_"
);
const scopedVariables: Record<string, any> = {};
for (const v of localVars) {
try {
const jsonValue = await luaValueToJS(env.get(v));
// Ensure this is JSON serializable
JSON.stringify(jsonValue);
scopedVariables[v] = jsonValue;
} catch (e: any) {
console.error(
"Failed to JSON serialize variable",
v,
e,
);
throw new LuaRuntimeError(
`Failed to JSON serialize variable ${v} in query`,
sf,
);
}
}
return (await global.get("datastore").get("query_lua").call(
sf,
[
"idx",
tagName,
],
query,
scopedVariables,
)).toJSArray();
},
};
},
}; };
} }

View File

@ -42,6 +42,8 @@ import { plugPrefix } from "$common/spaces/constants.ts";
import { base64EncodedDataUrl } from "$lib/crypto.ts"; import { base64EncodedDataUrl } from "$lib/crypto.ts";
import type { ConfigContainer } from "../type/config.ts"; import type { ConfigContainer } from "../type/config.ts";
import { indexSyscalls } from "$common/syscalls/index.ts"; import { indexSyscalls } from "$common/syscalls/index.ts";
import { commandSyscalls } from "$common/syscalls/command.ts";
import { eventListenerSyscalls } from "$common/syscalls/event.ts";
const fileListInterval = 30 * 1000; // 30s const fileListInterval = 30 * 1000; // 30s
@ -122,6 +124,7 @@ export class ServerSystem extends CommonSystem {
this.system.registerSyscalls( this.system.registerSyscalls(
[], [],
eventSyscalls(this.eventHook), eventSyscalls(this.eventHook),
eventListenerSyscalls(this),
spaceReadSyscalls(space, this.allKnownFiles), spaceReadSyscalls(space, this.allKnownFiles),
assetSyscalls(this.system), assetSyscalls(this.system),
yamlSyscalls(), yamlSyscalls(),
@ -134,7 +137,8 @@ export class ServerSystem extends CommonSystem {
mqSyscalls(this.mq), mqSyscalls(this.mq),
languageSyscalls(), languageSyscalls(),
jsonschemaSyscalls(), jsonschemaSyscalls(),
indexSyscalls(this.system), indexSyscalls(this),
commandSyscalls(this),
luaSyscalls(), luaSyscalls(),
templateSyscalls(this.ds), templateSyscalls(this.ds),
dataStoreReadSyscalls(this.ds, this), dataStoreReadSyscalls(this.ds, this),

View File

@ -45,6 +45,8 @@ import { plugPrefix } from "$common/spaces/constants.ts";
import { jsonschemaSyscalls } from "$common/syscalls/jsonschema.ts"; import { jsonschemaSyscalls } from "$common/syscalls/jsonschema.ts";
import { luaSyscalls } from "$common/syscalls/lua.ts"; import { luaSyscalls } from "$common/syscalls/lua.ts";
import { indexSyscalls } from "$common/syscalls/index.ts"; import { indexSyscalls } from "$common/syscalls/index.ts";
import { commandSyscalls } from "$common/syscalls/command.ts";
import { eventListenerSyscalls } from "$common/syscalls/event.ts";
const plugNameExtractRegex = /\/(.+)\.plug\.js$/; const plugNameExtractRegex = /\/(.+)\.plug\.js$/;
@ -152,6 +154,7 @@ export class ClientSystem extends CommonSystem {
this.system.registerSyscalls( this.system.registerSyscalls(
[], [],
eventSyscalls(this.eventHook), eventSyscalls(this.eventHook),
eventListenerSyscalls(this),
editorSyscalls(this.client), editorSyscalls(this.client),
spaceReadSyscalls(this.client), spaceReadSyscalls(this.client),
systemSyscalls(this.system, false, this, this.client, this.client), systemSyscalls(this.system, false, this, this.client, this.client),
@ -163,7 +166,8 @@ export class ClientSystem extends CommonSystem {
clientCodeWidgetSyscalls(), clientCodeWidgetSyscalls(),
languageSyscalls(), languageSyscalls(),
jsonschemaSyscalls(), jsonschemaSyscalls(),
indexSyscalls(this.system), indexSyscalls(this),
commandSyscalls(this),
luaSyscalls(), luaSyscalls(),
this.client.syncMode this.client.syncMode
// In sync mode handle locally // In sync mode handle locally

View File

@ -1,5 +1,5 @@
This describes the APIs available in [[Space Lua]] This describes the APIs available in [[Space Lua]]
${template.each(query[[ ${template.each(query[[
from tag("page") where string.startswith(name, "API/") from index.tag("page") where string.startswith(name, "API/")
]], render.page)} ]], render.page)}

14
website/API/command.md Normal file
View File

@ -0,0 +1,14 @@
APIs related to editor commands
### command.define(command_def)
Registers a command.
Example:
```lua
command.define {
name = "My custom command",
run = function()
editor.flash_notification "Triggered my custom command"
end
}
```

View File

@ -4,13 +4,25 @@ The Event API provides functions for working with SilverBullet's event bus syste
## Event Operations ## Event Operations
### event.dispatch_event(event_name, data, timeout) ### event.listen(listener_def)
Register an event listener.
```lua
event.listen {
name = "my-event",
run = function(e)
print("Data", e.data)
end
}
```
### event.dispatch(event_name, data, timeout)
Triggers an event on the SilverBullet event bus. Event handlers can return values, which are accumulated and returned to the caller. Triggers an event on the SilverBullet event bus. Event handlers can return values, which are accumulated and returned to the caller.
Example: Example:
```lua ```lua
-- Simple event dispatch -- Simple event dispatch
event.dispatch_event("custom.event", {message = "Hello"}) event.dispatch("custom.event", {message = "Hello"})
-- Event dispatch with timeout and response handling -- Event dispatch with timeout and response handling
local responses = event.dispatch_event("data.request", {id = 123}, 5000) local responses = event.dispatch_event("data.request", {id = 123}, 5000)

View File

@ -201,23 +201,3 @@ rawset(t, "foo", "bar") -- bypasses the metamethod
print(t.foo) -- prints: "bar" print(t.foo) -- prints: "bar"
``` ```
# Space Lua specific
## tag(name)
Returns a given [[Objects#Tags]] as a query collection, to be queried using [[Space Lua/Lua Integrated Query]].
Example:
${query[[from tag("page") limit 1]]}
## tpl(template)
Returns a template function that can be used to render a template. Conventionally, a template string is put between `[==[` and `]==]` as string delimiters.
Example:
```space-lua
examples = examples or {}
examples.say_hello = tpl[==[Hello ${name}!]==]
```
And its use: ${examples.say_hello {name="Pete"}}

View File

@ -2,7 +2,14 @@ The `index` API provides functions for interacting with SilverBullet's [[Objects
## Object Operations ## Object Operations
### index.index_objects(page, objects) ## index.tag(name)
Returns a given [[Objects#Tags]] as a query collection, to be queried using [[Space Lua/Lua Integrated Query]].
Example:
${query[[from index.tag("page") limit 1]]}
## index.index_objects(page, objects)
Indexes an array of objects for a specific page. Indexes an array of objects for a specific page.
Example: Example:
@ -14,7 +21,7 @@ local objects = {
index.index_objects("my page", objects) index.index_objects("my page", objects)
``` ```
### index.query_lua_objects(tag, query, scoped_variables?) ## index.query_lua_objects(tag, query, scoped_variables?)
Queries objects using a Lua-based collection query. Queries objects using a Lua-based collection query.
Example: Example:
@ -22,7 +29,7 @@ Example:
local tasks = index.query_lua_objects("mytask", {limit=3}) local tasks = index.query_lua_objects("mytask", {limit=3})
``` ```
### index.get_object_by_ref(page, tag, ref) ## index.get_object_by_ref(page, tag, ref)
Retrieves a specific object by its reference. Retrieves a specific object by its reference.
Example: Example:

View File

@ -1,10 +1,23 @@
Template functions that use the [[API/global#tpl(template)]] function. Template functions that use the [[API/template#template.new(template)]] function.
## template.new(template)
Returns a template function that can be used to render a template. Conventionally, a template string is put between `[==[` and `]==]` as string delimiters.
Example:
```space-lua
examples = examples or {}
examples.say_hello = template.new[==[Hello ${name}!]==]
```
And its use: ${examples.say_hello {name="Pete"}}
## template.each(collection, template) ## template.each(collection, template)
Iterates over a collection and renders a template for each item. Iterates over a collection and renders a template for each item.
Example: Example:
${template.each(query[[from tag "page" limit 3]], tpl[==[ ${template.each(query[[from index.tag "page" limit 3]], template.new[==[
* ${name} * ${name}
]==])} ]==])}

View File

@ -1,8 +0,0 @@
Defines some core useful templates for use in [[Space Lua]]
```space-lua
render = render or {}
render.page = tpl[==[
* [[${name}]]
]==]
```

View File

@ -60,7 +60,7 @@ For example: 10 + 2 = ${adder(10, 2)} (Alt-click, or select to see the expressio
Space Lua has a feature called [[Space Lua/Lua Integrated Query]], which integrate SQL-like queries into Lua. By using this feature, you can easily replicate [[Live Queries]]. More detail in [[Space Lua/Lua Integrated Query]], but heres a small example querying the last 3 modifies pages: Space Lua has a feature called [[Space Lua/Lua Integrated Query]], which integrate SQL-like queries into Lua. By using this feature, you can easily replicate [[Live Queries]]. More detail in [[Space Lua/Lua Integrated Query]], but heres a small example querying the last 3 modifies pages:
${query[[ ${query[[
from tag "page" from index.tag "page"
order by lastModified desc order by lastModified desc
select name select name
limit 3 limit 3
@ -101,12 +101,12 @@ ${marquee "Finally, marqeeeeeeee!"}
Oh boy, the times we live in! Oh boy, the times we live in!
## Commands ## Commands
Custom commands can be defined using `define_command`: Custom commands can be defined using `command.define`:
```space-lua ```space-lua
define_command { command.define {
name = "Hello World"; name = "Hello World",
function() run = function()
editor.flash_notification "Hello world!" editor.flash_notification "Hello world!"
event.dispatch("my-custom-event", {name="Pete"}) event.dispatch("my-custom-event", {name="Pete"})
end end
@ -116,15 +116,14 @@ define_command {
Try it: {[Hello World]} Try it: {[Hello World]}
## Event listeners ## Event listeners
You can listen to events using `define_event_listener`: You can listen to events using `event.listen`:
```space-lua ```space-lua
define_event_listener { event.listen {
event = "my-custom-event"; name = "my-custom-event";
function(e) run = function(e)
editor.flash_notification("Custom triggered: " editor.flash_notification("Custom triggered: "
.. e.data.name .. e.data.name)
.. " on page " .. _CTX.pageMeta.name)
end end
} }
``` ```
@ -138,7 +137,6 @@ Space Lua currently introduces a few new features on top core Lua:
## Thread locals ## Thread locals
Theres a magic `_CTX` global variable available from which you can access useful context-specific values. Currently the following keys are available: Theres a magic `_CTX` global variable available from which you can access useful context-specific values. Currently the following keys are available:
* `_CTX.pageMeta` contains a reference to the loaded page metadata (can be `nil` when not yet loaded)
* `_CTX.GLOBAL` providing access to the global scope * `_CTX.GLOBAL` providing access to the global scope
# API # API

View File

@ -19,10 +19,10 @@ Unlike [[Query Language]] which operates on [[Objects]] only, LIQ can operate on
For instance, to sort a list of numbers in descending order: For instance, to sort a list of numbers in descending order:
${query[[from n = {1, 2, 3} order by n desc]]} ${query[[from n = {1, 2, 3} order by n desc]]}
However, in most cases youll use it in conjunction with [[../API/global#tag(name)]]. Heres an example querying the 3 pages that were last modified: However, in most cases youll use it in conjunction with [[API/index#index.tag(name)]]. Heres an example querying the 3 pages that were last modified:
${query[[ ${query[[
from p = tag "page" from p = index.tag "page"
order by p.lastModified desc order by p.lastModified desc
select p.name select p.name
limit 3 limit 3
@ -52,8 +52,8 @@ ${query[[from {1, 2, 3} select _]]}
With variable binding: With variable binding:
${query[[from n = {1, 2, 3} select n]]} ${query[[from n = {1, 2, 3} select n]]}
A more realistic example using `tag`: A more realistic example using `index.tag`:
${query[[from tag "page" order by lastModified select name limit 3]]} ${query[[from index.tag "page" order by lastModified select name limit 3]]}
## `where <expression>` ## `where <expression>`
The `where` clause allows you to filter data. When the expression evaluated to a truthy value, the item is included in the result. The `where` clause allows you to filter data. When the expression evaluated to a truthy value, the item is included in the result.
@ -64,14 +64,14 @@ ${query[[from {1, 2, 3, 4, 5} where _ > 2]]}
Or to select all pages tagged with `#meta`: Or to select all pages tagged with `#meta`:
${query[[from tag "page" where table.includes(tags, "meta")]]} ${query[[from index.tag "page" where table.includes(tags, "meta")]]}
## `order by <expression> [desc]` ## `order by <expression> [desc]`
The `order by` clause allows you to sort data, when `desc` is specified it reverts the sort order. The `order by` clause allows you to sort data, when `desc` is specified it reverts the sort order.
As an example, the last 3 modified pages: As an example, the last 3 modified pages:
${query[[ ${query[[
from tag "page" from index.tag "page"
order by lastModified desc order by lastModified desc
select name select name
limit 3 limit 3
@ -97,11 +97,11 @@ Double each number:
${query[[from {1, 2, 3} select _ * 2]]} ${query[[from {1, 2, 3} select _ * 2]]}
Extract just the name from pages: Extract just the name from pages:
${query[[from tag "page" select _.name limit 3]]} ${query[[from index.tag "page" select _.name limit 3]]}
You can also return tables or other complex values: You can also return tables or other complex values:
${query[[ ${query[[
from p = tag "page" from p = index.tag "page"
select { select {
name = p.name, name = p.name,
modified = p.lastModified modified = p.lastModified