I am attempting to configure CodeMirror 6 for collaboration in a way that shares the selection of remote peers, displaying the selection of each peer as a mark decoration.
I’ve already successfully created initialized the collab plugin with a sharedEffects function that looks for changes to selection and generates a “setPeerSelection” state effect for each change:
function createPeerPlugin(startVersion) {
let peerPlugin = ViewPlugin.fromClass(...);
return [
collab({
//clientID: <auto>
startVersion: startVersion,
sharedEffects: (tr) => {
// Transmit any changes in this peer's selection as
// "setPeerSelection" shared state effects.
if (tr.selection) {
return [setPeerSelection.of({
clientID: getClientID(tr.startState),
ranges: tr.selection.ranges,
})];
} else {
return [];
}
},
}),
peerPlugin,
];
}
Those state effects from multiple peers are combined into a single “peerSelectionsField” that maps each clientID to its selection state:
let peerSelectionsField = StateField/*<
Map<clientID: String, { clientID: String, ranges: SelectionRange[] }>
>*/.define({
create: () => new Map(),
update(peerSelections, tr) {
let newPeerSelections = new Map();
for (let [k, v] of peerSelections) {
newPeerSelections.set(k, v);
}
tr.effects.filter(e => e.is(setPeerSelection)).forEach(e => {
newPeerSelections.set(e.value.clientID, e.value);
});
return newPeerSelections;
},
}
Now I’m trying to take the combined field value and generate a mark decoration for each clientID’s selection state. If I understand the documentation, I should be able to extend this state field to “provide” values for the EditorView.decorations
facet. I tried this following code that uses computeN:
let peerSelectionsField = StateField/*<
Map<clientID: String, { clientID: String, ranges: SelectionRange[] }>
>*/.define({
create: () => ...,
update(peerSelections, tr) { ... },
// BOOM
provide: f => EditorView.decorations.computeN([f], (state) => {
let marks = Decoration.none;
let peerSelections = state.field(peerSelectionsField);
for (let [_, {clientID, ranges}] of peerSelections) {
for (r of ranges) {
marks = marks.update({
add: peerSelectionMark.range(r.from, r.to)
});
}
}
return marks;
})
}
let peerSelectionMark = Decoration.mark({class: "cm-peer-selection"});
But defining that handler for provide
causes a crash deep inside the EditorState constructor:
If I drastically simplify the provide
code to always return an empty DecorationSet for the EditorView.decorations facet it still crashes in the same way:
let peerSelectionsField = StateField<...>.define({
create: () => ...,
update(peerSelections, tr) { ... },
// BOOM
provide: f => EditorView.decorations.computeN([f], (state) => {
let marks = Decoration.none;
return marks;
})
}
Is there something obvious I’m doing wrong? In particular is it okay for a StateField (like peerSelectionsField) to provide a value to a facet (EditorView.decorations) using computeN, where the state field’s type and facet’s type differ?
Also, as a newcomer to writing JS that uses a library written in TypeScript, are there recommended techniques for debugging issues like this? I imagine if I read enough CM6 source code I might be able to puzzle out what’s going on, but maybe you have some other suggestions?
Thank you for your time.