Skip to content

Commit

Permalink
Initial commit for builtin emmet extension #21943
Browse files Browse the repository at this point in the history
  • Loading branch information
ramya-rao-a committed May 24, 2017
1 parent 921107c commit ea55445
Show file tree
Hide file tree
Showing 20 changed files with 1,302 additions and 5 deletions.
3 changes: 2 additions & 1 deletion build/npm/postinstall.js
Expand Up @@ -35,7 +35,8 @@ const extensions = [
'gulp',
'grunt',
'jake',
'merge-conflict'
'merge-conflict',
'emmet'
];

extensions.forEach(extension => npmInstall(`extensions/${extension}`));
Expand Down
70 changes: 70 additions & 0 deletions extensions/emmet/package.json
@@ -0,0 +1,70 @@
{
"name": "emmet",
"displayName": "emmet",
"description": "Emmet support for VS Code",
"version": "0.0.1",
"publisher": "vscode",
"engines": {
"vscode": "^1.10.0"
},
"categories": [
"Other"
],
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/vscode-emmet"
},
"activationEvents": [
"onLanguage:html",
"onLanguage:jade",
"onLanguage:slim",
"onLanguage:haml",
"onLanguage:xml",
"onLanguage:xsl",
"onLanguage:css",
"onLanguage:scss",
"onLanguage:sass",
"onLanguage:less",
"onLanguage:stylus",
"onLanguage:javascriptreact",
"onLanguage:typescriptreact"
],
"main": "./out/extension",
"contributes": {
"configuration": {
"type": "object",
"title": "Emmet configuration",
"properties": {
"emmet.suggestExpandedAbbreviation": {
"type": "boolean",
"default": true,
"description": "Shows expanded emmet abbreviations as suggestions"
},
"emmet.suggestAbbreviations": {
"type": "boolean",
"default": true,
"description": "Shows possible emmet abbreviations as suggestions"
}
}
}
},
"scripts": {
"vscode:prepublish": "tsc -p ./",
"compile": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "node ./node_modules/vscode/bin/test"
},
"devDependencies": {
"typescript": "^2.0.3",
"vscode": "^1.0.0",
"mocha": "^2.3.3",
"@types/node": "^6.0.40",
"@types/mocha": "^2.2.32"
},
"dependencies": {
"@emmetio/expand-abbreviation": "^0.5.4",
"@emmetio/extract-abbreviation": "^0.1.1",
"@emmetio/html-matcher": "^0.3.1",
"@emmetio/css-parser": "^0.3.0"
}
}
57 changes: 57 additions & 0 deletions extensions/emmet/src/abbreviationActions.ts
@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* 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 { getSyntax, getProfile, extractAbbreviation } from './util';

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);
let options = {
field: field,
syntax: getSyntax(editor.document),
profile: getProfile(getSyntax(editor.document)),
text: textToReplace
};

vscode.window.showInputBox({ prompt: 'Enter Abbreviation' }).then(abbr => {
if (!abbr || !abbr.trim()) { return; }
let expandedText = expand(abbr, options);
editor.insertSnippet(new vscode.SnippetString(expandedText), rangeToReplace);
});
}

export function expandAbbreviation() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
let rangeToReplace: vscode.Range = editor.selection;
let abbr = editor.document.getText(rangeToReplace);
if (rangeToReplace.isEmpty) {
[rangeToReplace, abbr] = extractAbbreviation(rangeToReplace.start);
}

let options = {
field: field,
syntax: getSyntax(editor.document),
profile: getProfile(getSyntax(editor.document))
};

let expandedText = expand(abbr, options);
editor.insertSnippet(new vscode.SnippetString(expandedText), rangeToReplace);
}
75 changes: 75 additions & 0 deletions extensions/emmet/src/balance.ts
@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* 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 { getNode, getNodeOuterSelection, getNodeInnerSelection, isStyleSheet } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';

export function balanceOut() {
balance(true);
}

export function balanceIn() {
balance(false);
}

function balance(out: boolean) {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
if (isStyleSheet(editor.document.languageId)) {
return;
}
let getRangeFunction = out ? getRangeToBalanceOut : getRangeToBalanceIn;

let rootNode: Node = parse(editor.document.getText());

let newSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
let range = getRangeFunction(editor.document, selection, rootNode);
if (range) {
newSelections.push(range);
}
});

editor.selection = newSelections[0];
editor.selections = newSelections;
}

function getRangeToBalanceOut(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.Selection {
let offset = document.offsetAt(selection.start);
let nodeToBalance = getNode(rootNode, offset);

let innerSelection = getNodeInnerSelection(document, nodeToBalance);
let outerSelection = getNodeOuterSelection(document, nodeToBalance);

if (innerSelection.contains(selection) && !innerSelection.isEqual(selection)) {
return innerSelection;
}
if (outerSelection.contains(selection) && !outerSelection.isEqual(selection)) {
return outerSelection;
}
return;
}

function getRangeToBalanceIn(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.Selection {
let offset = document.offsetAt(selection.start);
let nodeToBalance: Node = getNode(rootNode, offset);

if (!nodeToBalance.firstChild) {
return selection;
}

if (nodeToBalance.firstChild.start === offset && nodeToBalance.firstChild.end === document.offsetAt(selection.end)) {
return getNodeInnerSelection(document, nodeToBalance.firstChild);
}

return new vscode.Selection(document.positionAt(nodeToBalance.firstChild.start), document.positionAt(nodeToBalance.firstChild.end));

}

70 changes: 70 additions & 0 deletions extensions/emmet/src/editPoint.ts
@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* 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 { validate } from './util';

export function fetchEditPoint(direction: string): void {
let editor = vscode.window.activeTextEditor;
if (!validate()) {
return;
}

let newSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
let updatedSelection = direction === 'next' ? nextEditPoint(selection.anchor, editor) : prevEditPoint(selection.anchor, editor);
newSelections.push(updatedSelection);
});
editor.selections = newSelections;
}

function nextEditPoint(position: vscode.Position, editor: vscode.TextEditor): vscode.Selection {
for (let lineNum = position.line; lineNum < editor.document.lineCount; lineNum++) {
let updatedSelection = findEditPoint(lineNum, editor, position, 'next');
if (updatedSelection) {
return updatedSelection;
}
}
}

function prevEditPoint(position: vscode.Position, editor: vscode.TextEditor): vscode.Selection {
for (let lineNum = position.line; lineNum >= 0; lineNum--) {
let updatedSelection = findEditPoint(lineNum, editor, position, 'prev');
if (updatedSelection) {
return updatedSelection;
}
}
}


function findEditPoint(lineNum: number, editor: vscode.TextEditor, position: vscode.Position, direction: string): vscode.Selection {
let line = editor.document.lineAt(lineNum);

if (lineNum !== position.line && line.isEmptyOrWhitespace) {
editor.selection = new vscode.Selection(lineNum, 0, lineNum, 0);
return;
}

let lineContent = line.text;
if (lineNum === position.line && direction === 'prev') {
lineContent = lineContent.substr(0, position.character);
}
let emptyAttrIndex = direction === 'next' ? lineContent.indexOf('""', lineNum === position.line ? position.character : 0) : lineContent.lastIndexOf('""');
let emptyTagIndex = direction === 'next' ? lineContent.indexOf('><', lineNum === position.line ? position.character : 0) : lineContent.lastIndexOf('><');

let winner = -1;

if (emptyAttrIndex > -1 && emptyTagIndex > -1) {
winner = direction === 'next' ? Math.min(emptyAttrIndex, emptyTagIndex) : Math.max(emptyAttrIndex, emptyTagIndex);
} else if (emptyAttrIndex > -1) {
winner = emptyAttrIndex;
} else {
winner = emptyTagIndex;
}

if (winner > -1) {
return new vscode.Selection(lineNum, winner + 1, lineNum, winner + 1);
}
}
105 changes: 105 additions & 0 deletions extensions/emmet/src/emmetCompletionProvider.ts
@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* 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, createSnippetsRegistry } from '@emmetio/expand-abbreviation';
import { getSyntax, isStyleSheet, getProfile, extractAbbreviation } from './util';

const field = (index, placeholder) => `\${${index}${placeholder ? ':' + placeholder : ''}}`;
const snippetCompletionsCache = new Map<string, vscode.CompletionItem[]>();

export class EmmetCompletionItemProvider implements vscode.CompletionItemProvider {

public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable<vscode.CompletionList> {

if (!vscode.workspace.getConfiguration('emmet')['useModules']) {
return Promise.resolve(null);
}

let currentWord = getCurrentWord(document, position);
let expandedAbbr = getExpandedAbbreviation(document, position);
let abbreviationSuggestions = getAbbreviationSuggestions(getSyntax(document), currentWord, (expandedAbbr && currentWord === expandedAbbr.label));
let completionItems = expandedAbbr ? [expandedAbbr, ...abbreviationSuggestions] : abbreviationSuggestions;

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

function getExpandedAbbreviation(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem {
if (!vscode.workspace.getConfiguration('emmet')['suggestExpandedAbbreviation']) {
return;
}
let [rangeToReplace, wordToExpand] = extractAbbreviation(position);
if (!rangeToReplace || !wordToExpand) {
return;
}
let syntax = getSyntax(document);
let expandedWord = expand(wordToExpand, {
field: field,
syntax: syntax,
profile: getProfile(syntax)
});

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

// 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
if (!isStyleSheet(syntax)) {
completionitem.kind = vscode.CompletionItemKind.Value;
}
return completionitem;
}

function getCurrentWord(document: vscode.TextDocument, position: vscode.Position): string {
let wordAtPosition = document.getWordRangeAtPosition(position);
let currentWord = '';
if (wordAtPosition && wordAtPosition.start.character < position.character) {
let word = document.getText(wordAtPosition);
currentWord = word.substr(0, position.character - wordAtPosition.start.character);
}

return currentWord;
}

function removeTabStops(expandedWord: string): string {
return expandedWord.replace(/\$\{\d+\}/g, '').replace(/\$\{\d+:([^\}]+)\}/g, '$1');
}
function getAbbreviationSuggestions(syntax: string, prefix: string, skipExactMatch: boolean) {
if (!vscode.workspace.getConfiguration('emmet')['suggestAbbreviations'] || !prefix || isStyleSheet(syntax)) {
return [];
}

if (!snippetCompletionsCache.has(syntax)) {
let registry = createSnippetsRegistry(syntax);
let completions: vscode.CompletionItem[] = registry.all({ type: 'string' }).map(snippet => {
let expandedWord = expand(snippet.value, {
field: field,
syntax: syntax
});

let item = new vscode.CompletionItem(snippet.key);
item.documentation = removeTabStops(expandedWord);
item.detail = 'Complete Emmet Abbreviation';
item.insertText = snippet.key;
return item;
});
snippetCompletionsCache.set(syntax, completions);
}

let snippetCompletions = snippetCompletionsCache.get(syntax);

snippetCompletions = snippetCompletions.filter(x => x.label.startsWith(prefix) && (!skipExactMatch || x.label !== prefix));

return snippetCompletions;

}



0 comments on commit ea55445

Please sign in to comment.