abbreviationActions.ts 5.5 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  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';
8 9 10 11 12
import * as extract from '@emmetio/extract-abbreviation';
import parseStylesheet from '@emmetio/css-parser';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';

13
import { getSyntax, getProfile, getVariables, isStyleSheet, getNode, getInnerRange } from './util';
14
import { DocumentStreamReader } from './bufferStream';
15 16 17 18 19 20 21 22 23 24 25 26 27 28

const field = (index, placeholder) => `\${${index}${placeholder ? ':' + placeholder : ''}}`;

export function wrapWithAbbreviation() {
	let editor = vscode.window.activeTextEditor;
	if (!editor) {
		vscode.window.showInformationMessage('No editor is active');
		return;
	}
	let rangeToReplace: vscode.Range = editor.selection;
	if (rangeToReplace.isEmpty) {
		rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, editor.document.lineAt(rangeToReplace.start.line).text.length);
	}
	let textToReplace = editor.document.getText(rangeToReplace);
29
	let syntax = getSyntax(editor.document);
30 31 32

	vscode.window.showInputBox({ prompt: 'Enter Abbreviation' }).then(abbr => {
		if (!abbr || !abbr.trim()) { return; }
33
		let expandedText = expand(abbr, getExpandOptions(syntax, textToReplace));
34 35 36 37
		editor.insertSnippet(new vscode.SnippetString(expandedText), rangeToReplace);
	});
}

38 39
export function expandAbbreviation(args) {

40 41 42 43 44
	let editor = vscode.window.activeTextEditor;
	if (!editor) {
		vscode.window.showInformationMessage('No editor is active');
		return;
	}
45
	if (typeof args !== 'object' || !args['syntax']) {
46 47
		return;
	}
48
	let output = expandAbbreviationHelper(args['syntax'], editor.document, editor.selection);
49
	if (output) {
50
		editor.insertSnippet(new vscode.SnippetString(output.expandedText), output.abbreviationRange);
51 52 53 54 55
	}
}

export interface ExpandAbbreviationHelperOutput {
	expandedText: string;
56
	abbreviationRange: vscode.Range;
57 58 59 60 61 62
	abbreviation: string;
	syntax: string;
}

/**
 * Expands abbreviation at given range in the given document
63 64 65 66
 * @param syntax string syntax to be used for expanding abbreviations
 * @param document vscode.TextDocument
 * @param abbreviationRange vscode.Range range of the abbreviation that needs to be expanded
 * */
67
export function expandAbbreviationHelper(syntax: string, document: vscode.TextDocument, abbreviationRange: vscode.Range): ExpandAbbreviationHelperOutput {
68 69 70
	if (!syntax) {
		return;
	}
71 72 73
	let abbreviation = document.getText(abbreviationRange);
	if (abbreviationRange.isEmpty) {
		[abbreviationRange, abbreviation] = extractAbbreviation(document, abbreviationRange.start);
74
	}
75

76
	let expandedText = expand(abbreviation, getExpandOptions(syntax));
77
	return { expandedText, abbreviationRange, abbreviation, syntax };
78 79
}

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
/**
 * Checks whether given position is valid for emmet abbreviation and returns appropriate syntax
 * @param syntax string language mode of current document
 * @param document vscode.Textdocument
 * @param position vscode.Position position of the abbreviation that needs to be expanded
 */
export function syntaxHelper(syntax: string, document: vscode.TextDocument, position: vscode.Position): string {
	let parseContent = isStyleSheet(syntax) ? parseStylesheet : parse;
	let rootNode: Node = parseContent(new DocumentStreamReader(document));
	let currentNode = getNode(rootNode, position);

	if (forceCssSyntax(syntax, currentNode, position)) {
		return 'css';
	} else if (!isValidLocationForEmmetAbbreviation(currentNode, syntax, position)) {
		return;
	}
96
	return syntax;
97 98
}

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
/**
 * Extracts abbreviation from the given position in the given document
 */
function extractAbbreviation(document: vscode.TextDocument, position: vscode.Position): [vscode.Range, string] {
	let currentLine = document.lineAt(position.line).text;
	let result = extract(currentLine, position.character, true);
	if (!result) {
		return [null, ''];
	}

	let rangeToReplace = new vscode.Range(position.line, result.location, position.line, result.location + result.abbreviation.length);
	return [rangeToReplace, result.abbreviation];
}

/**
 * Inside <style> tag, force use of css abbreviations
 */
function forceCssSyntax(syntax: string, currentNode: Node, position: vscode.Position): boolean {
	return !isStyleSheet(syntax)
		&& currentNode
		&& currentNode.close
		&& currentNode.name === 'style'
		&& getInnerRange(currentNode).contains(position);
}

/**
 * Checks if given position is a valid location to expand emmet abbreviation
 * @param currentNode parsed node at given position
 * @param syntax syntax of the abbreviation
 * @param position position to validate
 */
function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: string, position: vscode.Position): boolean {
	if (!currentNode) {
		return true;
	}

	if (isStyleSheet(syntax)) {
		return currentNode.type !== 'rule'
			|| (currentNode.selectorToken && position.isAfter(currentNode.selectorToken.end));
	}

	if (currentNode.close) {
		return getInnerRange(currentNode).contains(position);
	}

	return false;
145 146
}

147
export function getExpandOptions(syntax: string, textToReplace?: string) {
148 149 150 151 152
	return {
		field: field,
		syntax: syntax,
		profile: getProfile(syntax),
		addons: syntax === 'jsx' ? { 'jsx': true } : null,
153
		variables: getVariables(),
154 155
		text: textToReplace ? textToReplace : ''
	};
156
}