Why is the Ctrl+A selection different from selecting the whole document with the mouse?

Hi there,

Imagine a simple plugin like this:

new Plugin({
  props: {
    handlePaste(view, event) {
        console.log(view.state.selection)
        return false
    },
  },
})

When I select the whole document with Ctrl+A and paste something, I get one selection, but when I click and drag the text cursor from the start of the document to the end and paste something, I get a different selection. The head position of these selections seem to differ by 1, although that’s not the only difference:

The one at the top is the Ctrl+A selection, the bottom one is the “click-and-drag” selection.

I’m wondering why are they different, and if they should be different?

Ctrl-A creates an AllSelection instance, which isn’t (like TextSelection is) required to have its start and end point in text positions, so it can include non-textblock content at the start and end of the document. In a simple document, the AllSelection will go from 0 to doc.content.size, whereas a TextSelection will go from 1 to doc.content.size - 1 (the first and last valid text positions). AllSelection is mostly a hack to make Ctrl-A do what you’d expect in documents that start of end in a horizontal rule, video node, or what have you.

In Tiptap we tested a lot with the selections and found out that some core commands do not work as expected when an AllSelection is active, e.g. liftListItem. Therefore we use a classic TextSelection, which works better for us.

@marijn That’s good to know, thanks.

@philippkuehn But the AllSelection is not something I can control, is it? I mean, I’m talking about user selections, and if they use Ctrl+A, I will get an AllSelection from view.state.selection. How do you handle that? Do you convert it to a TextSelection, and use that instead?


I think both your replies already help me move forward, but let me clarify my goal here. I’m trying to build an augmented plain-text Markdown editor, and I’m actually using Tiptap (which is powered by ProseMirror). To achieve that, my doc.content is paragraph*, and only allow paragraphs and text nodes. The augmented part is that I will also allow other custom node types, like the ones used by mentions.

Now I’m trying to build a plugin to make typing Markdown more pleasant (like GitHub), for instance, you select some text and paste a URL, the plugin should automatically wrap the selected text into [<selected_text>](<url>).

I have this pretty much done, but while I was testing multiple selection scenarios - like multiple paragraphs, Ctrl+A, etc. - I found that if the user used an AllSelection, my automatic wrapping wasn’t inserting text in the right place.

I’m wondering if doing something like const textSelection = TextSelection.create(view.state.tr.doc, view.state.selection.from, view.state.selection.to) would do the trick for me? Right now, I’m using view.state.selection to figure out the positions to insert text, but maybe this new textSelection will work better for me. Thoughts?

Just override or remove the default Ctrl+A behavior in https://github.com/ProseMirror/prosemirror-commands/blob/a137cd5973e589ef55f96190535436cb14dd407e/src/commands.js#L637-L676.

If you use tiptap you should not get an AllSelection because we use a custom default keymap (without AllSelection: https://github.com/ueberdosis/tiptap/blob/main/packages/core/src/extensions/keymap.ts

@philippkuehn I’m using Tiptap, and the console log screenshots in the OP are output from Tiptap. Maybe the view.state.selection from a ProseMirror plugin does not give me the selection overridden by Tiptap’s keymap?

@bhl I can’t do that because I’m not using ProseMirror directly, but Tiptap instead. However, as you can see from Philipp’s comment above, that’s already overridden for me, but I’m still not getting the expected outcome.

Strange. We don’t use the AllSelection in our codebase at all. Are you sure you’re not overwriting something by yourself? Can you create a minimal codesandbox or something?

@philippkuehn I just created a minimal CodeSandbox, and to my surprise it worked as expected. I looked through my code, and couldn’t figure out the difference between them. Then I decided to update all Tiptap dependencies, just in case, and it worked :sweat_smile:

It looks like you fixed something related to this just yesterday: https://github.com/ueberdosis/tiptap/commit/bdab760cdbf89da7a56dcc1a183010974d2e1290, problem solved. Thank you, everyone who contributed to this discussion :slight_smile: