1
0
silverbullet/web/cm_plugins/fenced_code.ts

125 lines
4.1 KiB
TypeScript

import { Decoration, EditorState, syntaxTree } from "../deps.ts";
import type { Client } from "../client.ts";
import {
decoratorStateField,
invisibleDecoration,
isCursorInRange,
shouldRenderAsCode,
} from "./util.ts";
import { MarkdownWidget } from "./markdown_widget.ts";
import { IFrameWidget } from "./iframe_widget.ts";
import { isTemplate } from "$sb/lib/cheap_yaml.ts";
export function fencedCodePlugin(editor: Client) {
return decoratorStateField((state: EditorState) => {
const widgets: any[] = [];
syntaxTree(state).iterate({
enter({ from, to, name, node }) {
if (name === "FencedCode") {
if (shouldRenderAsCode(state, [from, to])) {
// Don't render the widget if the cursor is inside the fenced code
return;
}
const text = state.sliceDoc(from, to);
const [_, lang] = text.match(/^```(\w+)?/)!;
const codeWidgetCallback = editor.system.codeWidgetHook
.codeWidgetCallbacks
.get(lang);
const renderMode = editor.system.codeWidgetHook.codeWidgetModes.get(
lang,
);
// Only custom render when we have a custom renderer, and the current page is not a template
if (codeWidgetCallback && !isTemplate(state.sliceDoc(0, from))) {
// We got a custom renderer!
const lineStrings = text.split("\n");
const lines: { from: number; to: number }[] = [];
let fromIt = from;
for (const line of lineStrings) {
lines.push({
from: fromIt,
to: fromIt + line.length,
});
fromIt += line.length + 1;
}
const firstLine = lines[0], lastLine = lines[lines.length - 1];
// In case of doubt, back out
if (!firstLine || !lastLine) return;
widgets.push(
invisibleDecoration.range(firstLine.from, firstLine.to),
);
widgets.push(
invisibleDecoration.range(lastLine.from, lastLine.to),
);
widgets.push(
Decoration.line({
class: "sb-fenced-code-iframe",
}).range(firstLine.from),
);
widgets.push(
Decoration.line({
class: "sb-fenced-code-hide",
}).range(lastLine.from),
);
lines.slice(1, lines.length - 1).forEach((line) => {
widgets.push(
Decoration.line({ class: "sb-line-table-outside" }).range(
line.from,
),
);
});
const bodyText = lineStrings.slice(1, lineStrings.length - 1).join(
"\n",
);
const widget = renderMode === "markdown"
? new MarkdownWidget(
from + lineStrings[0].length + 1,
editor,
`widget:${editor.currentPage}:${bodyText}`,
bodyText,
codeWidgetCallback,
"sb-markdown-widget",
)
: new IFrameWidget(
from + lineStrings[0].length + 1,
to - lineStrings[lineStrings.length - 1].length - 1,
editor,
lineStrings.slice(1, lineStrings.length - 1).join("\n"),
codeWidgetCallback,
);
widgets.push(
Decoration.widget({
widget: widget,
}).range(from),
);
return false;
}
return true;
}
if (
name === "CodeMark"
) {
const parent = node.parent!;
// Hide ONLY if CodeMark is not insine backticks (InlineCode) and the cursor is placed outside
if (
parent.node.name !== "InlineCode" &&
!isCursorInRange(state, [parent.from, parent.to])
) {
widgets.push(
Decoration.line({
class: "sb-line-code-outside",
}).range(state.doc.lineAt(from).from),
);
}
}
},
});
return Decoration.set(widgets, true);
});
}