From 0e8a5dfccc6d97226ea6b1b028590410d2455b53 Mon Sep 17 00:00:00 2001 From: Jonatan Heyman Date: Mon, 9 Jun 2025 14:13:59 +0200 Subject: [PATCH] Make the Lezer parser for Heynote documents use overlay parsers for the individual blocks This fixes the issue with some blocks not being foldable (since the NoteContent nodes were getting replaced by the tree produced by the sub-parser). Update the todo-checkbox plugin to use regular expression to find the todo list syntax, since it was previously relying on the parsed syntax tree, and the markdown syntax tree is no longer available in the main document's syntax tree. --- src/editor/lang-heynote/nested-parser.js | 1 + src/editor/todo-checkbox.ts | 40 +++++++----------------- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/editor/lang-heynote/nested-parser.js b/src/editor/lang-heynote/nested-parser.js index 3efa9ca..9e96c14 100644 --- a/src/editor/lang-heynote/nested-parser.js +++ b/src/editor/lang-heynote/nested-parser.js @@ -34,6 +34,7 @@ export function configureNesting() { //console.log("found parser for language:", langName) return { parser:languageMapping[langName], + overlay: [{from:node.from, to:node.to}], } } } diff --git a/src/editor/todo-checkbox.ts b/src/editor/todo-checkbox.ts index 2a74a2b..4cbac72 100644 --- a/src/editor/todo-checkbox.ts +++ b/src/editor/todo-checkbox.ts @@ -1,8 +1,8 @@ import { EditorView, Decoration } from "@codemirror/view" -import { syntaxTree, ensureSyntaxTree } from "@codemirror/language" import { WidgetType } from "@codemirror/view" import { ViewUpdate, ViewPlugin, DecorationSet } from "@codemirror/view" +import { getNoteBlockFromPos } from "./block/block" import { isMonospaceFont } from "./theme/font-theme" import { transactionsHasAnnotation, SET_FONT } from "./annotation" @@ -48,38 +48,22 @@ class CheckboxWidget extends WidgetType { ignoreEvent() { return false } } +const checkboxRegex = /^([\t\f\v ]*-[\t\f\v ]*)\[( |x|X)\]/gm function checkboxes(view: EditorView) { let widgets: any = [] for (let { from, to } of view.visibleRanges) { - syntaxTree(view.state).iterate({ - from, to, - enter: (nodeRef) => { - // make sure we only enter markdown nodes - if (nodeRef.name == "Note") { - let langNode = nodeRef.node.firstChild?.firstChild - if (langNode) { - const language = view.state.doc.sliceString(langNode.from, langNode.to) - if (!language.startsWith("markdown")) { - return false - } - } - } - - if (nodeRef.name == "TaskMarker") { - // the Markdown parser creates a TaskMarker for "- [x]", but we don't want to replace it with a - // checkbox widget, unless its followed by a space - if (view.state.doc.sliceString(nodeRef.to, nodeRef.to+1) === " ") { - let isChecked = view.state.doc.sliceString(nodeRef.from, nodeRef.to).toLowerCase() === "[x]" - let deco = Decoration.replace({ - widget: new CheckboxWidget(isChecked, view.state.facet(isMonospaceFont)), - inclusive: false, - }) - widgets.push(deco.range(nodeRef.from, nodeRef.to)) - } - } + let range = view.state.sliceDoc(from, to) + let match + while (match = checkboxRegex.exec(range)) { + if (getNoteBlockFromPos(view.state, from + match.index)?.language?.name === "markdown") { + let deco = Decoration.replace({ + widget: new CheckboxWidget(match[2] === "x" || match[2] === "X", view.state.facet(isMonospaceFont)), + inclusive: false, + }) + widgets.push(deco.range(from + match.index + match[1].length, from + match.index + match[0].length)) } - }) + } } return Decoration.set(widgets) }