Replacing keywords in text as they are written

Hello, I started using CM6 yesterday so I apologize if this question has been answered before but I’ve spent hours searching here, on GitHub issues, and reading through the implementations of the existing extensions, to no avail.

I’m trying to make it so that when the document contains text that matches a certain regular expression, it is automatically replaced with something else based on the regex matches (as the user types - no need to invoke a command). The text could have been inserted by typing, by pasting, by removing characters or in any other way.

For a concrete example: imagine I want to replace all instances of ABCDEF with bcde. If the user pastes 123 ABCDEF 456 ABCDEF, I’d like the text to immediately be converted to 123 bcde 456 bcde. Same goes if the user had ABC DEF and deletes the space in the middle, I want it to become bcde.

I have considered just monitoring the view.state.doc.toString() with an updateListener and constantly doing regex replacements that replace the whole contents of the editor (with view.dispatch( ... from: 0, to: end, insert: fullContents ...)) but obviously this destroys the state more than I’d like it to (moves the cursor, etc.), and it’s certainly not the most performant solution. Of course, I could also issue more fine-grained changes that replace just each range but I am still unsure if this is the right approach (and if triggering it with the updateListener is the right approach, or if I should make more use of the transactions mechanism so things are changed before the transaction is applied).

I also don’t want a replace decoration because I want to change the actual content that is being edited, I don’t want to just display it in a different way in the view.

Thanks in advance.

1 Like

This could be helpful to you - CodeMirror Document Change Example
In the first example it replaces tabs with spaces. You can maybe extend this logic to replace other things as well.

Thanks, my question is not so much about how to perform the text replacements but about what is the best way to trigger those replacements as the content is edited.

For now I’m using an updateListener and it seems to work well enough:

return EditorView.updateListener.of((viewUpdate) => {
            if (viewUpdate.docChanged) {
                let oldContents = viewUpdate.state.doc.toString();
                let matches = oldContents.matchAll(/ABCDEF/);
                let changes: ChangeSpec[] = [];
                for (let match of matches) {
                        changes.push({ from: match.index, to: match.index + match[0].length, insert: "bcde" });
                    }
                }
                if (changes.length > 0) {
                    viewUpdate.view.dispatch({
                        changes: changes,
                    });
                }
            }
        });

You could use a transactionFilter that checks whether the text around any of the changed ranges (tr.changes.iterChangedRanges) the new doc matches your keyword, and adds an additional transaction that changes them when it does (return [tr, {changes: [...], sequential: true}]).

1 Like