pull/1219/merge
Zef Hemel 2025-02-12 20:09:02 +01:00
parent 7abb096382
commit cf81594b02
4 changed files with 174 additions and 12 deletions

143
common/markdown.ts Normal file
View File

@ -0,0 +1,143 @@
import {
findNodeOfType,
type ParseTree,
renderToText,
replaceNodesMatchingAsync,
} from "@silverbulletmd/silverbullet/lib/tree";
import {
parsePageRef,
validatePageName,
} from "@silverbulletmd/silverbullet/lib/page_ref";
import { renderExpressionResult } from "../plugs/template/util.ts";
import { parseMarkdown } from "$common/markdown_parser/parser.ts";
import type { LuaExpression } from "$common/space_lua/ast.ts";
import { evalExpression } from "$common/space_lua/eval.ts";
import type { LuaEnv, LuaStackFrame } from "$common/space_lua/runtime.ts";
import { parseExpressionString } from "$common/space_lua/parse.ts";
import type { CodeWidgetHook } from "../web/hooks/code_widget.ts";
/**
* Finds code widgets, runs their plug code to render and inlines their content in the parse tree
* @param mdTree parsed markdown tree
* @param pageName name of the current page
* @returns modified mdTree
*/
export async function expandCodeWidgets(
codeWidgetHook: CodeWidgetHook,
mdTree: ParseTree,
pageName: string,
env: LuaEnv,
sf: LuaStackFrame,
): Promise<ParseTree> {
await replaceNodesMatchingAsync(mdTree, async (n) => {
if (n.type === "FencedCode") {
const codeInfo = findNodeOfType(n, "CodeInfo");
if (!codeInfo) {
return;
}
const codeType = codeInfo.children![0].text!;
const codeTextNode = findNodeOfType(n, "CodeText");
try {
// This will error out if this is not a code wiget, which is fine
const langCallback = codeWidgetHook.codeWidgetCallbacks.get(codeType);
if (!langCallback) {
return {
text: "",
};
}
const result = await langCallback(
renderToText(codeTextNode!),
pageName,
);
if (!result) {
return {
text: "",
};
}
// Only do this for "markdown" widgets, that is: that can render to markdown
if (result.markdown !== undefined) {
const parsedBody = parseMarkdown(result.markdown);
// Recursively process
return expandCodeWidgets(
codeWidgetHook,
parsedBody,
pageName,
env,
sf,
);
}
} catch (e: any) {
// 'not found' is to be expected (no code widget configured for this language)
// Every other error should probably be reported
if (!e.message.includes("not found")) {
console.trace();
console.error("Error rendering code", e.message);
}
}
} else if (n.type === "Image") {
// Let's scan for ![[embeds]] that are codified as Images, confusingly
const wikiLinkMark = findNodeOfType(n, "WikiLinkMark");
if (!wikiLinkMark) {
return;
}
const wikiLinkPage = findNodeOfType(n, "WikiLinkPage");
if (!wikiLinkPage) {
return;
}
const page = wikiLinkPage.children![0].text!;
// Check if this is likely a page link (based on the path format, e.g. if it contains an extension, it's probably not a page link)
try {
const ref = parsePageRef(page);
validatePageName(ref.page);
} catch {
// Not a valid page name, so not a page reference
return;
}
// Internally translate this to a template that inlines a page, then render that
const langCallback = codeWidgetHook.codeWidgetCallbacks.get("template")!;
const result = await langCallback(`{{[[${page}]]}}`, pageName);
if (!result) {
return {
text: "",
};
}
// Only do this for "markdown" widgets, that is: that can render to markdown
if (result.markdown !== undefined) {
const parsedBody = await parseMarkdown(result.markdown);
// Recursively process
return expandCodeWidgets(
codeWidgetHook,
parsedBody,
page,
env,
sf,
);
}
} else if (n.type === "LuaDirective") {
const expr = findNodeOfType(n, "LuaExpressionDirective") as
| LuaExpression
| null;
if (!expr) {
return;
}
const exprText = renderToText(expr);
let result = await evalExpression(
parseExpressionString(exprText),
env,
sf,
);
if (result?.markdown) {
result = result.markdown;
}
const markdown = await renderExpressionResult(result);
return parseMarkdown(markdown);
}
});
return mdTree;
}

View File

@ -106,6 +106,15 @@ export class LuaStackFrame {
}
static lostFrame = new LuaStackFrame(new LuaEnv(), null);
static createWithGlobalEnv(
globalEnv: any,
ctx: ASTCtx | null = null,
): LuaStackFrame {
const env = new LuaEnv();
env.setLocal("_GLOBAL", globalEnv);
return new LuaStackFrame(env, ctx);
}
}
export class LuaMultiRes {

View File

@ -29,16 +29,21 @@ export function luaSyscalls(commonSystem: CommonSystem): SysCallMapping {
commonSystem.spaceLuaEnv.env,
);
const sf = new LuaStackFrame(env, null);
const result = evalExpression(ast, commonSystem.spaceLuaEnv.env, sf);
if (isSendable(result)) {
return luaValueToJS(result);
const luaResult = await evalExpression(
ast,
commonSystem.spaceLuaEnv.env,
sf,
);
const jsResult = luaValueToJS(luaResult);
if (isSendable(jsResult)) {
return jsResult;
} else {
// This may evaluate to e.g. a function, which is not sendable, in this case we'll console.warn and return a stringified version of the result
console.warn(
"Lua eval result is not sendable, returning stringified version",
result,
jsResult,
);
return luaToString(result);
return luaToString(luaResult);
}
} catch (e: any) {
console.error("Lua eval error: ", e.message, e.sf?.astCtx);

View File

@ -11,6 +11,8 @@ import { renderToText } from "@silverbulletmd/silverbullet/lib/tree";
import { activeWidgets } from "./markdown_widget.ts";
import { attachWidgetEventHandlers } from "./widget_util.ts";
import { renderExpressionResult } from "../../plugs/template/util.ts";
import { expandCodeWidgets } from "$common/markdown.ts";
import { LuaEnv, LuaStackFrame } from "$common/space_lua/runtime.ts";
export type LuaWidgetCallback = (
bodyText: string,
@ -101,13 +103,16 @@ export class LuaWidget extends WidgetType {
extendedMarkdownLanguage,
mdContent,
);
mdTree = await this.client.clientSystem.localSyscall(
"system.invokeFunction",
[
"markdown.expandCodeWidgets",
mdTree,
this.client.currentPage,
],
const tl = new LuaEnv();
const sf = new LuaStackFrame(tl, null, LuaStackFrame.lostFrame);
tl.setLocal("_GLOBAL", client.clientSystem.spaceLuaEnv.env);
mdTree = await expandCodeWidgets(
client.clientSystem.codeWidgetHook,
mdTree,
this.client.currentPage,
client.clientSystem.spaceLuaEnv.env,
sf,
);
const trimmedMarkdown = renderToText(mdTree).trim();