Lua work
parent
29e127ca78
commit
c2fea2d25b
|
@ -62,14 +62,16 @@ export class SpaceLuaEnvironment {
|
||||||
for (const globalName of this.env.keys()) {
|
for (const globalName of this.env.keys()) {
|
||||||
const value = this.env.get(globalName);
|
const value = this.env.get(globalName);
|
||||||
if (value instanceof LuaFunction) {
|
if (value instanceof LuaFunction) {
|
||||||
console.log("Now registering Lua function", globalName);
|
console.log(
|
||||||
|
`[Lua] Registering global function '${globalName}' (source: ${value.body.ctx.ref})`,
|
||||||
|
);
|
||||||
scriptEnv.registerFunction({ name: globalName }, (...args: any[]) => {
|
scriptEnv.registerFunction({ name: globalName }, (...args: any[]) => {
|
||||||
const sf = new LuaStackFrame(new LuaEnv(), value.body.ctx);
|
const sf = new LuaStackFrame(new LuaEnv(), value.body.ctx);
|
||||||
return luaValueToJS(value.call(sf, ...args.map(jsToLuaValue)));
|
return luaValueToJS(value.call(sf, ...args.map(jsToLuaValue)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("Loaded", allScripts.length, "Lua scripts");
|
console.log("[Lua] Loaded", allScripts.length, "scripts");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
LuaEnv,
|
LuaEnv,
|
||||||
LuaNativeJSFunction,
|
LuaNativeJSFunction,
|
||||||
LuaStackFrame,
|
LuaStackFrame,
|
||||||
|
LuaTable,
|
||||||
luaValueToJS,
|
luaValueToJS,
|
||||||
singleResult,
|
singleResult,
|
||||||
} from "./runtime.ts";
|
} from "./runtime.ts";
|
||||||
|
@ -11,9 +12,9 @@ import type { LuaBlock, LuaFunctionCallStatement } from "./ast.ts";
|
||||||
import { evalExpression, evalStatement } from "./eval.ts";
|
import { evalExpression, evalStatement } from "./eval.ts";
|
||||||
import { luaBuildStandardEnv } from "$common/space_lua/stdlib.ts";
|
import { luaBuildStandardEnv } from "$common/space_lua/stdlib.ts";
|
||||||
|
|
||||||
function evalExpr(s: string, e = new LuaEnv()): any {
|
function evalExpr(s: string, e = new LuaEnv(), sf?: LuaStackFrame): any {
|
||||||
const node = parse(`e(${s})`).statements[0] as LuaFunctionCallStatement;
|
const node = parse(`e(${s})`).statements[0] as LuaFunctionCallStatement;
|
||||||
const sf = new LuaStackFrame(e, node.ctx);
|
sf = sf || new LuaStackFrame(e, node.ctx);
|
||||||
return evalExpression(
|
return evalExpression(
|
||||||
node.call.args[0],
|
node.call.args[0],
|
||||||
e,
|
e,
|
||||||
|
@ -277,3 +278,149 @@ Deno.test("Statement evaluation", async () => {
|
||||||
luaBuildStandardEnv(),
|
luaBuildStandardEnv(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test("Thread local _CTX", async () => {
|
||||||
|
const env = new LuaEnv();
|
||||||
|
const threadLocal = new LuaEnv();
|
||||||
|
threadLocal.setLocal("threadValue", "test123");
|
||||||
|
|
||||||
|
const sf = new LuaStackFrame(threadLocal, null);
|
||||||
|
|
||||||
|
await evalBlock(
|
||||||
|
`
|
||||||
|
function test()
|
||||||
|
return _CTX.threadValue
|
||||||
|
end
|
||||||
|
`,
|
||||||
|
env,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await evalExpr("test()", env, sf);
|
||||||
|
assertEquals(singleResult(result), "test123");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Thread local _CTX - advanced cases", async () => {
|
||||||
|
// Create environment with standard library
|
||||||
|
const env = new LuaEnv(luaBuildStandardEnv());
|
||||||
|
const threadLocal = new LuaEnv();
|
||||||
|
|
||||||
|
// Set up some thread local values
|
||||||
|
threadLocal.setLocal("user", "alice");
|
||||||
|
threadLocal.setLocal("permissions", new LuaTable());
|
||||||
|
threadLocal.get("permissions").set("admin", true);
|
||||||
|
threadLocal.setLocal("data", {
|
||||||
|
id: 123,
|
||||||
|
settings: { theme: "dark" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const sf = new LuaStackFrame(threadLocal, null);
|
||||||
|
|
||||||
|
// Test 1: Nested function access
|
||||||
|
await evalBlock(
|
||||||
|
`
|
||||||
|
function outer()
|
||||||
|
local function inner()
|
||||||
|
return _CTX.user
|
||||||
|
end
|
||||||
|
return inner()
|
||||||
|
end
|
||||||
|
`,
|
||||||
|
env,
|
||||||
|
);
|
||||||
|
assertEquals(await evalExpr("outer()", env, sf), "alice");
|
||||||
|
|
||||||
|
// Test 2: Table access and modification
|
||||||
|
await evalBlock(
|
||||||
|
`
|
||||||
|
function checkAdmin()
|
||||||
|
return _CTX.permissions.admin
|
||||||
|
end
|
||||||
|
|
||||||
|
function revokeAdmin()
|
||||||
|
_CTX.permissions.admin = false
|
||||||
|
return _CTX.permissions.admin
|
||||||
|
end
|
||||||
|
`,
|
||||||
|
env,
|
||||||
|
);
|
||||||
|
assertEquals(await evalExpr("checkAdmin()", env, sf), true);
|
||||||
|
assertEquals(await evalExpr("revokeAdmin()", env, sf), false);
|
||||||
|
assertEquals(threadLocal.get("permissions").get("admin"), false);
|
||||||
|
|
||||||
|
// Test 3: Complex data structures
|
||||||
|
await evalBlock(
|
||||||
|
`
|
||||||
|
function getNestedData()
|
||||||
|
return _CTX.data.settings.theme
|
||||||
|
end
|
||||||
|
|
||||||
|
function updateTheme(newTheme)
|
||||||
|
_CTX.data.settings.theme = newTheme
|
||||||
|
return _CTX.data.settings.theme
|
||||||
|
end
|
||||||
|
`,
|
||||||
|
env,
|
||||||
|
);
|
||||||
|
assertEquals(await evalExpr("getNestedData()", env, sf), "dark");
|
||||||
|
assertEquals(await evalExpr("updateTheme('light')", env, sf), "light");
|
||||||
|
|
||||||
|
// Test 4: Multiple thread locals
|
||||||
|
const threadLocal2 = new LuaEnv();
|
||||||
|
threadLocal2.setLocal("user", "bob");
|
||||||
|
const sf2 = new LuaStackFrame(threadLocal2, null);
|
||||||
|
|
||||||
|
await evalBlock(
|
||||||
|
`
|
||||||
|
function getUser()
|
||||||
|
return _CTX.user
|
||||||
|
end
|
||||||
|
`,
|
||||||
|
env,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Same function, different thread contexts
|
||||||
|
assertEquals(await evalExpr("getUser()", env, sf), "alice");
|
||||||
|
assertEquals(await evalExpr("getUser()", env, sf2), "bob");
|
||||||
|
|
||||||
|
// Test 5: Async operations with _CTX
|
||||||
|
env.set(
|
||||||
|
"asyncOperation",
|
||||||
|
new LuaNativeJSFunction(async () => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
|
return "done";
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await evalBlock(
|
||||||
|
`
|
||||||
|
function asyncTest()
|
||||||
|
_CTX.status = "starting"
|
||||||
|
local result = asyncOperation()
|
||||||
|
_CTX.status = "completed"
|
||||||
|
return _CTX.status
|
||||||
|
end
|
||||||
|
`,
|
||||||
|
env,
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(await evalExpr("asyncTest()", env, sf), "completed");
|
||||||
|
assertEquals(threadLocal.get("status"), "completed");
|
||||||
|
|
||||||
|
// Test 6: Error handling with _CTX
|
||||||
|
await evalBlock(
|
||||||
|
`
|
||||||
|
function errorTest()
|
||||||
|
_CTX.error = nil
|
||||||
|
local status, err = pcall(function()
|
||||||
|
error("test error")
|
||||||
|
end)
|
||||||
|
_CTX.error = "caught"
|
||||||
|
return _CTX.error
|
||||||
|
end
|
||||||
|
`,
|
||||||
|
env,
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals(await evalExpr("errorTest()", env, sf), "caught");
|
||||||
|
assertEquals(threadLocal.get("error"), "caught");
|
||||||
|
});
|
||||||
|
|
|
@ -334,11 +334,11 @@ function evalPrefixExpression(
|
||||||
if (prefixValue instanceof Promise) {
|
if (prefixValue instanceof Promise) {
|
||||||
return prefixValue.then(async (resolvedPrefix) => {
|
return prefixValue.then(async (resolvedPrefix) => {
|
||||||
const args = await resolveVarargs();
|
const args = await resolveVarargs();
|
||||||
return luaCall(resolvedPrefix, args, e.ctx, sf);
|
return luaCall(resolvedPrefix, args, e.ctx, sf.withCtx(e.ctx));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return resolveVarargs().then((args) =>
|
return resolveVarargs().then((args) =>
|
||||||
luaCall(prefixValue, args, e.ctx, sf)
|
luaCall(prefixValue, args, e.ctx, sf.withCtx(e.ctx))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -409,7 +409,7 @@ const operatorsMetaMethods: Record<string, {
|
||||||
"/": { metaMethod: "__div", nativeImplementation: (a, b) => a / b },
|
"/": { metaMethod: "__div", nativeImplementation: (a, b) => a / b },
|
||||||
"//": {
|
"//": {
|
||||||
metaMethod: "__idiv",
|
metaMethod: "__idiv",
|
||||||
nativeImplementation: (a, b, ctx, sf) => Math.floor(a / b),
|
nativeImplementation: (a, b) => Math.floor(a / b),
|
||||||
},
|
},
|
||||||
"%": { metaMethod: "__mod", nativeImplementation: (a, b) => a % b },
|
"%": { metaMethod: "__mod", nativeImplementation: (a, b) => a % b },
|
||||||
"^": { metaMethod: "__pow", nativeImplementation: (a, b) => a ** b },
|
"^": { metaMethod: "__pow", nativeImplementation: (a, b) => a ** b },
|
||||||
|
|
|
@ -150,6 +150,7 @@ export class LuaFunction implements ILuaFunction {
|
||||||
if (!sf) {
|
if (!sf) {
|
||||||
console.trace(sf);
|
console.trace(sf);
|
||||||
}
|
}
|
||||||
|
// Set _CTX to the thread local environment from the stack frame
|
||||||
env.setLocal("_CTX", sf.threadLocal);
|
env.setLocal("_CTX", sf.threadLocal);
|
||||||
|
|
||||||
// Assign the passed arguments to the parameters
|
// Assign the passed arguments to the parameters
|
||||||
|
@ -271,6 +272,7 @@ export class LuaBuiltinFunction implements ILuaFunction {
|
||||||
}
|
}
|
||||||
|
|
||||||
call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
call(sf: LuaStackFrame, ...args: LuaValue[]): Promise<LuaValue> | LuaValue {
|
||||||
|
// _CTX is already available via the stack frame
|
||||||
return this.fn(sf, ...args);
|
return this.fn(sf, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,21 +610,35 @@ export class LuaRuntimeError extends Error {
|
||||||
// Find the line and column
|
// Find the line and column
|
||||||
let line = 1;
|
let line = 1;
|
||||||
let column = 0;
|
let column = 0;
|
||||||
|
let lastNewline = -1;
|
||||||
for (let i = 0; i < ctx.from; i++) {
|
for (let i = 0; i < ctx.from; i++) {
|
||||||
if (code[i] === "\n") {
|
if (code[i] === "\n") {
|
||||||
line++;
|
line++;
|
||||||
|
lastNewline = i;
|
||||||
column = 0;
|
column = 0;
|
||||||
} else {
|
} else {
|
||||||
column++;
|
column++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
traceStr += `* ${
|
|
||||||
ctx.ref || "(unknown source)"
|
// Get the full line of code for context
|
||||||
} @ ${line}:${column}:\n ${code.substring(ctx.from, ctx.to)}\n`;
|
const lineStart = lastNewline + 1;
|
||||||
|
const lineEnd = code.indexOf("\n", ctx.from);
|
||||||
|
const codeLine = code.substring(
|
||||||
|
lineStart,
|
||||||
|
lineEnd === -1 ? undefined : lineEnd,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add position indicator
|
||||||
|
const pointer = " ".repeat(column) + "^";
|
||||||
|
|
||||||
|
traceStr += `* ${ctx.ref || "(unknown source)"} @ ${line}:${column}:\n` +
|
||||||
|
` ${codeLine}\n` +
|
||||||
|
` ${pointer}\n`;
|
||||||
current = current.parent;
|
current = current.parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `LuaRuntimeError: ${this.message} ${traceStr}`;
|
return `LuaRuntimeError: ${this.message}\nStack trace:\n${traceStr}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
override toString() {
|
override toString() {
|
||||||
|
|
|
@ -72,8 +72,8 @@ const tonumberFunction = new LuaBuiltinFunction((_sf, value: LuaValue) => {
|
||||||
return Number(value);
|
return Number(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const errorFunction = new LuaBuiltinFunction((_sf, message: string) => {
|
const errorFunction = new LuaBuiltinFunction((sf, message: string) => {
|
||||||
throw new Error(message);
|
throw new LuaRuntimeError(message, sf);
|
||||||
});
|
});
|
||||||
|
|
||||||
const pcallFunction = new LuaBuiltinFunction(
|
const pcallFunction = new LuaBuiltinFunction(
|
||||||
|
@ -81,6 +81,9 @@ const pcallFunction = new LuaBuiltinFunction(
|
||||||
try {
|
try {
|
||||||
return new LuaMultiRes([true, await luaCall(fn, args, sf.astCtx!, sf)]);
|
return new LuaMultiRes([true, await luaCall(fn, args, sf.astCtx!, sf)]);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
if (e instanceof LuaRuntimeError) {
|
||||||
|
return new LuaMultiRes([false, e.message]);
|
||||||
|
}
|
||||||
return new LuaMultiRes([false, e.message]);
|
return new LuaMultiRes([false, e.message]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -91,9 +94,10 @@ const xpcallFunction = new LuaBuiltinFunction(
|
||||||
try {
|
try {
|
||||||
return new LuaMultiRes([true, await fn.call(sf, ...args)]);
|
return new LuaMultiRes([true, await fn.call(sf, ...args)]);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
const errorMsg = e instanceof LuaRuntimeError ? e.message : e.message;
|
||||||
return new LuaMultiRes([
|
return new LuaMultiRes([
|
||||||
false,
|
false,
|
||||||
await luaCall(errorHandler, [e.message], sf.astCtx!, sf),
|
await luaCall(errorHandler, [errorMsg], sf.astCtx!, sf),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,6 +22,7 @@ export const jsApi = new LuaTable({
|
||||||
log: new LuaBuiltinFunction((_sf, ...args) => {
|
log: new LuaBuiltinFunction((_sf, ...args) => {
|
||||||
console.log(...args);
|
console.log(...args);
|
||||||
}),
|
}),
|
||||||
|
stringify: new LuaBuiltinFunction((_sf, val) => JSON.stringify(val)),
|
||||||
// assignGlobal: new LuaBuiltinFunction((name: string, value: any) => {
|
// assignGlobal: new LuaBuiltinFunction((name: string, value: any) => {
|
||||||
// (globalThis as any)[name] = value;
|
// (globalThis as any)[name] = value;
|
||||||
// }),
|
// }),
|
||||||
|
|
|
@ -56,7 +56,12 @@ function exposeDefinitions(
|
||||||
if (!def.get("name")) {
|
if (!def.get("name")) {
|
||||||
throw new Error("Name is required");
|
throw new Error("Name is required");
|
||||||
}
|
}
|
||||||
console.log("Registering Lua command", def.get("name"));
|
const fn = def.get(1);
|
||||||
|
console.log(
|
||||||
|
`[Lua] Registering command '${
|
||||||
|
def.get("name")
|
||||||
|
}' (source: ${fn.body.ctx.ref})`,
|
||||||
|
);
|
||||||
scriptEnv.registerCommand(
|
scriptEnv.registerCommand(
|
||||||
{
|
{
|
||||||
name: def.get("name"),
|
name: def.get("name"),
|
||||||
|
@ -67,10 +72,10 @@ function exposeDefinitions(
|
||||||
hide: def.get("hide"),
|
hide: def.get("hide"),
|
||||||
} as CommandDef,
|
} as CommandDef,
|
||||||
async (...args: any[]) => {
|
async (...args: any[]) => {
|
||||||
const tl = new LuaEnv();
|
const tl = await buildThreadLocalEnv(system);
|
||||||
const sf = new LuaStackFrame(tl, null);
|
const sf = new LuaStackFrame(tl, null);
|
||||||
try {
|
try {
|
||||||
return await def.get(1).call(sf, ...args.map(jsToLuaValue));
|
return await fn.call(sf, ...args.map(jsToLuaValue));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
await handleLuaError(e, system);
|
await handleLuaError(e, system);
|
||||||
}
|
}
|
||||||
|
@ -88,13 +93,19 @@ function exposeDefinitions(
|
||||||
if (!def.get("event")) {
|
if (!def.get("event")) {
|
||||||
throw new Error("Event is required");
|
throw new Error("Event is required");
|
||||||
}
|
}
|
||||||
console.log("Subscribing to Lua event", def.get("event"));
|
const fn = def.get(1);
|
||||||
|
console.log(
|
||||||
|
`[Lua] Subscribing to event '${
|
||||||
|
def.get("event")
|
||||||
|
}' (source: ${fn.body.ctx.ref})`,
|
||||||
|
);
|
||||||
scriptEnv.registerEventListener(
|
scriptEnv.registerEventListener(
|
||||||
{ name: def.get("event") },
|
{ name: def.get("event") },
|
||||||
async (...args: any[]) => {
|
async (...args: any[]) => {
|
||||||
const sf = new LuaStackFrame(new LuaEnv(), null);
|
const tl = await buildThreadLocalEnv(system);
|
||||||
|
const sf = new LuaStackFrame(tl, null);
|
||||||
try {
|
try {
|
||||||
return await def.get(1).call(sf, ...args.map(jsToLuaValue));
|
return await fn.call(sf, ...args.map(jsToLuaValue));
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
await handleLuaError(e, system);
|
await handleLuaError(e, system);
|
||||||
}
|
}
|
||||||
|
@ -104,6 +115,16 @@ function exposeDefinitions(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function buildThreadLocalEnv(system: System<any>) {
|
||||||
|
const tl = new LuaEnv();
|
||||||
|
const currentPageMeta = await system.localSyscall(
|
||||||
|
"editor.getCurrentPageMeta",
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
tl.setLocal("pageMeta", currentPageMeta);
|
||||||
|
return tl;
|
||||||
|
}
|
||||||
|
|
||||||
async function handleLuaError(e: any, system: System<any>) {
|
async function handleLuaError(e: any, system: System<any>) {
|
||||||
console.error(
|
console.error(
|
||||||
"Lua eval exception",
|
"Lua eval exception",
|
||||||
|
|
|
@ -79,7 +79,10 @@ async function renderExpressionDirective(
|
||||||
variables,
|
variables,
|
||||||
functionMap,
|
functionMap,
|
||||||
);
|
);
|
||||||
|
return renderExpressionResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderExpressionResult(result: any): string {
|
||||||
if (
|
if (
|
||||||
Array.isArray(result) && result.length > 0 && typeof result[0] === "object"
|
Array.isArray(result) && result.length > 0 && typeof result[0] === "object"
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { UploadFile } from "../types.ts";
|
import type { PageMeta, UploadFile } from "../types.ts";
|
||||||
import { syscall } from "../syscall.ts";
|
import { syscall } from "../syscall.ts";
|
||||||
import type { PageRef } from "../lib/page_ref.ts";
|
import type { PageRef } from "../lib/page_ref.ts";
|
||||||
import type { FilterOption } from "@silverbulletmd/silverbullet/type/client";
|
import type { FilterOption } from "@silverbulletmd/silverbullet/type/client";
|
||||||
|
@ -17,6 +17,14 @@ export function getCurrentPage(): Promise<string> {
|
||||||
return syscall("editor.getCurrentPage");
|
return syscall("editor.getCurrentPage");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the meta data of the page currently open in the editor.
|
||||||
|
* @returns the current page meta data
|
||||||
|
*/
|
||||||
|
export function getCurrentPageMeta(): Promise<PageMeta | undefined> {
|
||||||
|
return syscall("editor.getCurrentPageMeta");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the full text of the currently open page
|
* Returns the full text of the currently open page
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -43,13 +43,13 @@ export function luaDirectivePlugin(client: Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = state.sliceDoc(node.from + 2, node.to - 1);
|
const text = state.sliceDoc(node.from + 2, node.to - 1);
|
||||||
|
const currentPageMeta = client.ui.viewState.currentPageMeta;
|
||||||
widgets.push(
|
widgets.push(
|
||||||
Decoration.widget({
|
Decoration.widget({
|
||||||
widget: new LuaWidget(
|
widget: new LuaWidget(
|
||||||
node.from,
|
node.from,
|
||||||
client,
|
client,
|
||||||
`lua:${text}`,
|
`lua:${text}:${currentPageMeta?.name}`,
|
||||||
text,
|
text,
|
||||||
async (bodyText) => {
|
async (bodyText) => {
|
||||||
try {
|
try {
|
||||||
|
@ -58,14 +58,22 @@ export function luaDirectivePlugin(client: Client) {
|
||||||
(parsedLua.statements[0] as LuaFunctionCallStatement).call
|
(parsedLua.statements[0] as LuaFunctionCallStatement).call
|
||||||
.args[0];
|
.args[0];
|
||||||
|
|
||||||
const sf = new LuaStackFrame(new LuaEnv(), expr.ctx);
|
const tl = new LuaEnv();
|
||||||
return luaValueToJS(
|
tl.setLocal("pageMeta", currentPageMeta);
|
||||||
|
const sf = new LuaStackFrame(tl, expr.ctx);
|
||||||
|
const threadLocalizedEnv = new LuaEnv(
|
||||||
|
client.clientSystem.spaceLuaEnv.env,
|
||||||
|
);
|
||||||
|
threadLocalizedEnv.setLocal("_CTX", tl);
|
||||||
|
const result = luaValueToJS(
|
||||||
await evalExpression(
|
await evalExpression(
|
||||||
expr,
|
expr,
|
||||||
client.clientSystem.spaceLuaEnv.env,
|
threadLocalizedEnv,
|
||||||
sf,
|
sf,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
// console.log("Result:", result);
|
||||||
|
return result;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e instanceof LuaRuntimeError) {
|
if (e instanceof LuaRuntimeError) {
|
||||||
if (e.sf.astCtx) {
|
if (e.sf.astCtx) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { extendedMarkdownLanguage } from "$common/markdown_parser/parser.ts";
|
||||||
import { renderToText } from "@silverbulletmd/silverbullet/lib/tree";
|
import { renderToText } from "@silverbulletmd/silverbullet/lib/tree";
|
||||||
import { activeWidgets } from "./markdown_widget.ts";
|
import { activeWidgets } from "./markdown_widget.ts";
|
||||||
import { attachWidgetEventHandlers } from "./widget_util.ts";
|
import { attachWidgetEventHandlers } from "./widget_util.ts";
|
||||||
|
import { renderExpressionResult } from "$common/template/render.ts";
|
||||||
|
|
||||||
export type LuaWidgetCallback = (
|
export type LuaWidgetCallback = (
|
||||||
bodyText: string,
|
bodyText: string,
|
||||||
|
@ -97,10 +98,15 @@ export class LuaWidget extends WidgetType {
|
||||||
this.cacheKey,
|
this.cacheKey,
|
||||||
{ height: div.clientHeight, html },
|
{ height: div.clientHeight, html },
|
||||||
);
|
);
|
||||||
} else if (widgetContent.markdown) {
|
} else {
|
||||||
|
// If there is a markdown key, use it, otherwise render the objects as a markdown table
|
||||||
|
let mdContent = widgetContent.markdown;
|
||||||
|
if (!mdContent) {
|
||||||
|
mdContent = renderExpressionResult(widgetContent);
|
||||||
|
}
|
||||||
let mdTree = parse(
|
let mdTree = parse(
|
||||||
extendedMarkdownLanguage,
|
extendedMarkdownLanguage,
|
||||||
widgetContent.markdown!,
|
mdContent,
|
||||||
);
|
);
|
||||||
mdTree = await this.client.clientSystem.localSyscall(
|
mdTree = await this.client.clientSystem.localSyscall(
|
||||||
"system.invokeFunction",
|
"system.invokeFunction",
|
||||||
|
@ -187,8 +193,8 @@ export class LuaWidget extends WidgetType {
|
||||||
override eq(other: WidgetType): boolean {
|
override eq(other: WidgetType): boolean {
|
||||||
return (
|
return (
|
||||||
other instanceof LuaWidget &&
|
other instanceof LuaWidget &&
|
||||||
other.bodyText === this.bodyText && other.cacheKey === this.cacheKey &&
|
other.bodyText === this.bodyText && other.cacheKey === this.cacheKey
|
||||||
this.from === other.from
|
// && this.from === other.from
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { EditorView } from "@codemirror/view";
|
||||||
import { getCM as vimGetCm, Vim } from "@replit/codemirror-vim";
|
import { getCM as vimGetCm, Vim } from "@replit/codemirror-vim";
|
||||||
import type { SysCallMapping } from "$lib/plugos/system.ts";
|
import type { SysCallMapping } from "$lib/plugos/system.ts";
|
||||||
import type { FilterOption } from "@silverbulletmd/silverbullet/type/client";
|
import type { FilterOption } from "@silverbulletmd/silverbullet/type/client";
|
||||||
import type { UploadFile } from "../../plug-api/types.ts";
|
import type { PageMeta, UploadFile } from "../../plug-api/types.ts";
|
||||||
import type { PageRef } from "@silverbulletmd/silverbullet/lib/page_ref";
|
import type { PageRef } from "@silverbulletmd/silverbullet/lib/page_ref";
|
||||||
import { openSearchPanel } from "@codemirror/search";
|
import { openSearchPanel } from "@codemirror/search";
|
||||||
import { diffAndPrepareChanges } from "../cm_util.ts";
|
import { diffAndPrepareChanges } from "../cm_util.ts";
|
||||||
|
@ -22,6 +22,9 @@ export function editorSyscalls(client: Client): SysCallMapping {
|
||||||
"editor.getCurrentPage": (): string => {
|
"editor.getCurrentPage": (): string => {
|
||||||
return client.currentPage;
|
return client.currentPage;
|
||||||
},
|
},
|
||||||
|
"editor.getCurrentPageMeta": (): PageMeta | undefined => {
|
||||||
|
return client.ui.viewState.currentPageMeta;
|
||||||
|
},
|
||||||
"editor.getText": () => {
|
"editor.getText": () => {
|
||||||
return client.editorView.state.sliceDoc();
|
return client.editorView.state.sliceDoc();
|
||||||
},
|
},
|
||||||
|
|
|
@ -81,7 +81,9 @@ You can listen to events using `define_event_listener`:
|
||||||
define_event_listener {
|
define_event_listener {
|
||||||
event = "my-custom-event";
|
event = "my-custom-event";
|
||||||
function(e)
|
function(e)
|
||||||
editor.flash_notification("Custom triggered: " .. e.data.name)
|
editor.flash_notification("Custom triggered: "
|
||||||
|
.. e.data.name
|
||||||
|
.. " on page " .. _CTX.pageMeta.name)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -106,6 +108,11 @@ Template:
|
||||||
Here's a greeting: {{greet_me("Pete")}}
|
Here's a greeting: {{greet_me("Pete")}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Thread locals
|
||||||
|
There’s a magic `_CTX` global variable available from which you can access useful context-specific value. Currently the following keys are available:
|
||||||
|
|
||||||
|
* `_CTX.pageMeta` contains a reference to the loaded page metadata (can be `nil` when not yet loaded)
|
||||||
|
|
||||||
# API
|
# API
|
||||||
Lua APIs, which should be (roughly) implemented according to the Lua standard.
|
Lua APIs, which should be (roughly) implemented according to the Lua standard.
|
||||||
* `print`
|
* `print`
|
||||||
|
|
Loading…
Reference in New Issue