Refactor Emmet merge lines and select item commands (#113516)

This commit is contained in:
Raymond Zhao
2020-12-28 17:36:38 -08:00
committed by GitHub
parent c7dbab59ff
commit c44b7d25d9
5 changed files with 179 additions and 122 deletions

View File

@@ -4,11 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getDeepestNode, findNextWord, findPrevWord, getHtmlNode, isNumber } from './util';
import { HtmlNode } from 'EmmetNode';
import { getDeepestFlatNode, findNextWord, findPrevWord, getHtmlFlatNode, offsetRangeToSelection } from './util';
import { HtmlNode } from 'EmmetFlatNode';
export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vscode.Position, editor: vscode.TextEditor, rootNode: HtmlNode): vscode.Selection | undefined {
let currentNode = getHtmlNode(editor.document, rootNode, selectionEnd, false);
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) {
@@ -17,13 +18,16 @@ export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
if (currentNode.type !== 'comment') {
// If cursor is in the tag name, select tag
if (selectionEnd.isBefore(currentNode.open.start.translate(0, currentNode.name.length))) {
return getSelectionFromNode(currentNode);
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 (selectionEnd.isBefore(currentNode.open.end)) {
let attrSelection = getNextAttribute(selectionStart, selectionEnd, currentNode);
if (currentNode.open &&
selectionEndOffset < currentNode.open.end) {
const selectionStartOffset = document.offsetAt(selectionStart);
const attrSelection = getNextAttribute(document, selectionStartOffset, selectionEndOffset, currentNode);
if (attrSelection) {
return attrSelection;
}
@@ -31,12 +35,11 @@ export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
// Get the first child of current node which is right after the cursor and is not a comment
nextNode = currentNode.firstChild;
while (nextNode && (selectionEnd.isAfterOrEqual(nextNode.end) || nextNode.type === 'comment')) {
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) {
@@ -50,33 +53,36 @@ export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
}
}
return nextNode && getSelectionFromNode(nextNode);
return nextNode && getSelectionFromNode(document, nextNode);
}
export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vscode.Position, editor: vscode.TextEditor, rootNode: HtmlNode): vscode.Selection | undefined {
let currentNode = getHtmlNode(editor.document, rootNode, selectionStart, false);
export function prevItemHTML(document: vscode.TextDocument, selectionStart: vscode.Position, selectionEnd: vscode.Position, rootNode: HtmlNode): vscode.Selection | undefined {
const selectionStartOffset = document.offsetAt(selectionStart);
let currentNode = getHtmlFlatNode(document.getText(), rootNode, selectionStartOffset, false);
let prevNode: HtmlNode | undefined = undefined;
if (!currentNode) {
return;
}
if (currentNode.type !== 'comment' && selectionStart.translate(0, -1).isAfter(currentNode.open.start)) {
if (selectionStart.isBefore(currentNode.open.end) || !currentNode.firstChild || selectionEnd.isBeforeOrEqual(currentNode.firstChild.start)) {
const selectionEndOffset = document.offsetAt(selectionEnd);
if (currentNode.open &&
currentNode.type !== 'comment' &&
selectionStartOffset - 1 > currentNode.open.start) {
if (selectionStartOffset < currentNode.open.end || !currentNode.firstChild || selectionEndOffset <= currentNode.firstChild.start) {
prevNode = currentNode;
} else {
// Select the child that appears just before the cursor and is not a comment
prevNode = currentNode.firstChild;
let oldOption: HtmlNode | undefined = undefined;
while (prevNode.nextSibling && selectionStart.isAfterOrEqual(prevNode.nextSibling.end)) {
while (prevNode.nextSibling && selectionStartOffset >= prevNode.nextSibling.end) {
if (prevNode && prevNode.type !== 'comment') {
oldOption = prevNode;
}
prevNode = prevNode.nextSibling;
}
prevNode = <HtmlNode>getDeepestNode((prevNode && prevNode.type !== 'comment') ? prevNode : oldOption);
prevNode = <HtmlNode>getDeepestFlatNode((prevNode && prevNode.type !== 'comment') ? prevNode : oldOption);
}
}
@@ -84,7 +90,7 @@ export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
while (!prevNode && currentNode) {
if (currentNode.previousSibling) {
if (currentNode.previousSibling.type !== 'comment') {
prevNode = <HtmlNode>getDeepestNode(currentNode.previousSibling);
prevNode = <HtmlNode>getDeepestFlatNode(currentNode.previousSibling);
} else {
currentNode = currentNode.previousSibling;
}
@@ -98,66 +104,66 @@ export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
return undefined;
}
let attrSelection = getPrevAttribute(selectionStart, selectionEnd, prevNode);
return attrSelection ? attrSelection : getSelectionFromNode(prevNode);
const attrSelection = getPrevAttribute(document, selectionStartOffset, selectionEndOffset, prevNode);
return attrSelection ? attrSelection : getSelectionFromNode(document, prevNode);
}
function getSelectionFromNode(node: HtmlNode): vscode.Selection | undefined {
function getSelectionFromNode(document: vscode.TextDocument, node: HtmlNode): vscode.Selection | undefined {
if (node && node.open) {
let selectionStart = (<vscode.Position>node.open.start).translate(0, 1);
let selectionEnd = selectionStart.translate(0, node.name.length);
return new vscode.Selection(selectionStart, selectionEnd);
const selectionStart = node.open.start + 1;
const selectionEnd = selectionStart + node.name.length;
return offsetRangeToSelection(document, selectionStart, selectionEnd);
}
return undefined;
}
function getNextAttribute(selectionStart: vscode.Position, selectionEnd: vscode.Position, node: HtmlNode): vscode.Selection | undefined {
function getNextAttribute(document: vscode.TextDocument, selectionStart: number, selectionEnd: number, node: HtmlNode): vscode.Selection | undefined {
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
return;
}
for (const attr of node.attributes) {
if (selectionEnd.isBefore(attr.start)) {
if (selectionEnd < attr.start) {
// select full attr
return new vscode.Selection(attr.start, attr.end);
return offsetRangeToSelection(document, attr.start, attr.end);
}
if (!attr.value || (<vscode.Position>attr.value.start).isEqual(attr.value.end)) {
if (!attr.value || attr.value.start === attr.value.end) {
// No attr value to select
continue;
}
if ((selectionStart.isEqual(attr.start) && selectionEnd.isEqual(attr.end)) || selectionEnd.isBefore(attr.value.start)) {
if ((selectionStart === attr.start && selectionEnd === attr.end) ||
selectionEnd < attr.value.start) {
// cursor is in attr name, so select full attr value
return new vscode.Selection(attr.value.start, attr.value.end);
return offsetRangeToSelection(document, attr.value.start, attr.value.end);
}
// Fetch the next word in the attr value
if (attr.value.toString().indexOf(' ') === -1) {
// attr value does not have space, so no next word to find
continue;
}
let pos: number | undefined = undefined;
if (selectionStart.isEqual(attr.value.start) && selectionEnd.isEqual(attr.value.end)) {
if (selectionStart === attr.value.start && selectionEnd === attr.value.end) {
pos = -1;
}
if (pos === undefined && selectionEnd.isBefore(attr.end)) {
pos = selectionEnd.character - attr.value.start.character - 1;
if (pos === undefined && selectionEnd < attr.end) {
const selectionEndCharacter = document.positionAt(selectionEnd).character;
const attrValueStartCharacter = document.positionAt(attr.value.start).character;
pos = selectionEndCharacter - attrValueStartCharacter - 1;
}
if (pos !== undefined) {
let [newSelectionStartOffset, newSelectionEndOffset] = findNextWord(attr.value.toString(), pos);
if (!isNumber(newSelectionStartOffset) || !isNumber(newSelectionEndOffset)) {
const [newSelectionStartOffset, newSelectionEndOffset] = findNextWord(attr.value.toString(), pos);
if (newSelectionStartOffset === undefined || newSelectionEndOffset === undefined) {
return;
}
if (newSelectionStartOffset >= 0 && newSelectionEndOffset >= 0) {
const newSelectionStart = (<vscode.Position>attr.value.start).translate(0, newSelectionStartOffset);
const newSelectionEnd = (<vscode.Position>attr.value.start).translate(0, newSelectionEndOffset);
return new vscode.Selection(newSelectionStart, newSelectionEnd);
const newSelectionStart = attr.value.start + newSelectionStartOffset;
const newSelectionEnd = attr.value.start + newSelectionEndOffset;
return offsetRangeToSelection(document, newSelectionStart, newSelectionEnd);
}
}
@@ -166,44 +172,44 @@ function getNextAttribute(selectionStart: vscode.Position, selectionEnd: vscode.
return;
}
function getPrevAttribute(selectionStart: vscode.Position, selectionEnd: vscode.Position, node: HtmlNode): vscode.Selection | undefined {
function getPrevAttribute(document: vscode.TextDocument, selectionStart: number, selectionEnd: number, node: HtmlNode): vscode.Selection | undefined {
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
return;
}
for (let i = node.attributes.length - 1; i >= 0; i--) {
let attr = node.attributes[i];
if (selectionStart.isBeforeOrEqual(attr.start)) {
const attr = node.attributes[i];
if (selectionStart <= attr.start) {
continue;
}
if (!attr.value || (<vscode.Position>attr.value.start).isEqual(attr.value.end) || selectionStart.isBefore(attr.value.start)) {
if (!attr.value || attr.value.start === attr.value.end || selectionStart < attr.value.start) {
// select full attr
return new vscode.Selection(attr.start, attr.end);
return offsetRangeToSelection(document, attr.start, attr.end);
}
if (selectionStart.isEqual(attr.value.start)) {
if (selectionEnd.isAfterOrEqual(attr.value.end)) {
if (selectionStart === attr.value.start) {
if (selectionEnd >= attr.value.end) {
// select full attr
return new vscode.Selection(attr.start, attr.end);
return offsetRangeToSelection(document, attr.start, attr.end);
}
// select attr value
return new vscode.Selection(attr.value.start, attr.value.end);
return offsetRangeToSelection(document, attr.value.start, attr.value.end);
}
// Fetch the prev word in the attr value
let pos = selectionStart.isAfter(attr.value.end) ? attr.value.toString().length : selectionStart.character - attr.value.start.character;
let [newSelectionStartOffset, newSelectionEndOffset] = findPrevWord(attr.value.toString(), pos);
if (!isNumber(newSelectionStartOffset) || !isNumber(newSelectionEndOffset)) {
const selectionStartCharacter = document.positionAt(selectionStart).character;
const attrValueStartCharacter = document.positionAt(attr.value.start).character;
const pos = selectionStart > attr.value.end ? attr.value.toString().length :
selectionStartCharacter - attrValueStartCharacter;
const [newSelectionStartOffset, newSelectionEndOffset] = findPrevWord(attr.value.toString(), pos);
if (newSelectionStartOffset === undefined || newSelectionEndOffset === undefined) {
return;
}
if (newSelectionStartOffset >= 0 && newSelectionEndOffset >= 0) {
const newSelectionStart = (<vscode.Position>attr.value.start).translate(0, newSelectionStartOffset);
const newSelectionEnd = (<vscode.Position>attr.value.start).translate(0, newSelectionEndOffset);
return new vscode.Selection(newSelectionStart, newSelectionEnd);
const newSelectionStart = attr.value.start + newSelectionStartOffset;
const newSelectionEnd = attr.value.start + newSelectionEndOffset;
return offsetRangeToSelection(document, newSelectionStart, newSelectionEnd);
}
}