Files
plainleaf/client/codemirror/lint.ts
T
Zef Hemel 09059aa18a Lint: support rich HTML tooltips
LintDiagnostic adds `messageHtml` (rendered into the hover
  tooltip) and `markClass` (added to the inline range mark)
2026-05-09 12:29:19 +02:00

53 lines
1.5 KiB
TypeScript

import { type Diagnostic, linter } from "@codemirror/lint";
import { StateEffect } from "@codemirror/state";
import type { Client } from "../client.ts";
import { parse } from "../markdown_parser/parse_tree.ts";
import { extendedMarkdownLanguage } from "../markdown_parser/parser.ts";
import type {
LintDiagnostic,
LintEvent,
} from "@silverbulletmd/silverbullet/type/client";
/** Signals the linter to re-run even when the document hasn't changed. */
export const refreshLintEffect = StateEffect.define<null>();
export function plugLinter(client: Client) {
return linter(async (view): Promise<Diagnostic[]> => {
const text = view.state.sliceDoc();
const tree = parse(extendedMarkdownLanguage, text);
const results: LintDiagnostic[] = (
await client.dispatchAppEvent("editor:lint", {
name: client.currentName(),
pageMeta: client.currentPageMeta(),
tree,
text,
} as LintEvent)
).flat();
return results.map(toDiagnostic);
}, {
needsRefresh: (update) =>
update.transactions.some((tr) =>
tr.effects.some((e) => e.is(refreshLintEffect))
),
});
}
function toDiagnostic(d: LintDiagnostic): Diagnostic {
const out: Diagnostic = {
from: d.from,
to: d.to,
severity: d.severity,
message: d.message,
};
if (d.markClass) out.markClass = d.markClass;
if (d.messageHtml) {
const html = d.messageHtml;
out.renderMessage = () => {
const wrap = document.createElement("div");
wrap.innerHTML = html;
return wrap;
};
}
return out;
}