/*--------------------------------------------------------------------------------------------- * 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 { Node, HtmlNode, Rule } from 'EmmetNode'; import { getNode, getInnerRange, getMappingForIncludedLanguages, parseDocument, validate } from './util'; import { getExpandOptions, extractAbbreviation, extractAbbreviationFromText, isStyleSheet, isAbbreviationValid, getEmmetMode, expandAbbreviation } from 'vscode-emmet-helper'; const trimRegex = /[\u00a0]*[\d|#|\-|\*|\u2022]+\.?/; interface ExpandAbbreviationInput { syntax: string; abbreviation: string; rangeToReplace: vscode.Range; textToWrap?: string[]; filters?: string[]; } export function wrapWithAbbreviation(args) { if (!validate(false)) { return; } const editor = vscode.window.activeTextEditor; const abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation' }); const syntax = getSyntaxFromArgs({ language: editor.document.languageId }); return abbreviationPromise.then(abbreviation => { if (!abbreviation || !abbreviation.trim() || !isAbbreviationValid(syntax, abbreviation)) { return; } let expandAbbrList: ExpandAbbreviationInput[] = []; 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 firstLineOfSelection = editor.document.lineAt(rangeToReplace.start).text.substr(rangeToReplace.start.character); const matches = firstLineOfSelection.match(/^(\s*)/); const preceedingWhiteSpace = matches ? matches[1].length : 0; rangeToReplace = new vscode.Range(rangeToReplace.start.line, rangeToReplace.start.character + preceedingWhiteSpace, rangeToReplace.end.line, rangeToReplace.end.character); expandAbbrList.push({ syntax, abbreviation, rangeToReplace, textToWrap: ['\n\t\$TM_SELECTED_TEXT\n'] }); }); return expandAbbreviationInRange(editor, expandAbbrList, true); }); } export function wrapIndividualLinesWithAbbreviation(args) { if (!validate(false)) { return; } const editor = vscode.window.activeTextEditor; if (editor.selection.isEmpty) { vscode.window.showInformationMessage('Select more than 1 line and try again.'); return; } const abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation' }); const syntax = getSyntaxFromArgs({ language: editor.document.languageId }); const lines = editor.document.getText(editor.selection).split('\n').map(x => x.trim()); return abbreviationPromise.then(inputAbbreviation => { if (!inputAbbreviation || !inputAbbreviation.trim() || !isAbbreviationValid(syntax, inputAbbreviation)) { return; } let { abbreviation, filters } = extractAbbreviationFromText(inputAbbreviation); let input: ExpandAbbreviationInput = { syntax, abbreviation, rangeToReplace: editor.selection, textToWrap: lines, filters }; return expandAbbreviationInRange(editor, [input], true); }); } export function expandEmmetAbbreviation(args) { const syntax = getSyntaxFromArgs(args); if (!syntax || !validate()) { return; } const editor = vscode.window.activeTextEditor; let rootNode = parseDocument(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, string[]] => { let rangeToReplace: vscode.Range = selection; let abbr = document.getText(rangeToReplace); if (!rangeToReplace.isEmpty) { let { abbreviation, filters } = extractAbbreviationFromText(abbr); return [rangeToReplace, abbreviation, filters]; } // 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) { abbr = matches[1]; rangeToReplace = new vscode.Range(position.translate(0, -(abbr.length + 1)), position); return [rangeToReplace, abbr, []]; } } let { abbreviationRange, abbreviation, filters } = extractAbbreviation(editor.document, position); return [new vscode.Range(abbreviationRange.start.line, abbreviationRange.start.character, abbreviationRange.end.line, abbreviationRange.end.character), abbreviation, filters]; }; editor.selections.forEach(selection => { let position = selection.isReversed ? selection.anchor : selection.active; let [rangeToReplace, abbreviation, filters] = 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, filters }); }); return 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 !isStyleSheet(syntax); } if (isStyleSheet(syntax)) { if (currentNode.type !== 'rule') { return true; } const currentCssNode =