How do I get the text node at the current cursor position?

I want to get the text node that the current cursor is at (let’s assume it’s an empty selection). If I do view.state.selection.$anchor.parent, this returns the parent of the text node (this is expected since according to the documentation text nodes are “flat”). However, I don’t see a way to get the text node itself. What am I missing?

What do you want to do with the node? Text nodes are conceptually not coherent nodes, they just cover a piece of text that happens to have the same marks. You can get the text before and after a resolved position with nodeBefore and nodeAfter, but I’ve never had a situation where I needed the actual text node around it.

I have the same problem now. I’m trying to get the text node and check whether it has a link mark to toggle a link edit popover menu. In the menu, to allow changing the link text, I have to get the full text node content. Right now, I’m iterating over all nodes from the selection and get the text nodes with link mark, but not sure whether that’s the proper way. See my code below.

Also, resolvedPos.after() seems to point the the start position of the next non-text node, so comparing $from.after() and $to.after() to determine whether my selection only spans one single text node doesn’t work.

I’m glad for ideas how to improve this :blush:

showLinkBubble({ view, state, from, to }) {
	const { doc, selection } = state
	const { $from, $to } = selection

	const isCursorSelection = from === to
	// TODO: doesn't work as expected if `$to` is inside a subsequent text node
	if (!isCursorSelection && ($from.after() !== $to.after())) {
		// Selection spans several nodes
		return false
	}

	// TODO: Probably there's a cleaner way to get the text node with link mark from selection?
	let node = null
	doc.nodesBetween($from.pos - $from.textOffset, $from.pos + 1, n => {
		if (n.isText && n.marks && n.marks.some(m => m.type.name === 'link')) {
			node = n
			return false
		}
		return true
	})

	if (!node || !view.hasFocus()) {
		return false
	}

	this.text = node.textContent
	this.href = node.marks.find(m => m.type.name === 'link').attrs.href
	return true
}

Ok, I just learned at Finding absolute start/end position of the node at a given position. · Issue #505 · ProseMirror/prosemirror · GitHub that this works as well to get the text node with link mark from cursor position:

const node = $from.parent.child($from.index())
if (!node.isText || !node.marks.some(m => m.type.name === 'link')) {
	throw new Error('Node at selection is not a text node with link mark')
}

And to make sure that the selection only spans one text node with link mark, the following works:

const linkStart = $from.pos - $from.textOffset
const linkEnd = linkStart + $from.parent.child($from.index()).nodeSize

if ($to.pos > linkEnd) {
	throw new Error('Selection spans further than one text node')
}