Providing mark decorations from a StateField holding something other than DecorationSet

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.

I think the problem is that you’re using computeN, which should return an array of results, but have it return a single result.

Something like provide: f => EditorView.decorations.from(f, fieldValue => ...) should work.

1 Like

Thanks marjin! That was just the fix!