Is there a good way to understand the "pos" that is used all over?

I get very confused by the “pos” values that are used throughout the docs.

In an appendTransaction, I check if the number of paragraphs has grown. If it has, I grab the marks from the selection position of the old state, and then try to store them in the new state:

new Plugin({
  appendTransaction(transactions, oldState, newState) {
    const oldParagraphCount = oldState.doc.content.content.length;
    const newParagraphCount = newState.doc.content.content.length;

    if (newParagraphCount > oldParagraphCount) {
      const transaction = newState.tr;
      const oldMarks = oldState.storedMarks || oldState.selection.$head.marks();

      const markObj = oldMarks.reduce((all, current) => {
        const markName = current.type.name;
        const markValue = current.attrs[markName];
        all[markName] = markValue;

        return all;
      }, {});

      transaction.setStoredMarks(oldMarks);
      transaction.setNodeMarkup(
        newState.selection.$head.pos - 1,
        null,
        { marks: markObj }
      );

      return transaction;
    }
  }
})

Here, I found that I have to subtract one from the resolved position of the selection head. This works as long as I am pressing enter while at the end of the last paragraph. If, however, I am in any paragraph before the last one, the setNodeMarkup doesn’t target the node I expect.

I really expected to be able to do this:

transaction.setNodeMarkup(
  newState.selection.$head.pos,
  null,
  { marks: markObj }
);

This raises RangeError: No node at given position.

I am missing something fundamental in my understanding of the positions, but I don’t know what it is. Can anyone help me to bridge the gap in my understanding?

Thank you!

After reading another post on this forum, I learned about the dev tools library. Here is a visualization of what I am experiencing. I expect that I can pass 7 as the position to setNodeMarkup, but that is not what it wants. It wants 6, even though that is the last position of the node before the one I want to affect.

Any information to help me understand why this is would be greatly appreciated :grinning_face_with_smiling_eyes:

See this section of the library guide. I’ve never used the ProseMirror devtools, but that picture looks misleading—the document isn’t at position 0. The document’s content starts at position 0. I’m not sure how to interpret those other positions, given this. Maybe it lists the position of their first child? In any case, setNodeMarkup wants the position at which a node’s opening token sits, so that’s 0 for the first paragraph and, assuming that paragraph has a single character of text, 3 for the second paragraph (past the opening token, closing token, and single-character content of the first), and so on.

Thanks @marijn.

I have read that guide content countless times, and my brain just doesn’t click with it. I think it is the fact that there are two types of positions (flat and nested) and it throws me off every time.

Can we use my code as an example?

transaction.setNodeMarkup(
  newState.selection.$head.pos - 1,
  null,
  { marks: markObj }
);

How would I use the selection data to always find the correct position for this? No matter where the selection head is in the paragraph (within nested text nodes), I would like to target the correct token when I set the node markup.

I read another post where you mentioned parentOffset. Would it be something like newState.selection.$head.pos - newState.selection.$head.parentOffset - 1?

Thanks again.

Probably by using the before method of the resolved position somehow, but I’m not really sure what you’re trying to do. Just subtracting 1 from the selection head will indeed not give you any kind of predictable result, since that might be pretty much anywhere.

@marijn Thank you!

This question came up while trying to figure out the answer to the other question thread I have open.

I have been trying to find the paragraph node that the selection is in, so I can store some marks on it in an attr (as shown in my example code). I couldn’t figure out how to get the correct position though, because I didn’t understand what position setNodeMarkup needed. before(1) seems to work perfectly though.

Thank you so much for your help, and once again thanks for this awesome platform (ProseMirror). It is so much more than just a library!

It finally clicked, thanks to you @marijn. Thank you again!

I have also encountered similar problem with this library and the position is indeed incorrect and leads to a lot of head scratches. Since the library is unmaintained, I created a monkey patched version of this library with the correct positions, see GitHub - kepta/prosemirror-dev-tools: A fork Developer Tools for ProseMirror with the positions patched..

Though in the long term we should build an updated version of a pm dev tools, it is super handy!