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 { 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 {
constructor() {
super()
@ -47,20 +90,18 @@ const noteBlockWidget = () => {
const decorate = (state) => {
const widgets = [];
syntaxTree(state).iterate({
enter: (type) => {
if (type.name === "NoteDelimiter") {
//console.log("found!", type.name, type.from, type.to)
let deco = Decoration.replace({
widget: type.from === 0 ? new FirstNoteBlockStart() : new NoteBlockStart(),
inclusive: true,
block: type.from === 0 ? false : true,
side: 0,
});
widgets.push(deco.range(type.from === 0 ? type.from : type.from+1, type.from === 0 ? type.to : type.to-1));
}
},
mode: IterMode.IgnoreMounts,
state.facet(blockState).forEach(block => {
let delimiter = block.delimiter
let deco = Decoration.replace({
widget: delimiter.from === 0 ? new FirstNoteBlockStart() : new NoteBlockStart(),
inclusive: true,
block: delimiter.from === 0 ? false : true,
side: 0,
});
widgets.push(deco.range(
delimiter.from === 0 ? delimiter.from : delimiter.from+1,
delimiter.from === 0 ? delimiter.to : delimiter.to-1,
));
});
return widgets.length > 0 ? RangeSet.of(widgets) : Decoration.none;
@ -88,19 +129,14 @@ const noteBlockWidget = () => {
function atomicRanges(view) {
let builder = new RangeSetBuilder()
syntaxTree(view.state).iterate({
enter: (type) => {
if (type.type.id === NoteDelimiter) {
builder.add(type.from, type.to, {})
}
},
mode: IterMode.IgnoreMounts,
});
view.state.facet(blockState).forEach(block => {
builder.add(block.delimiter.from, block.delimiter.to, {})
})
return builder.finish()
}
const atomicNoteBlock = ViewPlugin.fromClass(
class {
constructor(view) {
@ -115,7 +151,7 @@ const atomicNoteBlock = ViewPlugin.fromClass(
},
{
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) {
const markers = []
let idx = 0
syntaxTree(view.state).iterate({
enter: (type) => {
//console.log("type", type.name, type.type.id, Document)
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(
idx++ % 2 == 0 ? "block-even" : "block-odd",
0,
fromCoords.top - (view.documentTop - view.documentPadding.top),
editorWidth,
(toCoords.bottom - fromCoords.top),
))
return false;
}
return false;
},
mode: IterMode.IgnoreMounts,
});
view.state.facet(blockState).forEach(block => {
const fromCoords = view.coordsAtPos(block.content.from)
const toCoords = view.coordsAtPos(block.content.to)
markers.push(new RectangleMarker(
idx++ % 2 == 0 ? "block-even" : "block-odd",
0,
fromCoords.top - (view.documentTop - view.documentPadding.top) - 1,
editorWidth,
(toCoords.bottom - fromCoords.top) + 2,
))
})
return markers
},
@ -182,7 +203,7 @@ const blockLayer = () => {
const preventFirstBlockFromBeingDeleted = EditorState.changeFilter.of((tr) => {
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)
tr?.selection?.ranges.forEach(range => {
// change the selection to after the first block if the transaction sets the selection before the first block
if (range && range.from < 10) {
range.from = 10
const markerSize = 11
if (range && range.from < markerSize) {
range.from = markerSize
}
if (range && range.to < 10) {
range.to = 10
if (range && range.to < markerSize) {
range.to = markerSize
}
})
return tr
@ -206,6 +228,8 @@ const preventSelectionBeforeFirstBlock = EditorState.transactionFilter.of((tr) =
export const noteBlockExtension = () => {
return [
blockState,
noteBlockWidget(),
atomicNoteBlock,
blockLayer(),