Adding custom information to history object


#1

I’m building a layer on top of CodeMirror that effectively turns it into a blocks-based editor. The blocks editor has to manage its own cursor state, since (1) setCursor() doesn’t work inside atomic ranges, (2) a block can have children and (3) we want to track when a child is focused.

Part of this work also means restoring the active block after undo/redo. That means I also need to duplicate the history functionality of CM, maintaining a parallel history infrastructure that just tracks the active block over time. Can I store some kind of data inside the entires in the history object, and have CM manage it? Ideally, I’d like to add the id of the block to the current record, such that undoing/redoing will allow me to retrieve that id and re-focus the appropriate block.

FWIW, I’ve tried doing this programmatically, but anything I add to the history object is sanitized away when the history is updated. And obviously, this wouldn’t be an issue if I could set the cursor inside an atomic range, but I don’t want to sound too whiny on here. Marijn has already been so patient with my crazy requests in the past. :slight_smile:


#2

Since atomic ranges are defined as not allowing the cursor in the middle of them, I don’t see why you are using them if you don’t actually want that behavior.

There is indeed no feature for storing custom data in the undo history. It might be possible to keep a separate history data structure, and capture ctrl-z/y yourself and only defer to CodeMirror’s built-in undo/redo when your own history has no events more recent than the most recent event in the built-in history.


#3

Oh, I don’t want to use atomic ranges. But the API gives me no choice: replacing a range with a DOM node forces those ranges to be atomic. This would be fine if those nodes didn’t have children representing sub-ranges. The fact that I can’t use CM’s cursors for my purposes means I need to roll my own, which I’ve done. But this complicates undo/redo…

As for using my own history structure - I can’t defer to CM’s when my event queue is exhausted, because that means CM wouldn’t be aware of the changes to the underlying text. I need to keep them in sync - my homegrown cursor history and CM’s history.

I sympathize with the desire to keep the API as-is, so I mean no disrespect by asking about this. However, it sure should would unlock a LOT of potential if there was some way to programmatically set a cursor inside an atomic range, or decorate the history structure with user-created data. I suspect many use-cases would benefit from having additional context tracked alongside undo/redo.

If this argument isn’t persuasive, I’ll get to work thinking about a way to keep my history in sync with CM’s…any suggestions you have would be greatly appreciated.


#4

Since the content in your replacement widget has nothing to do with the CodeMirror document, there wouldn’t be a meaningful way to represent such selections.

The history is a rather dodgy, specialized data structure as it is, and I don’t want to further complicate it, sorry.


#5

Since the content in your replacement widget has nothing to do with the CodeMirror document, there wouldn’t be a meaningful way to represent such selections.

Not quite. It’s useful to think of blocks as merely a different Display component, and nobody would suggest that the lines or tokens in the Display have nothing to do with the Document! :slight_smile: The content inside the replacement widgets absolutely represent the CM document. The code: (+ 1 2) will be replaced with one node representing the whole expression, and that node has three children representing +, 1, and 2. Modifying any one of those children will update the document.

So what happens if the user has clicked on (selected) the second child? Essentially, their “selection” is on the node representing 1. Now suppose the user changes that node (thereby updating the underlying CM content) and then navigates elsewhere, then realizes they made a mistake and hits Ctrl-Z. What should happen? Well, we’d like CM to revert back to the previous state (returning that node to 1), and we’d like to see that node re-selected (just as it was before the change).

Take a look at this demo, if the above description doesn’t make sense. I’m using the CM value as the underlying data structure, and updating it whenever a node is changed, moved, created, or deleted.


#6

If I’m going to manage my own history, and keep it in sync with CM’s, it would be helpful for me to know if a change event is the result of an undo or redo. Is there any way of detecting that?


#7

I know you’re probably deep into this project and don’t want to start over, but have you looked at ProseMirror? That’s an actual editor for tree-shaped documents, rather than a plain text, and might be easier to fit to what you’re trying to do.


#8

Interesting. I’ll certainly have to take a look…perhaps for version 1.1. Thanks, as always, Marijn!