diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index 7a0b972546d3c..8c52bcc0f3edd 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -35,7 +35,8 @@ const extensions = [ 'gulp', 'grunt', 'jake', - 'merge-conflict' + 'merge-conflict', + 'emmet' ]; extensions.forEach(extension => npmInstall(`extensions/${extension}`)); diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json new file mode 100644 index 0000000000000..177d7be2cecad --- /dev/null +++ b/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" + } +} \ No newline at end of file diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts new file mode 100644 index 0000000000000..c95bfed321156 --- /dev/null +++ b/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); +} \ No newline at end of file diff --git a/extensions/emmet/src/balance.ts b/extensions/emmet/src/balance.ts new file mode 100644 index 0000000000000..02dd4c902d155 --- /dev/null +++ b/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)); + +} + diff --git a/extensions/emmet/src/editPoint.ts b/extensions/emmet/src/editPoint.ts new file mode 100644 index 0000000000000..757dad1b16a7e --- /dev/null +++ b/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); + } +} diff --git a/extensions/emmet/src/emmetCompletionProvider.ts b/extensions/emmet/src/emmetCompletionProvider.ts new file mode 100644 index 0000000000000..9fb7bf46af5bd --- /dev/null +++ b/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(); + +export class EmmetCompletionItemProvider implements vscode.CompletionItemProvider { + + public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable { + + 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; + +} + + + diff --git a/extensions/emmet/src/extension.ts b/extensions/emmet/src/extension.ts new file mode 100644 index 0000000000000..15a1bfa4a0336 --- /dev/null +++ b/extensions/emmet/src/extension.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * 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() { +} diff --git a/extensions/emmet/src/matchTag.ts b/extensions/emmet/src/matchTag.ts new file mode 100644 index 0000000000000..e5efc2d8e32b3 --- /dev/null +++ b/extensions/emmet/src/matchTag.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * 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); + } + +} + + diff --git a/extensions/emmet/src/mergeLines.ts b/extensions/emmet/src/mergeLines.ts new file mode 100644 index 0000000000000..fd63a2d36d44a --- /dev/null +++ b/extensions/emmet/src/mergeLines.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * 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*<'); + + return [rangeToReplace, textToReplaceWith]; +} \ No newline at end of file diff --git a/extensions/emmet/src/removeTag.ts b/extensions/emmet/src/removeTag.ts new file mode 100644 index 0000000000000..600557fd7a118 --- /dev/null +++ b/extensions/emmet/src/removeTag.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * 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; +} + + diff --git a/extensions/emmet/src/selectItem.ts b/extensions/emmet/src/selectItem.ts new file mode 100644 index 0000000000000..b479c0ca74c19 --- /dev/null +++ b/extensions/emmet/src/selectItem.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * 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 diff --git a/extensions/emmet/src/selectItemHTML.ts b/extensions/emmet/src/selectItemHTML.ts new file mode 100644 index 0000000000000..79d0a585e0ae0 --- /dev/null +++ b/extensions/emmet/src/selectItemHTML.ts @@ -0,0 +1,130 @@ +/*--------------------------------------------------------------------------------------------- + * 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 diff --git a/extensions/emmet/src/selectItemStylesheet.ts b/extensions/emmet/src/selectItemStylesheet.ts new file mode 100644 index 0000000000000..d0167a2d8b93d --- /dev/null +++ b/extensions/emmet/src/selectItemStylesheet.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * 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)); + +} + + + diff --git a/extensions/emmet/src/splitJoinTag.ts b/extensions/emmet/src/splitJoinTag.ts new file mode 100644 index 0000000000000..1886ca2472c18 --- /dev/null +++ b/extensions/emmet/src/splitJoinTag.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * 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 = `>`; + } 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 diff --git a/extensions/emmet/src/toggleComment.ts b/extensions/emmet/src/toggleComment.ts new file mode 100644 index 0000000000000..f87bd831209bd --- /dev/null +++ b/extensions/emmet/src/toggleComment.ts @@ -0,0 +1,134 @@ +/*--------------------------------------------------------------------------------------------- + * 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 = ''; + +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 diff --git a/extensions/emmet/src/updateTag.ts b/extensions/emmet/src/updateTag.ts new file mode 100644 index 0000000000000..43efb2cc28597 --- /dev/null +++ b/extensions/emmet/src/updateTag.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * 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; +} + + diff --git a/extensions/emmet/src/util.ts b/extensions/emmet/src/util.ts new file mode 100644 index 0000000000000..3b495069d1e53 --- /dev/null +++ b/extensions/emmet/src/util.ts @@ -0,0 +1,139 @@ +/*--------------------------------------------------------------------------------------------- + * 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 diff --git a/extensions/emmet/tsconfig.json b/extensions/emmet/tsconfig.json new file mode 100644 index 0000000000000..06d04868a7010 --- /dev/null +++ b/extensions/emmet/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "es6", + "lib": [ + "es2016" + ], + "module": "commonjs", + "outDir": "./out" + + }, + "exclude": [ + "node_modules", + ".vscode-test" + ], + "include": [ + "src/**/*" + ] +} \ No newline at end of file diff --git a/src/vs/workbench/parts/emmet/electron-browser/emmet.contribution.ts b/src/vs/workbench/parts/emmet/electron-browser/emmet.contribution.ts index 48186e1eb477e..0a177c237f649 100644 --- a/src/vs/workbench/parts/emmet/electron-browser/emmet.contribution.ts +++ b/src/vs/workbench/parts/emmet/electron-browser/emmet.contribution.ts @@ -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.') } } }); diff --git a/src/vs/workbench/parts/emmet/electron-browser/emmetActions.ts b/src/vs/workbench/parts/emmet/electron-browser/emmetActions.ts index 16c227e880087..c2325f2a22f59 100644 --- a/src/vs/workbench/parts/emmet/electron-browser/emmetActions.ts +++ b/src/vs/workbench/parts/emmet/electron-browser/emmetActions.ts @@ -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().emmet.useModules) { return commandService.executeCommand(mappedCommand); }