Only iterate through the syntax tree in one place to build up a state and store in a StateField, which is then used by the other extensions

This commit is contained in:
Jonatan Heyman 2022-12-29 19:38:01 +01:00
parent 551089d613
commit 693390cac4

View File

@ -7,6 +7,49 @@ import { Note, Document, NoteDelimiter } from "./lang-heynote/parser.terms.js"
import { IterMode } from "@lezer/common"; import { IterMode } from "@lezer/common";
import { INITIAL_DATA } from "./annotation.js"; import { INITIAL_DATA } from "./annotation.js";
function getBlocks(state) {
const blocks = [];
syntaxTree(state).iterate({
enter: (type) => {
if (type.type.id == Document || type.type.id == Note) {
return true
} else if (type.type.id === NoteDelimiter) {
const contentNode = type.node.nextSibling
blocks.push({
content: {
from: contentNode.from,
to: contentNode.to,
},
delimiter: {
from: type.from,
to: type.to,
},
})
return false;
}
return false;
},
mode: IterMode.IgnoreMounts,
});
return blocks
}
const blockState = StateField.define({
create(state) {
return getBlocks(state);
},
update(blocks, transaction) {
//console.log("blocks", blocks)
if (transaction.docChanged) {
return getBlocks(transaction.state);
}
//return widgets.map(transaction.changes);
return blocks
},
})
class NoteBlockStart extends WidgetType { class NoteBlockStart extends WidgetType {
constructor() { constructor() {
super() super()
@ -47,20 +90,18 @@ const noteBlockWidget = () => {
const decorate = (state) => { const decorate = (state) => {
const widgets = []; const widgets = [];
syntaxTree(state).iterate({ state.facet(blockState).forEach(block => {
enter: (type) => { let delimiter = block.delimiter
if (type.name === "NoteDelimiter") {
//console.log("found!", type.name, type.from, type.to)
let deco = Decoration.replace({ let deco = Decoration.replace({
widget: type.from === 0 ? new FirstNoteBlockStart() : new NoteBlockStart(), widget: delimiter.from === 0 ? new FirstNoteBlockStart() : new NoteBlockStart(),
inclusive: true, inclusive: true,
block: type.from === 0 ? false : true, block: delimiter.from === 0 ? false : true,
side: 0, side: 0,
}); });
widgets.push(deco.range(type.from === 0 ? type.from : type.from+1, type.from === 0 ? type.to : type.to-1)); widgets.push(deco.range(
} delimiter.from === 0 ? delimiter.from : delimiter.from+1,
}, delimiter.from === 0 ? delimiter.to : delimiter.to-1,
mode: IterMode.IgnoreMounts, ));
}); });
return widgets.length > 0 ? RangeSet.of(widgets) : Decoration.none; return widgets.length > 0 ? RangeSet.of(widgets) : Decoration.none;
@ -88,19 +129,14 @@ const noteBlockWidget = () => {
function atomicRanges(view) { function atomicRanges(view) {
let builder = new RangeSetBuilder() let builder = new RangeSetBuilder()
syntaxTree(view.state).iterate({ view.state.facet(blockState).forEach(block => {
enter: (type) => { builder.add(block.delimiter.from, block.delimiter.to, {})
if (type.type.id === NoteDelimiter) { })
builder.add(type.from, type.to, {})
}
},
mode: IterMode.IgnoreMounts,
});
return builder.finish() return builder.finish()
} }
const atomicNoteBlock = ViewPlugin.fromClass( const atomicNoteBlock = ViewPlugin.fromClass(
class { class {
constructor(view) { constructor(view) {
@ -115,7 +151,7 @@ const atomicNoteBlock = ViewPlugin.fromClass(
}, },
{ {
provide: plugin => EditorView.atomicRanges.of(view => { provide: plugin => EditorView.atomicRanges.of(view => {
return view.plugin(plugin)?.atomicRanges || Decoration.none return view.plugin(plugin)?.atomicRanges || []
}) })
} }
) )
@ -139,32 +175,17 @@ const blockLayer = () => {
markers(view) { markers(view) {
const markers = [] const markers = []
let idx = 0 let idx = 0
syntaxTree(view.state).iterate({ view.state.facet(blockState).forEach(block => {
enter: (type) => { const fromCoords = view.coordsAtPos(block.content.from)
//console.log("type", type.name, type.type.id, Document) const toCoords = view.coordsAtPos(block.content.to)
if (type.type.id == Document || type.type.id == Note) {
return true
} else if (type.type.id === NoteDelimiter) {
const contentNode = type.node.nextSibling
//console.log("adding marker", type.node.nextSibling.name)
//let line = view.state.doc.lineAt(type.from)
const fromCoords = view.coordsAtPos(contentNode.from)
const toCoords = view.coordsAtPos(contentNode.to)
//console.log("line", fromCoords.top, toCoords.bottom)
//console.log("documentTop", view.documentTop)
markers.push(new RectangleMarker( markers.push(new RectangleMarker(
idx++ % 2 == 0 ? "block-even" : "block-odd", idx++ % 2 == 0 ? "block-even" : "block-odd",
0, 0,
fromCoords.top - (view.documentTop - view.documentPadding.top), fromCoords.top - (view.documentTop - view.documentPadding.top) - 1,
editorWidth, editorWidth,
(toCoords.bottom - fromCoords.top), (toCoords.bottom - fromCoords.top) + 2,
)) ))
return false; })
}
return false;
},
mode: IterMode.IgnoreMounts,
});
return markers return markers
}, },
@ -182,7 +203,7 @@ const blockLayer = () => {
const preventFirstBlockFromBeingDeleted = EditorState.changeFilter.of((tr) => { const preventFirstBlockFromBeingDeleted = EditorState.changeFilter.of((tr) => {
if (!tr.annotations.some(a => a.value === INITIAL_DATA)) { if (!tr.annotations.some(a => a.value === INITIAL_DATA)) {
return [-1,10] return [-1,11]
} }
}) })
@ -193,11 +214,12 @@ const preventSelectionBeforeFirstBlock = EditorState.transactionFilter.of((tr) =
//console.log("transaction:", tr) //console.log("transaction:", tr)
tr?.selection?.ranges.forEach(range => { tr?.selection?.ranges.forEach(range => {
// change the selection to after the first block if the transaction sets the selection before the first block // change the selection to after the first block if the transaction sets the selection before the first block
if (range && range.from < 10) { const markerSize = 11
range.from = 10 if (range && range.from < markerSize) {
range.from = markerSize
} }
if (range && range.to < 10) { if (range && range.to < markerSize) {
range.to = 10 range.to = markerSize
} }
}) })
return tr return tr
@ -206,6 +228,8 @@ const preventSelectionBeforeFirstBlock = EditorState.transactionFilter.of((tr) =
export const noteBlockExtension = () => { export const noteBlockExtension = () => {
return [ return [
blockState,
noteBlockWidget(), noteBlockWidget(),
atomicNoteBlock, atomicNoteBlock,
blockLayer(), blockLayer(),