diff --git a/extensions/emmet/src/removeTag.ts b/extensions/emmet/src/removeTag.ts index 9209a6d75e8..5a10fef9ee7 100644 --- a/extensions/emmet/src/removeTag.ts +++ b/extensions/emmet/src/removeTag.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { parseDocument, validate, getHtmlNode } from './util'; -import { HtmlNode } from 'EmmetNode'; +import { validate, getHtmlNodeLS, toLSTextDocument, offsetRangeToVsRange } from './util'; export function removeTag() { if (!validate(false) || !vscode.window.activeTextEditor) { @@ -13,54 +12,113 @@ export function removeTag() { } const editor = vscode.window.activeTextEditor; - let rootNode = parseDocument(editor.document); - if (!rootNode) { - return; - } - - let indentInSpaces = ''; - const tabSize: number = editor.options.tabSize ? +editor.options.tabSize : 0; - for (let i = 0; i < tabSize || 0; i++) { - indentInSpaces += ' '; - } - - let rangesToRemove: vscode.Range[] = []; - editor.selections.reverse().forEach(selection => { - rangesToRemove = rangesToRemove.concat(getRangeToRemove(editor, rootNode, selection, indentInSpaces)); - }); + const tabSize: number = +editor.options.tabSize!; + let finalRangesToRemove = editor.selections.reverse() + .reduce((prev, selection) => + prev.concat(getRangesToRemove(editor.document, selection, tabSize)), []); return editor.edit(editBuilder => { - rangesToRemove.forEach(range => { + finalRangesToRemove.forEach(range => { editBuilder.replace(range, ''); }); }); } -function getRangeToRemove(editor: vscode.TextEditor, rootNode: HtmlNode, selection: vscode.Selection, indentInSpaces: string): vscode.Range[] { - - let nodeToUpdate = getHtmlNode(editor.document, rootNode, selection.start, true); +/** + * Calculates the ranges to remove, along with what to replace those ranges with. + * It finds the node to remove based on the selection's start position + * and then removes that node, reindenting the content in between. + * Assumption: The document indents consist of only tabs or only spaces. + */ +function getRangesToRemove(document: vscode.TextDocument, selection: vscode.Selection, tabSize: number): vscode.Range[] { + const lsDocument = toLSTextDocument(document); + const nodeToUpdate = getHtmlNodeLS(lsDocument, selection.start, true); if (!nodeToUpdate) { return []; } - let openRange = new vscode.Range(nodeToUpdate.open.start, nodeToUpdate.open.end); - let closeRange: vscode.Range | null = null; - if (nodeToUpdate.close) { - closeRange = new vscode.Range(nodeToUpdate.close.start, nodeToUpdate.close.end); + const openTagRange = offsetRangeToVsRange(lsDocument, nodeToUpdate.start, nodeToUpdate.startTagEnd ?? nodeToUpdate.end); + let closeTagRange: vscode.Range | undefined; + if (nodeToUpdate.endTagStart !== undefined) { + closeTagRange = offsetRangeToVsRange(lsDocument, nodeToUpdate.endTagStart, nodeToUpdate.end); } - let ranges = [openRange]; - if (closeRange) { - for (let i = openRange.start.line + 1; i <= closeRange.start.line; i++) { - let lineContent = editor.document.lineAt(i).text; - if (lineContent.startsWith('\t')) { - ranges.push(new vscode.Range(i, 0, i, 1)); - } else if (lineContent.startsWith(indentInSpaces)) { - ranges.push(new vscode.Range(i, 0, i, indentInSpaces.length)); - } + let rangesToRemove = [openTagRange]; + if (closeTagRange) { + const indentAmountToRemove = calculateIndentAmountToRemove(document, openTagRange, closeTagRange, tabSize); + for (let i = openTagRange.start.line + 1; i < closeTagRange.start.line; i++) { + rangesToRemove.push(new vscode.Range(i, 0, i, indentAmountToRemove)); } - ranges.push(closeRange); + rangesToRemove.push(closeTagRange); } - return ranges; + return rangesToRemove; } +type IndentInfo = { + indentAmount: number, + tabsOnly: boolean +}; + +/** + * Calculates the amount of indent to remove for getRangesToRemove. + */ +function calculateIndentAmountToRemove(document: vscode.TextDocument, openRange: vscode.Range, closeRange: vscode.Range, tabSize: number): number { + const startLine = openRange.start.line; + const endLine = closeRange.start.line; + + const startLineIndent = calculateLineIndentInSpaces(document.lineAt(startLine).text, tabSize); + const endLineIndent = calculateLineIndentInSpaces(document.lineAt(endLine).text, tabSize); + + let contentIndent: IndentInfo | undefined; + for (let i = startLine + 1; i <= endLine - 1; i++) { + const lineContent = document.lineAt(i).text; + const indent = calculateLineIndentInSpaces(lineContent, tabSize); + contentIndent = !contentIndent ? indent : + { + indentAmount: Math.min(contentIndent.indentAmount, indent.indentAmount), + tabsOnly: contentIndent.tabsOnly && indent.tabsOnly + }; + } + + let indentAmountSpaces = 0; + let tabsOnly = startLineIndent.tabsOnly && endLineIndent.tabsOnly; + + if (contentIndent) { + if (contentIndent.indentAmount < startLineIndent.indentAmount + || contentIndent.indentAmount < endLineIndent.indentAmount) { + indentAmountSpaces = 0; + } + else { + indentAmountSpaces = Math.min( + contentIndent.indentAmount - startLineIndent.indentAmount, + contentIndent.indentAmount - endLineIndent.indentAmount + ); + } + tabsOnly = tabsOnly && contentIndent.tabsOnly; + } + return tabsOnly ? Math.trunc(indentAmountSpaces / tabSize) : indentAmountSpaces; +} + +function calculateLineIndentInSpaces(line: string, tabSize: number): IndentInfo { + const whiteSpaceMatch = line.match(/^\s+/); + const whiteSpaceContent = whiteSpaceMatch ? whiteSpaceMatch[0] : ''; + + if (!whiteSpaceContent) { + return { indentAmount: 0, tabsOnly: true }; + } + + let numSpaces = 0; + let numTabs = 0; + let tabsOnly = true; + for (const c of whiteSpaceContent) { + if (c === '\t') { + numTabs++; + } + else { + numSpaces++; + tabsOnly = false; + } + } + + return { indentAmount: numTabs * tabSize + numSpaces, tabsOnly }; +} diff --git a/extensions/emmet/src/test/tagActions.test.ts b/extensions/emmet/src/test/tagActions.test.ts index 80f2d30ff5c..8c217bf3cbd 100644 --- a/extensions/emmet/src/test/tagActions.test.ts +++ b/extensions/emmet/src/test/tagActions.test.ts @@ -175,7 +175,7 @@ suite('Tests for Emmet actions on html tags', () => {
  • Hello
  • There
  • Bye
  • -\t +\t\t `;