tr.setSelection strange results for insert empty paragraph

I have an issue that I wasn’t having with the previous 0.10.0 release that I now have with new 0.11.x

I’ve been trying to determine where the problem lies, but I have a keymap plugin for ‘Enter’ which I’m trying to handle end of line Enter keypress and then create a new paragraph and insert after the cursor, and move the selection to that new paragraph like so:

var sel = state.selection.$head.pos; state.tr.insert(sel+1, state.schema.nodes.paragraph.createAndFill()).setSelection(new TextSelection(state.doc.resolve(sel+1))).scrollAction();

The insert and selection movement all appear to work properly, however if you try pressing Enter again on the empty paragraph, state.selection is always returning the paragraph after, with textContent equal to the next paragraph’s. That’s not what I would expect. If I manually move the cursor ahead and then back to the same pos with arrow keys the selection appears to be at the same position except it now returns the correct node and correct empty textContent.

After digging through the source I notice that an empty string is not valid for a TextNode, however these paragraphs do have a newline that textContent doesn’t return.

Seems like the empty paragraph is not being distinguished from the node/paragraph in front of it. This happens if I try inserting a TextNode with whitespace as the text content also. But if there is a non-empty string it the setSelection works as expected.

Please let me know if I’m doing something boneheaded or if I have found a legitimate problem. The problem seems to occur with my custom schema and the basic one too.

whoops, I think my code example was incorrect, this is what I’m actually trying to do with sel+2 for selection:

state.tr.insert(sel+1, state.schema.nodes.paragraph.createAndFill()).setSelection(new TextSelection(state.doc.resolve(sel+2))).scrollAction();

I’ve tried using Selection.near and variations of replace but it seems to have to do with the empty text node.

Not sure of your exact issue but what’s helped me debugging a lot is to check pm.selection before and after to see any differences.

So if your saying that after you hit enter the cursor appears in a specific location but has a bug (a subsequent enter fails) AND YET moving the cursor manually to that exact spot seems to work, then I’m guessing you’ll get a difference with pm.selection for these two cursor positions.

Check that pm.selection.(to, from) are the same in both cases.

Thanks for the reply wes-r.

I did indeed do that and that’s where I found it to seem erroneous in the behaviour. After the transform, the view.state.selection shows the same $from, $to, $head, $anchor positions, paths, depth as when I move the cursor manually and back to that position. However, the node() is now the correct empty node after cursor movement, not the one that’s returned immediately after the transform.

I’ll keep digging around to see if I can figure out why this is happening. I don’t think it has anything to do with the position’s I’m resolving during the transform, since I’ve tried lots of different positions plus everything else I can think of so far! :slight_smile:

A resolved position is applicable only in the document into which it was resolved. Thus, your new selection should be based on positions in the transformed document, not the old one. Something like

let tr = tr.insert(sel+1, state.schema.nodes.paragraph.createAndFill())
tr.setSelection(new TextSelection(tr.doc.resolve(sel+2))) // note tr.doc, not state.doc
2 Likes

Ahh, there you have it.

I knew it was probably something I was doing incorrectly.

Although I did try doing this before with the same incorrect result:

state.tr.insert(sel+1, state.schema.nodes.paragraph.createAndFill()).setSelection(new TextSelection(state.tr.doc.resolve(sel+2))).scrollAction();

Still wrongly using state.tr.doc, but I suppose I was almost on the right track :relaxed:. It makes more sense now, thanks marijn!

I hope other new users find the same lesson useful, even though it seems pretty obvious now. You need to use the value of the resulting transform after any (transform) operations in order to get the correct (transformed) ResolvePos. Otherwise you’ll get the ResolvedPos for the old state/doc before your operation.