Path: blob/main/extensions/emmet/src/selectItemHTML.ts
4772 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as vscode from 'vscode';6import { getDeepestFlatNode, findNextWord, findPrevWord, getHtmlFlatNode, offsetRangeToSelection } from './util';7import { HtmlNode } from 'EmmetFlatNode';89export function nextItemHTML(document: vscode.TextDocument, selectionStart: vscode.Position, selectionEnd: vscode.Position, rootNode: HtmlNode): vscode.Selection | undefined {10const selectionEndOffset = document.offsetAt(selectionEnd);11let currentNode = getHtmlFlatNode(document.getText(), rootNode, selectionEndOffset, false);12let nextNode: HtmlNode | undefined = undefined;1314if (!currentNode) {15return;16}1718if (currentNode.type !== 'comment') {19// If cursor is in the tag name, select tag20if (currentNode.open &&21selectionEndOffset <= currentNode.open.start + currentNode.name.length) {22return getSelectionFromNode(document, currentNode);23}2425// If cursor is in the open tag, look for attributes26if (currentNode.open &&27selectionEndOffset < currentNode.open.end) {28const selectionStartOffset = document.offsetAt(selectionStart);29const attrSelection = getNextAttribute(document, selectionStartOffset, selectionEndOffset, currentNode);30if (attrSelection) {31return attrSelection;32}33}3435// Get the first child of current node which is right after the cursor and is not a comment36nextNode = currentNode.firstChild;37while (nextNode && (selectionEndOffset >= nextNode.end || nextNode.type === 'comment')) {38nextNode = nextNode.nextSibling;39}40}4142// Get next sibling of current node which is not a comment. If none is found try the same on the parent43while (!nextNode && currentNode) {44if (currentNode.nextSibling) {45if (currentNode.nextSibling.type !== 'comment') {46nextNode = currentNode.nextSibling;47} else {48currentNode = currentNode.nextSibling;49}50} else {51currentNode = currentNode.parent;52}53}5455return nextNode && getSelectionFromNode(document, nextNode);56}5758export function prevItemHTML(document: vscode.TextDocument, selectionStart: vscode.Position, selectionEnd: vscode.Position, rootNode: HtmlNode): vscode.Selection | undefined {59const selectionStartOffset = document.offsetAt(selectionStart);60let currentNode = getHtmlFlatNode(document.getText(), rootNode, selectionStartOffset, false);61let prevNode: HtmlNode | undefined = undefined;6263if (!currentNode) {64return;65}6667const selectionEndOffset = document.offsetAt(selectionEnd);68if (currentNode.open &&69currentNode.type !== 'comment' &&70selectionStartOffset - 1 > currentNode.open.start) {71if (selectionStartOffset < currentNode.open.end || !currentNode.firstChild || selectionEndOffset <= currentNode.firstChild.start) {72prevNode = currentNode;73} else {74// Select the child that appears just before the cursor and is not a comment75prevNode = currentNode.firstChild;76let oldOption: HtmlNode | undefined = undefined;77while (prevNode.nextSibling && selectionStartOffset >= prevNode.nextSibling.end) {78if (prevNode && prevNode.type !== 'comment') {79oldOption = prevNode;80}81prevNode = prevNode.nextSibling;82}8384prevNode = <HtmlNode>getDeepestFlatNode((prevNode && prevNode.type !== 'comment') ? prevNode : oldOption);85}86}8788// Select previous sibling which is not a comment. If none found, then select parent89while (!prevNode && currentNode) {90if (currentNode.previousSibling) {91if (currentNode.previousSibling.type !== 'comment') {92prevNode = <HtmlNode>getDeepestFlatNode(currentNode.previousSibling);93} else {94currentNode = currentNode.previousSibling;95}96} else {97prevNode = currentNode.parent;98}99100}101102if (!prevNode) {103return undefined;104}105106const attrSelection = getPrevAttribute(document, selectionStartOffset, selectionEndOffset, prevNode);107return attrSelection ? attrSelection : getSelectionFromNode(document, prevNode);108}109110function getSelectionFromNode(document: vscode.TextDocument, node: HtmlNode): vscode.Selection | undefined {111if (node && node.open) {112const selectionStart = node.open.start + 1;113const selectionEnd = selectionStart + node.name.length;114return offsetRangeToSelection(document, selectionStart, selectionEnd);115}116return undefined;117}118119function getNextAttribute(document: vscode.TextDocument, selectionStart: number, selectionEnd: number, node: HtmlNode): vscode.Selection | undefined {120if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {121return;122}123124for (const attr of node.attributes) {125if (selectionEnd < attr.start) {126// select full attr127return offsetRangeToSelection(document, attr.start, attr.end);128}129130if (!attr.value || attr.value.start === attr.value.end) {131// No attr value to select132continue;133}134135if ((selectionStart === attr.start && selectionEnd === attr.end) ||136selectionEnd < attr.value.start) {137// cursor is in attr name, so select full attr value138return offsetRangeToSelection(document, attr.value.start, attr.value.end);139}140141// Fetch the next word in the attr value142if (!attr.value.toString().includes(' ')) {143// attr value does not have space, so no next word to find144continue;145}146147let pos: number | undefined = undefined;148if (selectionStart === attr.value.start && selectionEnd === attr.value.end) {149pos = -1;150}151if (pos === undefined && selectionEnd < attr.end) {152const selectionEndCharacter = document.positionAt(selectionEnd).character;153const attrValueStartCharacter = document.positionAt(attr.value.start).character;154pos = selectionEndCharacter - attrValueStartCharacter - 1;155}156157if (pos !== undefined) {158const [newSelectionStartOffset, newSelectionEndOffset] = findNextWord(attr.value.toString(), pos);159if (newSelectionStartOffset === undefined || newSelectionEndOffset === undefined) {160return;161}162if (newSelectionStartOffset >= 0 && newSelectionEndOffset >= 0) {163const newSelectionStart = attr.value.start + newSelectionStartOffset;164const newSelectionEnd = attr.value.start + newSelectionEndOffset;165return offsetRangeToSelection(document, newSelectionStart, newSelectionEnd);166}167}168169}170171return;172}173174function getPrevAttribute(document: vscode.TextDocument, selectionStart: number, selectionEnd: number, node: HtmlNode): vscode.Selection | undefined {175if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {176return;177}178179for (let i = node.attributes.length - 1; i >= 0; i--) {180const attr = node.attributes[i];181if (selectionStart <= attr.start) {182continue;183}184185if (!attr.value || attr.value.start === attr.value.end || selectionStart < attr.value.start) {186// select full attr187return offsetRangeToSelection(document, attr.start, attr.end);188}189190if (selectionStart === attr.value.start) {191if (selectionEnd >= attr.value.end) {192// select full attr193return offsetRangeToSelection(document, attr.start, attr.end);194}195// select attr value196return offsetRangeToSelection(document, attr.value.start, attr.value.end);197}198199// Fetch the prev word in the attr value200const selectionStartCharacter = document.positionAt(selectionStart).character;201const attrValueStartCharacter = document.positionAt(attr.value.start).character;202const pos = selectionStart > attr.value.end ? attr.value.toString().length :203selectionStartCharacter - attrValueStartCharacter;204const [newSelectionStartOffset, newSelectionEndOffset] = findPrevWord(attr.value.toString(), pos);205if (newSelectionStartOffset === undefined || newSelectionEndOffset === undefined) {206return;207}208if (newSelectionStartOffset >= 0 && newSelectionEndOffset >= 0) {209const newSelectionStart = attr.value.start + newSelectionStartOffset;210const newSelectionEnd = attr.value.start + newSelectionEndOffset;211return offsetRangeToSelection(document, newSelectionStart, newSelectionEnd);212}213}214215return;216}217218219