/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; import { expand } from '@emmetio/expand-abbreviation'; import { Node, HtmlNode, Rule } from 'EmmetNode'; import { getNode, getInnerRange, getMappingForIncludedLanguages, parse, validate } from './util'; import { getExpandOptions, extractAbbreviation, isStyleSheet, isAbbreviationValid, getEmmetMode } from 'vscode-emmet-helper'; interface ExpandAbbreviationInput { syntax: string; abbreviation: string; rangeToReplace: vscode.Range; textToWrap?: string; preceedingWhiteSpace?: string; } export function wrapWithAbbreviation(args) { const syntax = getSyntaxFromArgs(args); if (!syntax || !validate()) { return; } const editor = vscode.window.activeTextEditor; const newLine = editor.document.eol === vscode.EndOfLine.LF ? '\n' : '\r\n'; vscode.window.showInputBox({ prompt: 'Enter Abbreviation' }).then(abbreviation => { if (!abbreviation || !abbreviation.trim() || !isAbbreviationValid(syntax, abbreviation)) { return; } let expandAbbrList: ExpandAbbreviationInput[] = []; let firstTextToReplace: string; let allTextToReplaceSame: boolean = true; editor.selections.forEach(selection => { let rangeToReplace: vscode.Range = selection.isReversed ? new vscode.Range(selection.active, selection.anchor) : selection; if (rangeToReplace.isEmpty) { rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, editor.document.lineAt(rangeToReplace.start.line).text.length); } const firstLine = editor.document.lineAt(rangeToReplace.start).text; const firstLineTillSelection = firstLine.substr(0, rangeToReplace.start.character); const whitespaceBeforeSelection = /^\s*$/.test(firstLineTillSelection); let textToWrap = ''; let preceedingWhiteSpace = ''; if (whitespaceBeforeSelection) { const matches = firstLine.match(/^(\s*)/); if (matches) { preceedingWhiteSpace = matches[1]; } if (rangeToReplace.start.character <= preceedingWhiteSpace.length) { rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.end.line, rangeToReplace.end.character); } textToWrap = newLine; for (let i = rangeToReplace.start.line; i <= rangeToReplace.end.line; i++) { textToWrap += '\t' + editor.document.lineAt(i).text.substr(preceedingWhiteSpace.length) + newLine; } } else { textToWrap = editor.document.getText(rangeToReplace); } if (!firstTextToReplace) { firstTextToReplace = textToWrap; } else if (allTextToReplaceSame && firstTextToReplace !== textToWrap) { allTextToReplaceSame = false; } expandAbbrList.push({ syntax, abbreviation, rangeToReplace, textToWrap, preceedingWhiteSpace }); }); if (!allTextToReplaceSame) { expandAbbrList.forEach(input => { input.textToWrap = '\n\$TM_SELECTED_TEXT\n'; }); } expandAbbreviationInRange(editor, expandAbbrList, true); }); } export function expandAbbreviation(args) { const syntax = getSyntaxFromArgs(args); if (!syntax || !validate()) { return; } const editor = vscode.window.activeTextEditor; let rootNode = parse(editor.document); if (!rootNode) { return; } let abbreviationList: ExpandAbbreviationInput[] = []; let firstAbbreviation: string; let allAbbreviationsSame: boolean = true; let getAbbreviation = (document: vscode.TextDocument, selection: vscode.Selection, position: vscode.Position, isHtml: boolean): [vscode.Range, string] => { let rangeToReplace: vscode.Range = selection; let abbreviation = document.getText(rangeToReplace); if (!rangeToReplace.isEmpty) { return [rangeToReplace, abbreviation]; } // Expand cases like
explicitly // else we will end up with < if (isHtml) { const currentLine = editor.document.lineAt(position.line).text; const textTillPosition = currentLine.substr(0, position.character); let matches = textTillPosition.match(/<(\w+)$/); if (matches) { abbreviation = matches[1]; rangeToReplace = new vscode.Range(position.translate(0, -(abbreviation.length + 1)), position); return [rangeToReplace, abbreviation]; } } return extractAbbreviation(editor.document, position); }; editor.selections.forEach(selection => { let position = selection.isReversed ? selection.anchor : selection.active; let [rangeToReplace, abbreviation] = getAbbreviation(editor.document, selection, position, syntax === 'html'); if (!isAbbreviationValid(syntax, abbreviation)) { vscode.window.showErrorMessage('Emmet: Invalid abbreviation'); return; } let currentNode = getNode(rootNode, position); if (!isValidLocationForEmmetAbbreviation(currentNode, syntax, position)) { return; } if (!firstAbbreviation) { firstAbbreviation = abbreviation; } else if (allAbbreviationsSame && firstAbbreviation !== abbreviation) { allAbbreviationsSame = false; } abbreviationList.push({ syntax, abbreviation, rangeToReplace }); }); expandAbbreviationInRange(editor, abbreviationList, allAbbreviationsSame); } /** * Checks if given position is a valid location to expand emmet abbreviation. * Works only on html and css/less/scss syntax * @param currentNode parsed node at given position * @param syntax syntax of the abbreviation * @param position position to validate */ export function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: string, position: vscode.Position): boolean { if (!currentNode) { return true; } if (isStyleSheet(syntax)) { if (currentNode.type !== 'rule') { return true; } const currentCssNode =