Differentiating between del and backspace

Hey, I have started looking at two different approaches for tracking changes - one based on prosemirror-changeset and the other one by interrupting all changes and trying to figure out what content ReplaceStep and ReplaceAroundStep were trying to remove, mark that as deleted instead and then mark all inserted content. This second approach feels a lot more like common word processors, but especially in the case of ReplaceAroundStep it can be complicated finding out what the user was actually trying to do.

One complication, that does not seem to be solved with prosemirror-changeset is what direction the caret moves. This is fairly easy to achieve if the selection was collapsed at the start of the deletion – if it was in front of it, the caret will now be behind it and the other way round. But if an entire word was selected, and the user hits backspace or delete, I cannot see how one can differentiate between the two. The goal is to emulate the behavior of LibreOffice & co, collapsing to the right if the user hits del and collapsing to the left if the user hits backspace. But I don’t think there is a way which of those were used when all one has is the current state and the tr, right?

Could you not update your keymap handler for backspace / delete to resolve to different commands to get this behaviour? e.g. something like

const keymapPlugin = keymaps({
  'Backspace': collapseToTheLeft
  'Delete': collapseToTheRight

If you want to keep the existing backspace / delete behaviour, you could just wrap the transaction returned to append the meta, like:

const backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward);
const delete = chainCommands(deleteSelection, joinForward, selectNodeForward);

// Will intercept the txn from the dispatch & set the meta key/value
const appendMeta = (key, value) => (command) => 
  (state, dispatch, view) => {
    return command(
      dispatch ? tr => dispatch(tr.setMeta(key, value)) : undefiend,

const keymapPlugin = keymaps({
  'Backspace': appendMeta('isBackspace', true)(backspace)
  'Delete': appendMeta('isBackspace', false)(delete)
1 Like

Thanks @5id, that’s an approach I had not thought about. I was thinking that surely backspace/del should be thought of events that can originate from a number of different sources – an IME, screen reader interface, virtual assistent like Siri, etc. . But maybe that’s not really the case and it’s still really about those two keys.

This sounds like you are somewhat confused about the problem that prosemirror-changeset solves. It creates a set of insertions/deletions from a range of steps. Questions about which way a caret moves are not relevant to this problem.

I don’t think I’m confused. :slight_smile: I do get what it does. But when deleting entire words, and then adding those in the document as widgets, we don’t seem to get information about what it was that caused the deletion, so we don’t know whether we should place the caret to the right or left of it.

It’s actually a debate we also had in the W3C Editing Taskforce - some browser makers didn’t get why we need to specify the direction we delete when the deleted content will be gone anyway and the caret collapse at it’s deletion point. But we did end up adding this information to the beforeinput/input events precisely so that it will be easier to implement editors with tracked changes.

At first I was actually confused in the sense that I did not get that prosemirror-changeset only works by diffing to one original document so that it cannot be used when collaborators don’t have just one original document and need to also record insertions that are later deleted. So my first attempt made little sense. But now I’m thinking that parts may be useful to determine what contents ar deleted by one particular ReplaceAroundStep in order to put the deletion markers correctly.