diff --git a/app/syntax/indent-plugins.ts b/app/syntax/indent-plugins.ts new file mode 100644 index 0000000..132888a --- /dev/null +++ b/app/syntax/indent-plugins.ts @@ -0,0 +1,79 @@ +import { indentWithTab } from "@codemirror/commands"; +import { indentService, indentUnit, syntaxTree } from "@codemirror/language"; +import { + type DecorationSet, + type EditorView, + type ViewUpdate, + Decoration, + ViewPlugin, + WidgetType, + keymap, +} from "@codemirror/view"; + +class IndentWidget extends WidgetType { + constructor(readonly bullet: boolean) { + super(); + } + toDOM() { + const indent = document.createElement("span"); + indent.textContent = this.bullet ? "•" : " "; + indent.style.paddingInline = "0.5em"; + return indent; + } +} + +function indentDecorations(view: EditorView) { + const decorations: Array<{ + from: number; + to: number; + value: Decoration; + }> = []; + for (const { from, to } of view.visibleRanges) { + syntaxTree(view.state).iterate({ + from, + to, + enter(node) { + if (node.name === "Indent") { + const next = view.state.doc.sliceString(node.to, node.to + 1); + const decoration = Decoration.widget({ + widget: new IndentWidget(/^[^ \t]?$/.test(next)), + }); + decorations.push(decoration.range(node.from)); + } + }, + }); + } + return Decoration.set(decorations); +} + +const indentDecorationsPlugin = ViewPlugin.fromClass( + class { + decorations: DecorationSet; + constructor(view: EditorView) { + this.decorations = indentDecorations(view); + } + update(update: ViewUpdate) { + if (update.docChanged || update.viewportChanged) { + this.decorations = indentDecorations(update.view); + } + } + }, + { + decorations(v) { + return v.decorations; + }, + } +); + +const indentPlugins = [ + keymap.of([indentWithTab]), + indentUnit.of(" "), + indentService.of((context, pos) => { + const previousLine = context.lineAt(pos, -1).text; + if (previousLine.trim() === "") return null; + return /^[ \t]*/.exec(previousLine)?.[0]?.length ?? null; + }), + indentDecorationsPlugin, +]; + +export default indentPlugins; diff --git a/app/syntax/quot.ts b/app/syntax/quot.ts index d0f7bc7..7f1d70d 100644 --- a/app/syntax/quot.ts +++ b/app/syntax/quot.ts @@ -11,7 +11,6 @@ export const quotLanguage = LRLanguage.define({ props: [ styleTags({ Heading: t.heading, - Indent: t.separator, AutoLink: t.link, Code: t.monospace, }), @@ -27,14 +26,6 @@ export const quotHighlighting = syntaxHighlighting( fontSize: "1.25em", marginBlockEnd: "0.5em", }, - { - tag: t.separator, - letterSpacing: "1.5em", - "&:last-child:after": { - content: `"•"`, - marginInline: "-0.9em", - }, - }, { tag: t.link, class: "auto-link", diff --git a/app/views/components/editor.tsx b/app/views/components/editor.tsx index 54c8071..ff34d69 100644 --- a/app/views/components/editor.tsx +++ b/app/views/components/editor.tsx @@ -1,11 +1,11 @@ import { onCleanup, onMount } from "solid-js"; import { minimalSetup } from "codemirror"; -import { emacsStyleKeymap, indentWithTab } from "@codemirror/commands"; -import { indentService, indentUnit } from "@codemirror/language"; +import { emacsStyleKeymap } from "@codemirror/commands"; import { EditorView, keymap } from "@codemirror/view"; import type Pages from "../../protocol/pages"; import "./editor.css"; import { quotHighlighting, quotLanguage } from "../../syntax/quot"; +import indentPlugins from "../../syntax/indent-plugins"; export default (props: { id: number; @@ -36,19 +36,11 @@ export default (props: { props.onUpdatePage({ id: props.id, title, text }); }), EditorView.lineWrapping, - indentUnit.of(" "), - indentService.of((context, pos) => { - const previousLine = context.lineAt(pos, -1).text; - if (previousLine.trim() === "") return null; - return /^[ \t]*/.exec(previousLine)?.[0]?.length ?? null; - }), - keymap.of([ - indentWithTab, - ...emacsStyleKeymap.filter(({ key }) => key !== "Ctrl-v"), - ]), + keymap.of(emacsStyleKeymap.filter(({ key }) => key !== "Ctrl-v")), minimalSetup, quotLanguage, quotHighlighting, + ...indentPlugins, ], }); ref.addEventListener("click", (e) => {