Skip to content

Commit

Permalink
Consolidate emmet expand action and emmet completion
Browse files Browse the repository at this point in the history
  • Loading branch information
ramya-rao-a committed Jun 20, 2017
1 parent 7194723 commit 8902a30
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 79 deletions.
100 changes: 92 additions & 8 deletions extensions/emmet/src/abbreviationActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@

import * as vscode from 'vscode';
import { expand } from '@emmetio/expand-abbreviation';
import { getSyntax, getProfile, extractAbbreviation } from './util';
import * as extract from '@emmetio/extract-abbreviation';
import parseStylesheet from '@emmetio/css-parser';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';

import { getSyntax, getProfile, isStyleSheet, getNode, getInnerRange } from './util';
import { DocumentStreamReader } from './bufferStream';

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

Expand Down Expand Up @@ -42,19 +48,97 @@ export function expandAbbreviation() {
vscode.window.showInformationMessage('No editor is active');
return;
}
let rangeToReplace: vscode.Range = editor.selection;
let abbr = editor.document.getText(rangeToReplace);
let syntax = getSyntax(editor.document);
let output = expandAbbreviationHelper(syntax, editor.document, editor.selection);
if (output) {
editor.insertSnippet(new vscode.SnippetString(output.expandedText), output.rangeToReplace);
}
}

export interface ExpandAbbreviationHelperOutput {
expandedText: string;
rangeToReplace: vscode.Range;
abbreviation: string;
syntax: string;
}

/**
* Expands abbreviation at given range in the given document
* @param syntax
* @param document
* @param rangeToReplace
*/
export function expandAbbreviationHelper(syntax: string, document: vscode.TextDocument, rangeToReplace: vscode.Range): ExpandAbbreviationHelperOutput {
let parseContent = isStyleSheet(syntax) ? parseStylesheet : parse;
let rootNode: Node = parseContent(new DocumentStreamReader(document));
let currentNode = getNode(rootNode, rangeToReplace.end);

if (forceCssSyntax(syntax, currentNode, rangeToReplace.end)) {
syntax = 'css';
} else if (!isValidLocationForEmmetAbbreviation(currentNode, syntax, rangeToReplace.end)) {
return;
}

let abbreviation = document.getText(rangeToReplace);
if (rangeToReplace.isEmpty) {
[rangeToReplace, abbr] = extractAbbreviation(rangeToReplace.start);
[rangeToReplace, abbreviation] = extractAbbreviation(document, rangeToReplace.start);
}
let syntax = getSyntax(editor.document);

let options = {
field: field,
syntax: syntax,
profile: getProfile(getSyntax(editor.document)),
profile: getProfile(syntax),
addons: syntax === 'jsx' ? { 'jsx': true } : null
};

let expandedText = expand(abbr, options);
editor.insertSnippet(new vscode.SnippetString(expandedText), rangeToReplace);
let expandedText = expand(abbreviation, options);
return { expandedText, rangeToReplace, abbreviation, syntax };
}

/**
* 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;
}
78 changes: 17 additions & 61 deletions extensions/emmet/src/emmetCompletionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@

import * as vscode from 'vscode';
import { expand, createSnippetsRegistry } from '@emmetio/expand-abbreviation';
import { getSyntax, getProfile, extractAbbreviation, isStyleSheet, getNode } from './util';
import parseStylesheet from '@emmetio/css-parser';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
import { DocumentStreamReader } from './bufferStream';
import { getSyntax, isStyleSheet } from './util';
import { expandAbbreviationHelper, ExpandAbbreviationHelperOutput } from './abbreviationActions';

const field = (index, placeholder) => `\${${index}${placeholder ? ':' + placeholder : ''}}`;
const snippetCompletionsCache = new Map<string, vscode.CompletionItem[]>();
Expand All @@ -23,65 +20,35 @@ export class EmmetCompletionItemProvider implements vscode.CompletionItemProvide
return Promise.resolve(null);
}

let completionItems: vscode.CompletionItem[] = [];
let syntax = getSyntax(document);
let currentWord = getCurrentWord(document, position);

let parseContent = isStyleSheet(syntax) ? parseStylesheet : parse;
let rootNode: Node = parseContent(new DocumentStreamReader(document));
let currentNode = getNode(rootNode, position);

// Inside <style> tag, trigger css abbreviations
if (!isStyleSheet(syntax) && currentNode && currentNode.name === 'style') {
syntax = 'css';
let expandedAbbr: vscode.CompletionItem;
if (vscode.workspace.getConfiguration('emmet')['showExpandedAbbreviation']) {
let output: ExpandAbbreviationHelperOutput = expandAbbreviationHelper(syntax, document, new vscode.Range(position, position));
if (output) {
expandedAbbr = new vscode.CompletionItem(output.abbreviation);
expandedAbbr.insertText = new vscode.SnippetString(output.expandedText);
expandedAbbr.documentation = removeTabStops(output.expandedText);
expandedAbbr.range = output.rangeToReplace;
expandedAbbr.detail = 'Expand Emmet Abbreviation';
syntax = output.syntax;
}
}

let expandedAbbr = this.getExpandedAbbreviation(document, position, syntax, currentNode);

let completionItems: vscode.CompletionItem[] = expandedAbbr ? [expandedAbbr] : [];
if (!isStyleSheet(syntax)) {
if (expandedAbbr) {
// In non stylesheet like syntax, this extension returns expanded abbr plus posssible abbr completions
// To differentiate between the 2, the former is given CompletionItemKind.Value so that it gets a different icon
expandedAbbr.kind = vscode.CompletionItemKind.Value;
}
let currentWord = getCurrentWord(document, position);

let abbreviationSuggestions = this.getAbbreviationSuggestions(syntax, currentWord, (expandedAbbr && currentWord === expandedAbbr.label));
completionItems = expandedAbbr ? [expandedAbbr, ...abbreviationSuggestions] : abbreviationSuggestions;
} else {
completionItems = expandedAbbr ? [expandedAbbr] : [];
completionItems = completionItems.concat(abbreviationSuggestions);
}

return Promise.resolve(new vscode.CompletionList(completionItems, true));
}

getExpandedAbbreviation(document: vscode.TextDocument, position: vscode.Position, syntax: string, currentNode: Node): vscode.CompletionItem {
if (!vscode.workspace.getConfiguration('emmet')['showExpandedAbbreviation']) {
return;
}
let [rangeToReplace, wordToExpand] = extractAbbreviation(position);
if (!rangeToReplace || !wordToExpand) {
return;
}
if (!isValidLocationForEmmetAbbreviation(currentNode, syntax, position)) {
return;
}

let expandedWord = expand(wordToExpand, {
field: field,
syntax: syntax,
profile: getProfile(syntax),
addons: syntax === 'jsx' ? { 'jsx': true } : null
});

let completionitem = new vscode.CompletionItem(wordToExpand);
completionitem.insertText = new vscode.SnippetString(expandedWord);
completionitem.documentation = removeTabStops(expandedWord);
completionitem.range = rangeToReplace;
completionitem.detail = 'Expand Emmet Abbreviation';


return completionitem;
}

getAbbreviationSuggestions(syntax: string, prefix: string, skipExactMatch: boolean) {
if (!vscode.workspace.getConfiguration('emmet')['showAbbreviationSuggestions'] || !prefix) {
return [];
Expand Down Expand Up @@ -131,17 +98,6 @@ function removeTabStops(expandedWord: string): string {
return expandedWord.replace(/\$\{\d+\}/g, '').replace(/\$\{\d+:([^\}]+)\}/g, '$1');
}

function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: string, position: vscode.Position): boolean {
if (!currentNode) {
return true;
}

if (isStyleSheet(syntax)) {
return currentNode.type !== 'rule';
}

return position.isAfter(currentNode.open.end);
}



14 changes: 4 additions & 10 deletions extensions/emmet/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import * as vscode from 'vscode';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
import * as extract from '@emmetio/extract-abbreviation';
import { DocumentStreamReader } from './bufferStream';

export function validate(allowStylesheet: boolean = true): boolean {
Expand Down Expand Up @@ -112,16 +111,11 @@ export function getNode(root: Node, position: vscode.Position, includeNodeBounda
return foundNode;
}

export function extractAbbreviation(position: vscode.Position): [vscode.Range, string] {
let editor = vscode.window.activeTextEditor;
let currentLine = editor.document.lineAt(position.line).text;
let result = extract(currentLine, position.character, true);
if (!result) {
return [null, ''];
export function getInnerRange(currentNode: Node): vscode.Range {
if (!currentNode.close) {
return;
}

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

export function getDeepestNode(node: Node): Node {
Expand Down

0 comments on commit 8902a30

Please sign in to comment.