Equivalent for cm5 operation api in cm6

I am trying to implement macro recording and replay feature, but the straightforward method of implementing it (calling commands one by one as they are called during normal editing), is orders of magnitude slower than cm5 version, because of the time spent in updating dom and highlighting matching brackets.

The advice in documentation is to collect all the changes and dispatch them only once, but many commands need to read the intermediate states which is not possible with this approach.

I have tried to circumvent this by creating a dummy view with patched update method that would only update the state and not touch dom, accumulate changes, and then apply them to the original editor, but with this approach it is hard to ensure that the plugins filtering changes won’t produce different result on the original editor.

Is there a simpler way to achieve the effect of cm5 operation method?

What exactly are the ‘commands’ you are replaying? If they are transactions, you can dispatch them all at once as an array. If they are arbitrary code that can read the view state, then indeed, there’s no support for batching them. A dispatch will only synchronously write to the DOM, not read from it, so in many situations running commands should still be pretty fast, but there’s no way to update the view without updating the DOM in version 6.

I have sequence of keys that user have pressed, and need to emulate that same sequence being pressed again, possibly many times.

So commands are arbitrary key handlers from @codemirror/commands, things like cursorCharLeft, findNext, etc, and also vim keys that work in a similar way.

It does not seem possible to build a single transaction in this case because commands need to read the state modified by previous commands.

Dispatch indeed only writes to dom, but sadly that is already enough to make editor much slower than v5 when trying to emulate a key sequence.

Are you playing these back at a much faster rate than they initially happened? If so, would it maybe make sense to record document and selection changes instead of keys? (Of course, that will fail to capture some kind of state changes, but it seems that for a fast-forward kind of state change, those might not be relevant.)

The usecase is that the user performs some editing (e.g. find a word, then move to the next word, then insert some text) then this sequence of actions can be repeated at another position in document or for all matches of a word. So it is not known beforehand what selection and document changes will be produced when applying the key sequence in a different place.

Cm5 could handle tens of thousands of keystrokes without perceptible delay while cm6 takes a minute for a thousand. And removing the editor from document or setting the editor size to 0 does not help much.

If this is a rare issue, and not worth to add a way of delaying dom updates, do you see some workaround that could be used, short of using codemirror5 to accumulate changes and then applying them as one transaction on codemirror6?

If you have control over the way these commands access the editor state, you may be able to just run a state update, having them read from a state not a full view, while you accumulate transactions, and then dispatch them in one go.

I have tried to test this by replacing

    for (var i = 0; i < 1000; i++) {
        view.dispatch({
            changes: { from: 0, to: 0, insert: i + "\n" }
        });
    }

with

    var changes = []
    for (var i = 0; i < 1000; i++) {
        var tr = state.update({
            changes: { from: 0, to: 0, insert: i + "\n" }
        })
        state = tr.state
        changes.push(tr)
    }
    view.dispatch(changes)

But it produced only a very small improvement 4300 ms vs 4800ms, and from performance profile it seems that the majority of time is spent on updating dynamic slots, is there a way to delay updating these?

playground link

What you’re seeing is mostly the parser updating its parse tree on every change. Having editor states synchronously compute all their fields is a fundamental part of the editor’s design. There’s no way to delay that, except something like temporarily turning off the language.