extensions/emmet/src/selectItemHTML.ts/nextItemHTML
export function nextItemHTML(document: vscode.TextDocument, selectionStart: vscode.Position, selectionEnd: vscode.Position, rootNode: HtmlNode): vscode.Selection | undefined {
	const selectionEndOffset = document.offsetAt(selectionEnd);
	let currentNode = getHtmlFlatNode(document.getText(), rootNode, selectionEndOffset, false);
	let nextNode: HtmlNode | undefined = undefined;

	if (!currentNode) {
		return;
	}

	if (currentNode.type !== 'comment') {
		// If cursor is in the tag name, select tag
		if (currentNode.open &&
			selectionEndOffset <= currentNode.open.start + currentNode.name.length) {
			return getSelectionFromNode(document, currentNode);
		}

		// If cursor is in the open tag, look for attributes
		if (currentNode.open &&
			selectionEndOffset < currentNode.open.end) {
			const selectionStartOffset = document.offsetAt(selectionStart);
			const attrSelection = getNextAttribute(document, selectionStartOffset, selectionEndOffset, currentNode);
			if (attrSelection) {
				return attrSelection;
			}
		}

		// Get the first child of current node which is right after the cursor and is not a comment
		nextNode = currentNode.firstChild;
		while (nextNode && (selectionEndOffset >= nextNode.end || nextNode.type === 'comment')) {
			nextNode = nextNode.nextSibling;
		}
	}

	// Get next sibling of current node which is not a comment. If none is found try the same on the parent
	while (!nextNode && currentNode) {
		if (currentNode.nextSibling) {
			if (currentNode.nextSibling.type !== 'comment') {
				nextNode = currentNode.nextSibling;
			} else {
				currentNode = currentNode.nextSibling;
			}
		} else {
			currentNode = currentNode.parent;
		}
	}

	return nextNode && getSelectionFromNode(document, nextNode);
}