silverbullet/common/space_lua_api.ts

142 lines
4.2 KiB
TypeScript
Raw Normal View History

2024-10-11 21:34:27 +08:00
import { luaBuildStandardEnv } from "$common/space_lua/stdlib.ts";
import { parsePageRef } from "@silverbulletmd/silverbullet/lib/page_ref";
import {
jsToLuaValue,
LuaBuiltinFunction,
LuaEnv,
LuaNativeJSFunction,
2024-10-20 21:06:23 +08:00
LuaStackFrame,
2024-10-11 21:34:27 +08:00
LuaTable,
} from "$common/space_lua/runtime.ts";
import type { System } from "$lib/plugos/system.ts";
import type { ScriptEnvironment } from "$common/space_script.ts";
2024-10-15 02:59:10 +08:00
import type { CommandDef } from "$lib/command.ts";
2024-10-11 21:34:27 +08:00
export function buildLuaEnv(system: System<any>, scriptEnv: ScriptEnvironment) {
const env = new LuaEnv(luaBuildStandardEnv());
2024-10-13 21:14:22 +08:00
// Expose all syscalls to Lua
exposeSyscalls(env, system);
// Support defining commands and subscriptions from Lua
exposeDefinitions(env, system, scriptEnv);
return env;
}
function exposeSyscalls(env: LuaEnv, system: System<any>) {
2024-10-11 21:34:27 +08:00
// Expose all syscalls to Lua
2024-10-20 21:06:23 +08:00
const nativeFs = new LuaStackFrame(env, null);
2024-10-11 21:34:27 +08:00
for (const syscallName of system.registeredSyscalls.keys()) {
const [ns, fn] = syscallName.split(".");
2024-10-20 21:06:23 +08:00
if (!env.has(ns)) {
env.set(ns, new LuaTable(), nativeFs);
2024-10-11 21:34:27 +08:00
}
const luaFn = new LuaNativeJSFunction((...args) => {
return system.localSyscall(syscallName, args);
});
// Register the function with the same name as the syscall both in regular and snake_case
2024-10-20 21:06:23 +08:00
env.get(ns, nativeFs).set(fn, luaFn, nativeFs);
env.get(ns, nativeFs).set(snakeCase(fn), luaFn, nativeFs);
2024-10-11 21:34:27 +08:00
}
2024-10-13 21:14:22 +08:00
}
2024-10-11 21:34:27 +08:00
2024-10-13 21:14:22 +08:00
function exposeDefinitions(
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",
2024-10-11 21:34:27 +08:00
new LuaBuiltinFunction(
2024-10-20 21:06:23 +08:00
(_sf, def: LuaTable) => {
2024-10-11 21:34:27 +08:00
if (def.get(1) === undefined) {
throw new Error("Callback is required");
}
if (!def.get("name")) {
throw new Error("Name is required");
}
console.log("Registering Lua command", def.get("name"));
scriptEnv.registerCommand(
2024-10-15 02:59:10 +08:00
{
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,
2024-10-11 21:34:27 +08:00
async (...args: any[]) => {
2024-10-20 21:06:23 +08:00
const sf = new LuaStackFrame(new LuaEnv(), null);
2024-10-11 21:34:27 +08:00
try {
2024-10-20 21:06:23 +08:00
return await def.get(1).call(sf, ...args.map(jsToLuaValue));
2024-10-11 21:34:27 +08:00
} catch (e: any) {
2024-10-13 21:14:22 +08:00
await handleLuaError(e, system);
2024-10-11 21:34:27 +08:00
}
},
);
},
),
);
env.set(
"define_event_listener",
2024-10-20 21:06:23 +08:00
new LuaBuiltinFunction((_sf, def: LuaTable) => {
2024-10-13 21:14:22 +08:00
if (def.get(1) === undefined) {
throw new Error("Callback is required");
}
if (!def.get("event")) {
throw new Error("Event is required");
}
console.log("Subscribing to Lua event", def.get("event"));
scriptEnv.registerEventListener(
{ name: def.get("event") },
async (...args: any[]) => {
2024-10-20 21:06:23 +08:00
const sf = new LuaStackFrame(new LuaEnv(), null);
2024-10-13 21:14:22 +08:00
try {
2024-10-20 21:06:23 +08:00
return await def.get(1).call(sf, ...args.map(jsToLuaValue));
2024-10-13 21:14:22 +08:00
} catch (e: any) {
await handleLuaError(e, system);
}
},
);
}),
);
}
async function handleLuaError(e: any, system: System<any>) {
console.error(
"Lua eval exception",
e.message,
e.context,
);
if (e.context && e.context.ref) {
// We got an error and actually know where it came from, let's navigate there to help debugging
const pageRef = parsePageRef(e.context.ref);
await system.localSyscall(
"editor.flashNotification",
[
`Lua error: ${e.message}`,
"error",
],
);
await system.localSyscall(
"editor.flashNotification",
[
`Navigating to the place in the code where this error occurred in ${pageRef.page}`,
"info",
],
);
await system.localSyscall("editor.navigate", [
{
page: pageRef.page,
pos: pageRef.pos + e.context.from +
"```space-lua\n".length,
},
]);
}
2024-10-11 21:34:27 +08:00
}
function snakeCase(s: string) {
return s.replace(/([A-Z])/g, "_$1").toLowerCase();
}