提交 8e0f07fd 编写于 作者: R Ramya Achutha Rao 提交者: Ramya Rao

Initial commit for builtin emmet extension #21943

上级 921107cd
......@@ -35,7 +35,8 @@ const extensions = [
'gulp',
'grunt',
'jake',
'merge-conflict'
'merge-conflict',
'emmet'
];
extensions.forEach(extension => npmInstall(`extensions/${extension}`));
......
{
"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"
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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);
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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));
}
/*---------------------------------------------------------------------------------------------
* 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);
}
}
/*---------------------------------------------------------------------------------------------
* 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;
}
/*---------------------------------------------------------------------------------------------
* 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 { EmmetCompletionItemProvider } from './emmetCompletionProvider';
import { expandAbbreviation, wrapWithAbbreviation } from './abbreviationActions';
import { removeTag } from './removeTag';
import { updateTag } from './updateTag';
import { matchTag } from './matchTag';
import { balanceOut, balanceIn } from './balance';
import { splitJoinTag } from './splitJoinTag';
import { mergeLines } from './mergeLines';
import { toggleComment } from './toggleComment';
import { fetchEditPoint } from './editPoint';
import { fetchSelectItem } from './selectItem';
interface ISupportedLanguageMode {
id: string;
triggerCharacters: string[];
}
const SUPPORTED_LANGUAGE_MODES: ISupportedLanguageMode[] = [
{ id: 'html', triggerCharacters: ['!', '.'] },
{ id: 'jade', triggerCharacters: ['!', '.'] },
{ id: 'slim', triggerCharacters: ['!', '.'] },
{ id: 'haml', triggerCharacters: ['!', '.'] },
{ id: 'xml', triggerCharacters: ['.'] },
{ id: 'xsl', triggerCharacters: ['.'] },
{ id: 'css', triggerCharacters: [':'] },
{ id: 'scss', triggerCharacters: [':'] },
{ id: 'sass', triggerCharacters: [':'] },
{ id: 'less', triggerCharacters: [':'] },
{ id: 'stylus', triggerCharacters: [':'] },
{ id: 'javascriptreact', triggerCharacters: ['.'] },
{ id: 'typescriptreact', triggerCharacters: ['.'] }
];
export function activate(context: vscode.ExtensionContext) {
let completionProvider = new EmmetCompletionItemProvider();
for (let language of SUPPORTED_LANGUAGE_MODES) {
const selector: vscode.DocumentFilter = { language: language.id, scheme: 'file' };
const provider = vscode.languages.registerCompletionItemProvider(selector, completionProvider, ...language.triggerCharacters);
context.subscriptions.push(provider);
}
context.subscriptions.push(vscode.commands.registerCommand('emmet.wrapWithAbbreviation', () => {
wrapWithAbbreviation();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.expandAbbreviation', () => {
expandAbbreviation();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.removeTag', () => {
removeTag();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.updateTag', () => {
vscode.window.showInputBox({ prompt: 'Enter Tag' }).then(tagName => {
updateTag(tagName);
});
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.matchTag', () => {
matchTag();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.balanceOut', () => {
balanceOut();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.balanceIn', () => {
balanceIn();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.splitJoinTag', () => {
splitJoinTag();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.mergeLines', () => {
mergeLines();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.toggleComment', () => {
toggleComment();
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.nextEditPoint', () => {
fetchEditPoint('next');
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.prevEditPoint', () => {
fetchEditPoint('prev');
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.selectNextItem', () => {
fetchSelectItem('next');
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.selectPrevItem', () => {
fetchSelectItem('prev');
}));
}
export function deactivate() {
}
/*---------------------------------------------------------------------------------------------
* 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 } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
export function matchTag() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
let rootNode: Node = parse(editor.document.getText());
let updatedSelections = [];
editor.selections.forEach(selection => {
let updatedSelection = getUpdatedSelections(editor, editor.document.offsetAt(selection.start), rootNode);
if (updatedSelection) {
updatedSelections.push(updatedSelection);
}
});
if (updatedSelections.length > 0) {
editor.selections = updatedSelections;
}
}
function getUpdatedSelections(editor: vscode.TextEditor, offset: number, rootNode: Node): vscode.Selection {
let currentNode = getNode(rootNode, offset);
// If no closing tag or cursor is between open and close tag, then no-op
if (!currentNode.close || (currentNode.open.end < offset && currentNode.close.start > offset)) {
return;
}
if (offset <= currentNode.open.end) {
let matchingPosition = editor.document.positionAt(currentNode.close.start);
return new vscode.Selection(matchingPosition, matchingPosition);
} else {
let matchingPosition = editor.document.positionAt(currentNode.open.start);
return new vscode.Selection(matchingPosition, matchingPosition);
}
}
/*---------------------------------------------------------------------------------------------
* 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 { isStyleSheet, getNode } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
export function mergeLines() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
if (isStyleSheet(editor.document.languageId)) {
return;
}
let rootNode: Node = parse(editor.document.getText());
editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
let [rangeToReplace, textToReplaceWith] = getRangesToReplace(editor.document, selection, rootNode);
editBuilder.replace(rangeToReplace, textToReplaceWith);
});
});
}
function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range, string] {
let startNodeToUpdate: Node;
let endNodeToUpdate: Node;
if (selection.isEmpty) {
startNodeToUpdate = endNodeToUpdate = getNode(rootNode, document.offsetAt(selection.start));
} else {
startNodeToUpdate = getNode(rootNode, document.offsetAt(selection.start), true);
endNodeToUpdate = getNode(rootNode, document.offsetAt(selection.end), true);
}
let rangeToReplace = new vscode.Range(document.positionAt(startNodeToUpdate.start), document.positionAt(endNodeToUpdate.end));
let textToReplaceWith = document.getText(rangeToReplace).replace(/\r\n|\n/g, '').replace(/>\s*</g, '><');
return [rangeToReplace, textToReplaceWith];
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 { getOpenCloseRange } from './util';
export function removeTag() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
let indentInSpaces = '';
for (let i = 0; i < editor.options.tabSize; i++) {
indentInSpaces += ' ';
}
let rangesToRemove = [];
editor.selections.reverse().forEach(selection => {
rangesToRemove = rangesToRemove.concat(getRangeToRemove(editor, selection, indentInSpaces));
});
editor.edit(editBuilder => {
rangesToRemove.forEach(range => {
editBuilder.replace(range, '');
});
});
}
function getRangeToRemove(editor: vscode.TextEditor, selection: vscode.Selection, indentInSpaces: string): vscode.Range[] {
let offset = editor.document.offsetAt(selection.start);
let [openRange, closeRange] = getOpenCloseRange(editor.document, offset);
if (!openRange.contains(selection.start) && !closeRange.contains(selection.start)) {
return [];
}
let ranges = [openRange];
if (closeRange) {
for (let i = openRange.start.line + 1; i <= closeRange.start.line; i++) {
let lineContent = editor.document.lineAt(i).text;
if (lineContent.startsWith('\t')) {
ranges.push(new vscode.Range(i, 0, i, 1));
} else if (lineContent.startsWith(indentInSpaces)) {
ranges.push(new vscode.Range(i, 0, i, indentInSpaces.length));
}
}
ranges.push(closeRange);
}
return ranges;
}
/*---------------------------------------------------------------------------------------------
* 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, isStyleSheet } from './util';
import { nextItemHTML, prevItemHTML } from './selectItemHTML';
import { nextItemStylesheet, prevItemStylesheet } from './selectItemStylesheet';
import parseStylesheet from '@emmetio/css-parser';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
export function fetchSelectItem(direction: string): void {
let editor = vscode.window.activeTextEditor;
if (!validate()) {
return;
}
let nextItem;
let prevItem;
let parseContent;
if (isStyleSheet(editor.document.languageId)) {
nextItem = nextItemStylesheet;
prevItem = prevItemStylesheet;
parseContent = parseStylesheet;
} else {
nextItem = nextItemHTML;
prevItem = prevItemHTML;
parseContent = parse;
}
let rootNode: Node = parseContent(editor.document.getText());
let newSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
let updatedSelection = direction === 'next' ? nextItem(selection, editor, rootNode) : prevItem(selection, editor, rootNode);
newSelections.push(updatedSelection ? updatedSelection : selection);
});
editor.selections = newSelections;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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, getDeepestNode } from './util';
import Node from '@emmetio/node';
export function nextItemHTML(selection: vscode.Selection, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
let offset = editor.document.offsetAt(selection.active);
let currentNode = getNode(rootNode, offset);
// Cursor is in the open tag, look for attributes
if (offset < currentNode.open.end) {
let attrSelection = getNextAttribute(selection, editor.document, currentNode);
if (attrSelection) {
return attrSelection;
}
}
// Get the first child of current node which is right after the cursor
let nextNode = currentNode.firstChild;
while (nextNode && nextNode.start < offset) {
nextNode = nextNode.nextSibling;
}
// Get next sibling of current node or the parent
while (!nextNode && currentNode) {
nextNode = currentNode.nextSibling;
currentNode = currentNode.parent;
}
return getSelectionFromNode(nextNode, editor.document);
}
export function prevItemHTML(selection: vscode.Selection, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
let offset = editor.document.offsetAt(selection.active);
let currentNode = getNode(rootNode, offset);
let prevNode: Node;
// Cursor is in the open tag after the tag name
if (offset > currentNode.open.start + currentNode.name.length + 1 && offset <= currentNode.open.end) {
prevNode = currentNode;
}
// Cursor is inside the tag
if (!prevNode && offset > currentNode.open.end) {
if (!currentNode.firstChild) {
// No children, so current node should be selected
prevNode = currentNode;
} else {
// Select the child that appears just before the cursor
prevNode = currentNode.firstChild;
while (prevNode.nextSibling && prevNode.nextSibling.end < offset) {
prevNode = prevNode.nextSibling;
}
if (prevNode) {
prevNode = getDeepestNode(prevNode);
}
}
}
if (!prevNode && currentNode.previousSibling) {
prevNode = getDeepestNode(currentNode.previousSibling);
}
if (!prevNode && currentNode.parent) {
prevNode = currentNode.parent;
}
let attrSelection = getPrevAttribute(selection, editor.document, prevNode);
return attrSelection ? attrSelection : getSelectionFromNode(prevNode, editor.document);
}
function getSelectionFromNode(node: Node, document: vscode.TextDocument): vscode.Selection {
if (node && node.open) {
let selectionStart = document.positionAt(node.open.start + 1);
let selectionEnd = node.type === 'comment' ? document.positionAt(node.open.end - 1) : selectionStart.translate(0, node.name.length);
return new vscode.Selection(selectionStart, selectionEnd);
}
}
function getNextAttribute(selection: vscode.Selection, document: vscode.TextDocument, node: Node): vscode.Selection {
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
return;
}
let selectionStart = document.offsetAt(selection.anchor);
let selectionEnd = document.offsetAt(selection.active);
for (let i = 0; i < node.attributes.length; i++) {
let attr = node.attributes[i];
if (selectionEnd < attr.start) {
// select full attr
return new vscode.Selection(document.positionAt(attr.start), document.positionAt(attr.end));
}
if ((attr.value.start !== attr.value.end) && ((selectionStart === attr.start && selectionEnd === attr.end) || selectionEnd < attr.end - 1)) {
// select attr value
return new vscode.Selection(document.positionAt(attr.value.start), document.positionAt(attr.value.end));
}
}
}
function getPrevAttribute(selection: vscode.Selection, document: vscode.TextDocument, node: Node): vscode.Selection {
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
return;
}
let selectionStart = document.offsetAt(selection.anchor);
for (let i = node.attributes.length - 1; i >= 0; i--) {
let attr = node.attributes[i];
if (selectionStart > attr.value.start) {
// select attr value
return new vscode.Selection(document.positionAt(attr.value.start), document.positionAt(attr.value.end));
}
if (selectionStart > attr.start) {
// select full attr
return new vscode.Selection(document.positionAt(attr.start), document.positionAt(attr.end));
}
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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, getDeepestNode } from './util';
import Node from '@emmetio/node';
export function nextItemStylesheet(selection: vscode.Selection, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
let startOffset = editor.document.offsetAt(selection.anchor);
let endOffset = editor.document.offsetAt(selection.active);
let currentNode = getNode(rootNode, endOffset, true);
// Full property is selected, so select property value next
if (currentNode.type === 'property' && startOffset === currentNode.start && endOffset === currentNode.end) {
return getSelectionFromNode(currentNode, editor.document, true);
}
// Cursor is in the selector or in a property
if ((currentNode.type === 'rule' && endOffset < currentNode.selectorToken.end)
|| (currentNode.type === 'property' && endOffset < currentNode.valueToken.end)) {
return getSelectionFromNode(currentNode, editor.document);
}
// Get the first child of current node which is right after the cursor
let nextNode = currentNode.firstChild;
while (nextNode && endOffset >= nextNode.end) {
nextNode = nextNode.nextSibling;
}
// Get next sibling of current node or the parent
while (!nextNode && currentNode) {
nextNode = currentNode.nextSibling;
currentNode = currentNode.parent;
}
return getSelectionFromNode(nextNode, editor.document);
}
export function prevItemStylesheet(selection: vscode.Selection, editor: vscode.TextEditor, rootNode: Node): vscode.Selection {
let startOffset = editor.document.offsetAt(selection.anchor);
let currentNode = getNode(rootNode, startOffset);
if (!currentNode) {
currentNode = rootNode;
}
if (currentNode.type === 'property' || !currentNode.firstChild || (currentNode.type === 'rule' && startOffset <= currentNode.firstChild.start)) {
return getSelectionFromNode(currentNode, editor.document);;
}
// Select the child that appears just before the cursor
let prevNode = currentNode.firstChild;
while (prevNode.nextSibling && prevNode.nextSibling.end <= startOffset) {
prevNode = prevNode.nextSibling;
}
prevNode = getDeepestNode(prevNode);
return getSelectionFromNode(prevNode, editor.document, true);
}
function getSelectionFromNode(node: Node, document: vscode.TextDocument, selectPropertyValue: boolean = false): vscode.Selection {
if (!node) {
return;
}
let nodeToSelect = node.type === 'rule' ? node.selectorToken : node;
let selectionStart = (node.type === 'property' && selectPropertyValue) ? node.valueToken.start : nodeToSelect.start;
let selectionEnd = (node.type === 'property' && selectPropertyValue) ? node.valueToken.end : nodeToSelect.end;
return new vscode.Selection(document.positionAt(selectionStart), document.positionAt(selectionEnd));
}
/*---------------------------------------------------------------------------------------------
* 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 { isStyleSheet, getNode } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
export function splitJoinTag() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
if (isStyleSheet(editor.document.languageId)) {
return;
}
let rootNode: Node = parse(editor.document.getText());
editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
let [rangeToReplace, textToReplaceWith] = getRangesToReplace(editor.document, selection, rootNode);
editBuilder.replace(rangeToReplace, textToReplaceWith);
});
});
}
function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range, string] {
let offset = document.offsetAt(selection.start);
let nodeToUpdate: Node = getNode(rootNode, offset);
let rangeToReplace: vscode.Range;
let textToReplaceWith: string;
if (!nodeToUpdate.close) {
// Split Tag
let nodeText = document.getText(new vscode.Range(document.positionAt(nodeToUpdate.start), document.positionAt(nodeToUpdate.end)));
let m = nodeText.match(/(\s*\/)?>$/);
let end = nodeToUpdate.open.end;
let start = m ? end - m[0].length : end;
rangeToReplace = new vscode.Range(document.positionAt(start), document.positionAt(end));
textToReplaceWith = `></${nodeToUpdate.name}>`;
} else {
// Join Tag
rangeToReplace = new vscode.Range(document.positionAt(nodeToUpdate.open.end - 1), document.positionAt(nodeToUpdate.close.end));
textToReplaceWith = '/>';
}
return [rangeToReplace, textToReplaceWith];
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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, isStyleSheet } from './util';
import parse from '@emmetio/html-matcher';
import parseStylesheet from '@emmetio/css-parser';
import Node from '@emmetio/node';
const startCommentStylesheet = '/*';
const endCommentStylesheet = '*/';
const startCommentHTML = '<!--';
const endCommentHTML = '-->';
export function toggleComment() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
let toggleCommentInternal;
let startComment;
let endComment;
let parseContent;
if (isStyleSheet(editor.document.languageId)) {
parseContent = parseStylesheet;
toggleCommentInternal = toggleCommentStylesheet;
startComment = startCommentStylesheet;
endComment = endCommentStylesheet;
} else {
parseContent = parse;
toggleCommentInternal = toggleCommentHTML;
startComment = startCommentHTML;
endComment = endCommentHTML;
}
let rootNode = parseContent(editor.document.getText());
editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
let [rangesToUnComment, positionForCommentStart, positionForCommentEnd] = toggleCommentInternal(editor.document, selection, rootNode);
rangesToUnComment.forEach(rangeToDelete => {
editBuilder.delete(rangeToDelete);
});
if (positionForCommentStart) {
editBuilder.insert(positionForCommentStart, startComment);
}
if (positionForCommentEnd) {
editBuilder.insert(positionForCommentEnd, endComment);
}
});
});
}
function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range[], vscode.Position, vscode.Position] {
let offset = document.offsetAt(selection.start);
let nodeToUpdate = getNode(rootNode, offset);
let rangesToUnComment = getRangesToUnCommentHTML(nodeToUpdate, document);
if (nodeToUpdate.type === 'comment') {
return [rangesToUnComment, null, null];
}
let positionForCommentStart = document.positionAt(nodeToUpdate.start);
let positionForCommentEnd = document.positionAt(nodeToUpdate.end);
return [rangesToUnComment, positionForCommentStart, positionForCommentEnd];
}
function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vscode.Range[] {
let rangesToUnComment = [];
// If current node is commented, then uncomment and return
if (node.type === 'comment') {
rangesToUnComment.push(new vscode.Range(document.positionAt(node.start), document.positionAt(node.start + startCommentHTML.length)));
rangesToUnComment.push(new vscode.Range(document.positionAt(node.end), document.positionAt(node.end - endCommentHTML.length)));
return rangesToUnComment;
}
// All children of current node should be uncommented
node.children.forEach(childNode => {
rangesToUnComment = rangesToUnComment.concat(getRangesToUnCommentHTML(childNode, document));
});
return rangesToUnComment;
}
function toggleCommentStylesheet(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range[], vscode.Position, vscode.Position] {
let selectionStart = document.offsetAt(selection.anchor);
let selectionEnd = document.offsetAt(selection.active);
// If current node is commented, then uncomment and return
let rangesToUnComment = getRangesToUnCommentStylesheet(rootNode, selectionStart, selectionEnd, document, true);
if (rangesToUnComment.length > 0) {
return [rangesToUnComment, null, null];
}
// Uncomment children of current node and then comment the node
let nodeToComment = getNode(rootNode, selectionStart);
rangesToUnComment = getRangesToUnCommentStylesheet(rootNode, nodeToComment.start, nodeToComment.end, document, false);
let positionForCommentStart = document.positionAt(nodeToComment.start);
let positionForCommentEnd = document.positionAt(nodeToComment.end);
return [rangesToUnComment, positionForCommentStart, positionForCommentEnd];
}
function getRangesToUnCommentStylesheet(rootNode: Node, selectionStart: number, selectionEnd: number, document: vscode.TextDocument, selectionInsideComment: boolean): vscode.Range[] {
if (!rootNode.comments || rootNode.comments.length === 0) {
return [];
}
let rangesToUnComment = [];
rootNode.comments.forEach(comment => {
let foundComment = false;
if (selectionInsideComment) {
foundComment = comment.start <= selectionStart && comment.end >= selectionEnd;
} else {
foundComment = selectionStart <= comment.start && selectionEnd >= comment.end;
}
if (foundComment) {
rangesToUnComment.push(new vscode.Range(document.positionAt(comment.start), document.positionAt(comment.start + startCommentStylesheet.length)));
rangesToUnComment.push(new vscode.Range(document.positionAt(comment.end), document.positionAt(comment.end - endCommentStylesheet.length)));
}
});
return rangesToUnComment;
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 } from './util';
import parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
export function updateTag(tagName: string) {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return;
}
let rootNode: Node = parse(editor.document.getText());
let rangesToUpdate = [];
editor.selections.reverse().forEach(selection => {
rangesToUpdate = rangesToUpdate.concat(getRangesToUpdate(editor, selection, rootNode));
});
editor.edit(editBuilder => {
rangesToUpdate.forEach(range => {
editBuilder.replace(range, tagName);
});
});
}
function getRangesToUpdate(editor: vscode.TextEditor, selection: vscode.Selection, rootNode: Node): vscode.Range[] {
let offset = editor.document.offsetAt(selection.start);
let nodeToUpdate = getNode(rootNode, offset);
let openStart = editor.document.positionAt(nodeToUpdate.open.start + 1);
let openEnd = openStart.translate(0, nodeToUpdate.name.length);
let ranges = [new vscode.Range(openStart, openEnd)];
if (nodeToUpdate.close) {
let closeStart = editor.document.positionAt(nodeToUpdate.close.start + 2);
let closeEnd = editor.document.positionAt(nodeToUpdate.close.end - 1);
ranges.push(new vscode.Range(closeStart, closeEnd));
}
return ranges;
}
/*---------------------------------------------------------------------------------------------
* 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 parse from '@emmetio/html-matcher';
import Node from '@emmetio/node';
import * as extract from '@emmetio/extract-abbreviation';
export function validate(allowStylesheet: boolean = true): boolean {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
return false;
}
if (!allowStylesheet && isStyleSheet(editor.document.languageId)) {
return false;
}
return true;
}
export function getSyntax(document: vscode.TextDocument): string {
if (document.languageId === 'jade') {
return 'pug';
}
if (document.languageId === 'javascriptreact' || document.languageId === 'typescriptreact') {
return 'jsx';
}
return document.languageId;
}
export function isStyleSheet(syntax): boolean {
let stylesheetSyntaxes = ['css', 'scss', 'sass', 'less', 'stylus'];
return (stylesheetSyntaxes.indexOf(syntax) > -1);
}
export function getProfile(syntax: string): any {
let config = vscode.workspace.getConfiguration('emmet')['syntaxProfiles'] || {};
let options = config[syntax];
if (!options || typeof options === 'string') {
return {};
}
let newOptions = {};
for (let key in options) {
switch (key) {
case 'tag_case':
newOptions['tagCase'] = (options[key] === 'lower' || options[key] === 'upper') ? options[key] : '';
break;
case 'attr_case':
newOptions['attributeCase'] = (options[key] === 'lower' || options[key] === 'upper') ? options[key] : '';
break;
case 'attr_quotes':
newOptions['attributeQuotes'] = options[key];
break;
case 'tag_nl':
newOptions['format'] = (options[key] === 'true' || options[key] === 'false') ? options[key] : 'true';
break;
case 'indent':
newOptions['attrCase'] = (options[key] === 'true' || options[key] === 'false') ? '\t' : options[key];
break;
case 'inline_break':
newOptions['inlineBreak'] = options[key];
break;
case 'self_closing_tag':
if (options[key] === true) {
newOptions['selfClosingStyle'] = 'xml'; break;
}
if (options[key] === false) {
newOptions['selfClosingStyle'] = 'html'; break;
}
newOptions['selfClosingStyle'] = options[key];
break;
default:
newOptions[key] = options[key];
break;
}
}
return newOptions;
}
export function getOpenCloseRange(document: vscode.TextDocument, offset: number): [vscode.Range, vscode.Range] {
let rootNode: Node = parse(document.getText());
let nodeToUpdate = getNode(rootNode, offset);
let openRange = new vscode.Range(document.positionAt(nodeToUpdate.open.start), document.positionAt(nodeToUpdate.open.end));
let closeRange = null;
if (nodeToUpdate.close) {
closeRange = new vscode.Range(document.positionAt(nodeToUpdate.close.start), document.positionAt(nodeToUpdate.close.end));
}
return [openRange, closeRange];
}
export function getNode(root: Node, offset: number, includeNodeBoundary: boolean = false) {
let currentNode: Node = root.firstChild;
let foundNode: Node = null;
while (currentNode) {
if ((currentNode.start < offset && currentNode.end > offset)
|| (includeNodeBoundary && (currentNode.start <= offset && currentNode.end >= offset))) {
foundNode = currentNode;
// Dig deeper
currentNode = currentNode.firstChild;
} else {
currentNode = currentNode.nextSibling;
}
}
return foundNode;
}
export function getNodeOuterSelection(document: vscode.TextDocument, node: Node): vscode.Selection {
return new vscode.Selection(document.positionAt(node.start), document.positionAt(node.end));
}
export function getNodeInnerSelection(document: vscode.TextDocument, node: Node): vscode.Selection {
return new vscode.Selection(document.positionAt(node.open.end), document.positionAt(node.close.start));
}
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, ''];
}
let rangeToReplace = new vscode.Range(position.line, result.location, position.line, result.location + result.abbreviation.length);
return [rangeToReplace, result.abbreviation];
}
export function getDeepestNode(node: Node): Node {
if (!node || !node.children || node.children.length === 0) {
return node;
}
return getDeepestNode(node.children[node.children.length - 1]);
}
\ No newline at end of file
{
"compilerOptions": {
"target": "es6",
"lib": [
"es2016"
],
"module": "commonjs",
"outDir": "./out"
},
"exclude": [
"node_modules",
".vscode-test"
],
"include": [
"src/**/*"
]
}
\ No newline at end of file
......@@ -59,6 +59,11 @@ configurationRegistry.registerConfiguration({
'type': 'string',
'default': null,
'description': nls.localize('emmetExtensionsPath', 'Path to a folder containing emmet profiles, snippets and preferences')
},
'emmet.useModules': {
'type': 'boolean',
'default': false,
'description': nls.localize('emmetUseModules', 'Use the new emmet modules for emmet features than the single emmet library.')
}
}
});
......@@ -22,7 +22,7 @@ import * as pfs from 'vs/base/node/pfs';
import Severity from 'vs/base/common/severity';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ICommandService } from 'vs/platform/commands/common/commands';
interface IEmmetConfiguration {
emmet: {
......@@ -30,7 +30,8 @@ interface IEmmetConfiguration {
syntaxProfiles: any;
triggerExpansionOnTab: boolean,
excludeLanguages: string[],
extensionsPath: string
extensionsPath: string,
useModules: boolean
};
}
......@@ -240,7 +241,16 @@ export abstract class EmmetEditorAction extends EditorAction {
'editor.emmet.action.updateTag': 'emmet.updateTag',
'editor.emmet.action.matchingPair': 'emmet.matchTag',
'editor.emmet.action.wrapWithAbbreviation': 'emmet.wrapWithAbbreviation',
'editor.emmet.action.expandAbbreviation': 'emmet.expandAbbreviation'
'editor.emmet.action.expandAbbreviation': 'emmet.expandAbbreviation',
'editor.emmet.action.balanceInward': 'emmet.balanceIn',
'editor.emmet.action.balanceOutward': 'emmet.balanceOut',
'editor.emmet.action.previousEditPoint': 'emmet.prevEditPoint',
'editor.emmet.action.nextEditPoint': 'emmet.nextEditPoint',
'editor.emmet.action.mergeLines': 'emmet.mergeLines',
'editor.emmet.action.selectPreviousItem': 'emmet.selectPrevItem',
'editor.emmet.action.selectNextItem': 'emmet.selectNextItem',
'editor.emmet.action.splitJoinTag': 'emmet.splitJoinTag',
'editor.emmet.action.toggleComment': 'emmet.toggleComment'
};
protected emmetActionName: string;
......@@ -280,7 +290,7 @@ export abstract class EmmetEditorAction extends EditorAction {
const commandService = accessor.get(ICommandService);
let mappedCommand = this.actionMap[this.id];
if (mappedCommand && CommandsRegistry.getCommand(mappedCommand)) {
if (mappedCommand && configurationService.getConfiguration<IEmmetConfiguration>().emmet.useModules) {
return commandService.executeCommand<void>(mappedCommand);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册