Confusion around nodeBefore and path on resolved positions

I am running into an issue with nodeBefore and want to ask for help in understanding what might be going wrong.

Say I have a simple schema with a doc, a paragraph, a list and a list item. Let’s assume I have the following doc in pseudo-code:

abc
* def

If I understand correctly (and the devtools print this as structure too) that translates to:

0 doc
1 paragraph
2-4 text
5 /paragraph
6 list
7 list item
8-10 text
11 /list item
12 /list

If I resolve position 6 and access node().type I get list, however parent.type is also list? If I look at the path of the position I get [doc, 1, 5, list, 0, 6]. As such the depth is 1.

If I now call nodeBefore I get null. I expected it to be the paragraph node. However if we look at the code of nodeBefore it seems to resolve the index incorrectly:

  // nodeBefore
    let index = this.index(this.depth) // depth is 1 as described before
    let dOff = this.pos - this.path[this.path.length - 1]
    if (dOff) return this.parent.child(index).cut(0, dOff) // parent is the node itself?
    return index == 0 ? null : this.parent.child(index - 1) // index is 0 so we return null
 // index
 index(depth) { return this.path[this.resolveDepth(depth) * 3 + 1] } // depth = 1 => 1* 3 + 1 = 4

index resolves the 5th element of the path which is 0 (see path above). As far as I understand this means the offset into the list node itself. I think index should use depth = 0 and as such resolve the offset into the the doc node, no (the documentation calls this “level” sometimes)? If I force set the depth to depth -1 before calling nodeBefore the paragraph is correctly returned. This depth confusion also is what returns the wrong parent I think.

What am I misunderstanding here? Why does nodeBefore not work the way I understood it? Essentially I want to get the position of the node before as described in this post: How to get the absolute position of a sibling node?

I hope my post makes sense and I appreciate the help a lot.

Mind we are not on the latest prosemirror, however I didn’t find any changes to the source code in question between latest and the version we use.

I also think nodeAfter is wrong. I think it misses a index + 1. As it currently is it returns the parent node’s child at index which is the node itself at that position.

It should be

    let dOff = this.pos - this.path[this.path.length - 1], child = parent.child(index + 1)
    return dOff ? parent.child(index + 1).cut(dOff) : 

if I am not mistaken. And the condition should be index == parent.childCount - 1.

Yes, node, without argument, is always going to return the same as parent.

Position 6 is at the start of the list. nodeBefore returns the node before the position, at the position’s depth. There is no node in the list before position 6.

No, it’s really not.

Thanks for the reply. I misunderstood positioning. I thought nodes fall ONTO a position, but I was explained that they fall before or after. With all that in mind reasoning about positions makes a lot more sense now.

I appreciate your response.

In my experience with prosemirror-devtools, I have found its position to be off by 1. To fix this, I have a forked a version of prosemirror-devtools which applies the position correctly.

This is what the patched pm devtools position shows for your data:

(Ignore the -1 before the doc, it is a way to signal a position outside the reach of editor).

As you can see here it clearly shows position 6 is right before <bullet_list>.

That’s neat, thanks for the pointer! I think in the devtools the thought kind of was to have the light handles of an element indiciate start and end and the darker inner part with the node’s name to then actually show the node.

I think in the devtools the thought kind of was to have the light handles of an element indiciate start and end and the darker inner part with the node’s name to then actually show the node.

Yes that is correct, but the original devtools shows the position as absolute and not w.r.t to the document, which is what all of the PM commands need. So I forked the devtools to fix it.