I am in the progress of rewriting a statefield extension used for rendering gutter markers, to reduce rerendering of said markers to a minimum.
Currently, I’ve ended up with below code (slightly simplified for demonstration purposes):
type Node: { from: number, to: number, /* ... */ };
export class Widget extends GutterMarker {
constructor(public node: Node) {
super();
}
toDOM() {
/* Create a gutter marker containing the text between node.from, node.to */
}
}
export const widgets = StateField.define<RangeSet<Widget>>({
create(state) {
const builder = new RangeSetBuilder<Widget>();
// The nodes indicate where and how the widgets should be added,
// the set of nodes is directly generated from a Lezer parse tree
for (const node: Node of state.field(nodesField)) {
const block_from = state.doc.lineAt(node.from);
builder.add(block_from.from, block_from.to - 1, new Marker(node));
}
return builder.finish();
}
update(oldSet, tr) {
if (!tr.docChanged) return oldSet;
const allNodes: Node[] = tr.state.field(nodesField);
const changedNodes = /* All nodes that were changed/added */
const removedNodes = /* All nodes that were changed/removed */
// Any node that was changed, must have its widget updated
const add: Range<Widget>[] = [ /* All changedNodes */ ]
const filter: (from: number, to: number, value: Widget) => {
/* If value.node in removedNodes, return 'false' */
};
return oldSet.map(tr.changes).update({ filter, add });
}
});
In essence, above code should boil down to:
- Gutter contains Widgets which render text
- Multiple Widgets can exist in the same block
- Widgets are created via nodes parsed by a Lezer parser
- On Document change, update (re-render) Widgets to reflect changes in text
- Optimization: Render operation for Widget is expensive, only re-render Widget if a Node was updated
My main questions are:
-
Am I understanding the
add
andfilter
of theupdate
function correctly?
I have not been able to find examples of this method, except for the breakpoint example, but that’s used in the context of effects being applied to a state, not edit operations. -
Tangential to the previous question: is this a reasonable way to implement this feature, or are there alternative (simpler) functions/structures I should use instead? At the moment, I’m unsure how I will be getting the changed/added nodes (from state A → B) in an efficient manner.
I’m hoping to use this code as an example for other developers to efficiently update and render their custom widgets (be it for a gutter or a viewplugin).
Many thanks in advance!
(Below is some additional information and a short demo of what this code is doing in practice)
Additional information
The document can contain text which is surrounded by {>> <<}
tags (based on the CriticMarkup syntax)
Any text surrounded by these tags gets parsed by my custom grammar. The parser tree is stored within a StateField.
Every ‘Node’ (every instance of {>>TEXT<<}
) gets turned into a Widget and added to a gutter.
On insert/deletion in ‘Node’ (i.e.: {>>TEXTinserted<<}
), the corresponding Widget should be updated and re-rendered.
This is what the output currently looks like: