diff --git a/.vscode/launch.json b/.vscode/launch.json
index 1e417f51484..58840c5eddf 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,6 +1,7 @@
{
"version": "0.1.0",
"configurations": [
+
{
"type": "node",
"request": "launch",
@@ -63,6 +64,20 @@
"${workspaceRoot}/out/**/*.js"
]
},
+ {
+ "type": "extensionHost",
+ "request": "launch",
+ "name": "VS Code Emmet Tests",
+ "runtimeExecutable": "${execPath}",
+ "args": [
+ "${workspaceRoot}/extensions/emmet/test-fixtures",
+ "--extensionDevelopmentPath=${workspaceRoot}/extensions/emmet",
+ "--extensionTestsPath=${workspaceRoot}/extensions/emmet/out/test"
+ ],
+ "outFiles": [
+ "${workspaceRoot}/out/**/*.js"
+ ]
+ },
{
"type": "extensionHost",
"request": "launch",
diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js
index f717ca4d6bf..c973f97e228 100644
--- a/build/gulpfile.vscode.js
+++ b/build/gulpfile.vscode.js
@@ -43,8 +43,8 @@ const nodeModules = ['electron', 'original-fs']
// Build
const builtInExtensions = [
- { name: 'ms-vscode.node-debug', version: '1.15.13' },
- { name: 'ms-vscode.node-debug2', version: '1.14.5' }
+ { name: 'ms-vscode.node-debug', version: '1.15.18' },
+ { name: 'ms-vscode.node-debug2', version: '1.15.3' }
];
const excludedExtensions = [
@@ -286,6 +286,7 @@ function packageTask(platform, arch, opts) {
.map(function (d) { return ['node_modules/' + d + '/**', '!node_modules/' + d + '/**/{test,tests}/**']; }));
const deps = gulp.src(depsSrc, { base: '.', dot: true })
+ .pipe(filter(['**', '!**/package-lock.json']))
.pipe(util.cleanNodeModule('fsevents', ['binding.gyp', 'fsevents.cc', 'build/**', 'src/**', 'test/**'], ['**/*.node']))
.pipe(util.cleanNodeModule('oniguruma', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['**/*.node', 'src/*.js']))
.pipe(util.cleanNodeModule('windows-mutex', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node']))
diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json
index d7fd6d3d30e..1fd34c23234 100644
--- a/extensions/configuration-editing/package.json
+++ b/extensions/configuration-editing/package.json
@@ -35,6 +35,10 @@
"fileMatch": "vscode://defaultsettings/settings.json",
"url": "vscode://schemas/settings"
},
+ {
+ "fileMatch": "vscode://defaultsettings/resourceSettings.json",
+ "url": "vscode://schemas/settings/resource"
+ },
{
"fileMatch": "vscode://settings/workspaceSettings.json",
"url": "vscode://schemas/settings"
@@ -80,4 +84,4 @@
"devDependencies": {
"@types/node": "^7.0.4"
}
-}
+}
\ No newline at end of file
diff --git a/extensions/cpp/language-configuration.json b/extensions/cpp/language-configuration.json
index 86530b6007d..a8782a61e3c 100644
--- a/extensions/cpp/language-configuration.json
+++ b/extensions/cpp/language-configuration.json
@@ -21,5 +21,9 @@
["(", ")"],
["\"", "\""],
["'", "'"]
- ]
+ ],
+ "indentationRules": {
+ "increaseIndentPattern": "^.*\\{[^}\"\\']*$|^.*\\([^\\)\"\\']*$|^\\s*(public|private|protected):\\s*$|^\\s*@(public|private|protected)\\s*$|^\\s*\\{\\}$",
+ "decreaseIndentPattern": "^\\s*(\\s*/[*].*[*]/\\s*)*\\}|^\\s*(\\s*/[*].*[*]/\\s*)*\\)|^\\s*(public|private|protected):\\s*$|^\\s*@(public|private|protected)\\s*$"
+ }
}
\ No newline at end of file
diff --git a/extensions/emmet/npm-shrinkwrap.json b/extensions/emmet/npm-shrinkwrap.json
index 0ac9d4b7f21..d6419d1d4b1 100644
--- a/extensions/emmet/npm-shrinkwrap.json
+++ b/extensions/emmet/npm-shrinkwrap.json
@@ -48,9 +48,9 @@
"resolved": "https://registry.npmjs.org/@emmetio/html-snippets-resolver/-/html-snippets-resolver-0.1.4.tgz"
},
"@emmetio/html-transform": {
- "version": "0.3.2",
+ "version": "0.3.3",
"from": "@emmetio/html-transform@>=0.3.2 <0.4.0",
- "resolved": "https://registry.npmjs.org/@emmetio/html-transform/-/html-transform-0.3.2.tgz"
+ "resolved": "https://registry.npmjs.org/@emmetio/html-transform/-/html-transform-0.3.3.tgz"
},
"@emmetio/implicit-tag": {
"version": "1.0.0",
@@ -88,9 +88,9 @@
"resolved": "https://registry.npmjs.org/@emmetio/output-renderer/-/output-renderer-0.1.1.tgz"
},
"@emmetio/snippets": {
- "version": "0.2.3",
+ "version": "0.2.4",
"from": "@emmetio/snippets@>=0.2.3 <0.3.0",
- "resolved": "https://registry.npmjs.org/@emmetio/snippets/-/snippets-0.2.3.tgz"
+ "resolved": "https://registry.npmjs.org/@emmetio/snippets/-/snippets-0.2.4.tgz"
},
"@emmetio/snippets-registry": {
"version": "0.3.1",
@@ -123,14 +123,19 @@
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz"
},
"vscode-emmet-helper": {
- "version": "0.0.28",
- "from": "vscode-emmet-helper@0.0.28",
- "resolved": "https://registry.npmjs.org/vscode-emmet-helper/-/vscode-emmet-helper-0.0.28.tgz"
+ "version": "1.0.0",
+ "from": "vscode-emmet-helper@1.0.0",
+ "resolved": "https://registry.npmjs.org/vscode-emmet-helper/-/vscode-emmet-helper-1.0.0.tgz"
},
"vscode-languageserver-types": {
"version": "3.3.0",
"from": "vscode-languageserver-types@>=3.0.3 <4.0.0",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.3.0.tgz"
+ },
+ "vscode-nls": {
+ "version": "2.0.2",
+ "from": "vscode-nls@>=2.0.1 <3.0.0",
+ "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-2.0.2.tgz"
}
}
}
diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json
index 32a82d36714..482ef44988c 100644
--- a/extensions/emmet/package.json
+++ b/extensions/emmet/package.json
@@ -24,7 +24,9 @@
"title": "Emmet",
"properties": {
"emmet.showExpandedAbbreviation": {
- "type": ["string"],
+ "type": [
+ "string"
+ ],
"enum": [
"never",
"always",
@@ -43,7 +45,7 @@
"default": {},
"description": "Applicable only when emmet.useNewEmmet is set to true.\nEnable emmet abbreviations in languages that are not supported by default. Add a mapping here between the language and emmet supported language.\n Eg: {\"vue-html\": \"html\", \"javascript\": \"javascriptreact\"}"
},
- "emmet.variables":{
+ "emmet.variables": {
"type": "object",
"properties": {
"lang": {
@@ -55,18 +57,125 @@
"default": "UTF-8"
}
},
- "default":{},
+ "default": {},
"description": "Applicable only when emmet.useNewEmmet is set to true.\nVariables to be used in emmet snippets"
}
}
- }
+ },
+ "commands": [
+ {
+ "command": "editor.emmet.action.removeTag",
+ "title": "%command.removeTag%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.updateTag",
+ "title": "%command.updateTag%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.matchTag",
+ "title": "%command.matchTag%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.balanceIn",
+ "title": "%command.balanceIn%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.balanceOut",
+ "title": "%command.balanceOut%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.prevEditPoint",
+ "title": "%command.prevEditPoint%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.nextEditPoint",
+ "title": "%command.nextEditPoint%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.mergeLines",
+ "title": "%command.mergeLines%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.selectPrevItem",
+ "title": "%command.selectPrevItem%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.selectNextItem",
+ "title": "%command.selectNextItem%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.splitJoinTag",
+ "title": "%command.splitJoinTag%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.toggleComment",
+ "title": "%command.toggleComment%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.evaluateMathExpression",
+ "title": "%command.evaluateMathExpression%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.updateImageSize",
+ "title": "%command.updateImageSize%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.incrementNumberByOneTenth",
+ "title": "%command.incrementNumberByOneTenth%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.incrementNumberByOne",
+ "title": "%command.incrementNumberByOne%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.incrementNumberByTen",
+ "title": "%command.incrementNumberByTen%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.decrementNumberByOneTenth",
+ "title": "%command.decrementNumberByOneTenth%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.decrementNumberByOne",
+ "title": "%command.decrementNumberByOne%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.decrementNumberByTen",
+ "title": "%command.decrementNumberByTen%",
+ "category": "Emmet"
+ },
+ {
+ "command": "editor.emmet.action.reflectCSSValue",
+ "title": "%command.reflectCSSValue%",
+ "category": "Emmet"
+ }
+ ]
},
"scripts": {
"compile": "gulp compile-extension:emmet"
},
"devDependencies": {
"@types/node": "^7.0.4",
- "vscode": "1.0.1"
+ "vscode": "1.0.1"
},
"dependencies": {
"@emmetio/expand-abbreviation": "^0.5.8",
@@ -74,8 +183,9 @@
"@emmetio/html-matcher": "^0.3.1",
"@emmetio/css-parser": "^0.3.0",
"@emmetio/math-expression": "^0.1.1",
- "vscode-emmet-helper":"0.0.28",
+ "vscode-emmet-helper": "^1.0.0",
"vscode-languageserver-types": "^3.0.3",
- "image-size": "^0.5.2"
+ "image-size": "^0.5.2",
+ "vscode-nls": "2.0.2"
}
}
\ No newline at end of file
diff --git a/extensions/emmet/package.nls.json b/extensions/emmet/package.nls.json
new file mode 100644
index 00000000000..79a18e731d7
--- /dev/null
+++ b/extensions/emmet/package.nls.json
@@ -0,0 +1,23 @@
+{
+ "command.removeTag": "Remove Tag",
+ "command.updateTag": "Update Tag",
+ "command.matchTag": "Go to Matching Pair",
+ "command.balanceIn": "Balance (inward)",
+ "command.balanceOut": "Balance (outward)",
+ "command.prevEditPoint": "Go to Previous Edit Point",
+ "command.nextEditPoint": "Go to Next Edit Point",
+ "command.mergeLines": "Merge Lines",
+ "command.selectPrevItem": "Select Previous Item",
+ "command.selectNextItem": "Select Next Item",
+ "command.splitJoinTag": "Split/Join Tag",
+ "command.toggleComment": "Toggle Comment",
+ "command.evaluateMathExpression": "Evaluate Math Expression",
+ "command.updateImageSize": "Update Image Size",
+ "command.reflectCSSValue": "Reflect CSS Value",
+ "command.incrementNumberByOne": "Increment by 1",
+ "command.decrementNumberByOne": "Decrement by 1",
+ "command.incrementNumberByOneTenth": "Increment by 0.1",
+ "command.decrementNumberByOneTenth": "Decrement by 0.1",
+ "command.incrementNumberByTen": "Increment by 10",
+ "command.decrementNumberByTen": "Decrement by 10"
+}
\ No newline at end of file
diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts
index fc2958ccd4d..2ddfaae1e94 100644
--- a/extensions/emmet/src/abbreviationActions.ts
+++ b/extensions/emmet/src/abbreviationActions.ts
@@ -6,15 +6,15 @@
import * as vscode from 'vscode';
import { expand } from '@emmetio/expand-abbreviation';
import { Node, HtmlNode, Rule } from 'EmmetNode';
-import { getNode, getInnerRange, getMappingForIncludedLanguages, parse, validate } from './util';
-import { getExpandOptions, extractAbbreviation, isStyleSheet, isAbbreviationValid, getEmmetMode } from 'vscode-emmet-helper';
+import { getNode, getInnerRange, getMappingForIncludedLanguages, parseDocument, validate } from './util';
+import { getExpandOptions, extractAbbreviation, extractAbbreviationFromText, isStyleSheet, isAbbreviationValid, getEmmetMode } from 'vscode-emmet-helper';
interface ExpandAbbreviationInput {
syntax: string;
abbreviation: string;
rangeToReplace: vscode.Range;
textToWrap?: string;
- preceedingWhiteSpace?: string;
+ filters?: string[];
}
export function wrapWithAbbreviation(args) {
@@ -24,59 +24,28 @@ export function wrapWithAbbreviation(args) {
}
const editor = vscode.window.activeTextEditor;
- const newLine = editor.document.eol === vscode.EndOfLine.LF ? '\n' : '\r\n';
+ const abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation' });
- vscode.window.showInputBox({ prompt: 'Enter Abbreviation' }).then(abbreviation => {
+ return abbreviationPromise.then(abbreviation => {
if (!abbreviation || !abbreviation.trim() || !isAbbreviationValid(syntax, abbreviation)) { return; }
let expandAbbrList: ExpandAbbreviationInput[] = [];
- let firstTextToReplace: string;
- let allTextToReplaceSame: boolean = true;
editor.selections.forEach(selection => {
let rangeToReplace: vscode.Range = selection.isReversed ? new vscode.Range(selection.active, selection.anchor) : selection;
if (rangeToReplace.isEmpty) {
rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, editor.document.lineAt(rangeToReplace.start.line).text.length);
}
- const firstLine = editor.document.lineAt(rangeToReplace.start).text;
- const firstLineTillSelection = firstLine.substr(0, rangeToReplace.start.character);
- const whitespaceBeforeSelection = /^\s*$/.test(firstLineTillSelection);
- let textToWrap = '';
- let preceedingWhiteSpace = '';
- if (whitespaceBeforeSelection) {
- const matches = firstLine.match(/^(\s*)/);
- if (matches) {
- preceedingWhiteSpace = matches[1];
- }
- if (rangeToReplace.start.character <= preceedingWhiteSpace.length) {
- rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.end.line, rangeToReplace.end.character);
- }
+ const firstLineOfSelection = editor.document.lineAt(rangeToReplace.start).text.substr(rangeToReplace.start.character);
+ const matches = firstLineOfSelection.match(/^(\s*)/);
+ const preceedingWhiteSpace = matches ? matches[1].length : 0;
- textToWrap = newLine;
- for (let i = rangeToReplace.start.line; i <= rangeToReplace.end.line; i++) {
- textToWrap += '\t' + editor.document.lineAt(i).text.substr(preceedingWhiteSpace.length) + newLine;
- }
- } else {
- textToWrap = editor.document.getText(rangeToReplace);
- }
-
- if (!firstTextToReplace) {
- firstTextToReplace = textToWrap;
- } else if (allTextToReplaceSame && firstTextToReplace !== textToWrap) {
- allTextToReplaceSame = false;
- }
-
- expandAbbrList.push({ syntax, abbreviation, rangeToReplace, textToWrap, preceedingWhiteSpace });
+ rangeToReplace = new vscode.Range(rangeToReplace.start.line, rangeToReplace.start.character + preceedingWhiteSpace, rangeToReplace.end.line, rangeToReplace.end.character);
+ expandAbbrList.push({ syntax, abbreviation, rangeToReplace, textToWrap: '\n\t\$TM_SELECTED_TEXT\n' });
});
- if (!allTextToReplaceSame) {
- expandAbbrList.forEach(input => {
- input.textToWrap = '\n\$TM_SELECTED_TEXT\n';
- });
- }
-
- expandAbbreviationInRange(editor, expandAbbrList, true);
+ return expandAbbreviationInRange(editor, expandAbbrList, true);
});
}
@@ -88,7 +57,7 @@ export function expandAbbreviation(args) {
const editor = vscode.window.activeTextEditor;
- let rootNode = parse(editor.document);
+ let rootNode = parseDocument(editor.document);
if (!rootNode) {
return;
}
@@ -97,11 +66,12 @@ export function expandAbbreviation(args) {
let firstAbbreviation: string;
let allAbbreviationsSame: boolean = true;
- let getAbbreviation = (document: vscode.TextDocument, selection: vscode.Selection, position: vscode.Position, isHtml: boolean): [vscode.Range, string] => {
+ let getAbbreviation = (document: vscode.TextDocument, selection: vscode.Selection, position: vscode.Position, isHtml: boolean): [vscode.Range, string, string[]] => {
let rangeToReplace: vscode.Range = selection;
- let abbreviation = document.getText(rangeToReplace);
+ let abbr = document.getText(rangeToReplace);
if (!rangeToReplace.isEmpty) {
- return [rangeToReplace, abbreviation];
+ let { abbreviation, filters } = extractAbbreviationFromText(abbr);
+ return [rangeToReplace, abbreviation, filters];
}
// Expand cases like
explicitly
@@ -111,17 +81,18 @@ export function expandAbbreviation(args) {
const textTillPosition = currentLine.substr(0, position.character);
let matches = textTillPosition.match(/<(\w+)$/);
if (matches) {
- abbreviation = matches[1];
- rangeToReplace = new vscode.Range(position.translate(0, -(abbreviation.length + 1)), position);
- return [rangeToReplace, abbreviation];
+ abbr = matches[1];
+ rangeToReplace = new vscode.Range(position.translate(0, -(abbr.length + 1)), position);
+ return [rangeToReplace, abbr, []];
}
}
- return extractAbbreviation(editor.document, position);
+ let { abbreviationRange, abbreviation, filters } = extractAbbreviation(editor.document, position);
+ return [new vscode.Range(abbreviationRange.start.line, abbreviationRange.start.character, abbreviationRange.end.line, abbreviationRange.end.character), abbreviation, filters];
};
editor.selections.forEach(selection => {
let position = selection.isReversed ? selection.anchor : selection.active;
- let [rangeToReplace, abbreviation] = getAbbreviation(editor.document, selection, position, syntax === 'html');
+ let [rangeToReplace, abbreviation, filters] = getAbbreviation(editor.document, selection, position, syntax === 'html');
if (!isAbbreviationValid(syntax, abbreviation)) {
vscode.window.showErrorMessage('Emmet: Invalid abbreviation');
return;
@@ -138,10 +109,10 @@ export function expandAbbreviation(args) {
allAbbreviationsSame = false;
}
- abbreviationList.push({ syntax, abbreviation, rangeToReplace });
+ abbreviationList.push({ syntax, abbreviation, rangeToReplace, filters });
});
- expandAbbreviationInRange(editor, abbreviationList, allAbbreviationsSame);
+ return expandAbbreviationInRange(editor, abbreviationList, allAbbreviationsSame);
}
@@ -154,7 +125,7 @@ export function expandAbbreviation(args) {
*/
export function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: string, position: vscode.Position): boolean {
if (!currentNode) {
- return true;
+ return !isStyleSheet(syntax);
}
if (isStyleSheet(syntax)) {
@@ -162,6 +133,16 @@ export function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: s
return true;
}
const currentCssNode = currentNode;
+
+ // Workaround for https://github.com/Microsoft/vscode/30188
+ if (currentCssNode.parent
+ && currentCssNode.parent.type === 'rule'
+ && currentCssNode.selectorToken
+ && currentCssNode.selectorToken.start.line !== currentCssNode.selectorToken.end.line) {
+ return true;
+ }
+
+ // Position is valid if it occurs after the `{` that marks beginning of rule contents
return currentCssNode.selectorToken && position.isAfter(currentCssNode.selectorToken.end);
}
@@ -179,88 +160,71 @@ export function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: s
* @param expandAbbrList
* @param insertSameSnippet
*/
-function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrList: ExpandAbbreviationInput[], insertSameSnippet: boolean) {
+function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrList: ExpandAbbreviationInput[], insertSameSnippet: boolean): Thenable {
if (!expandAbbrList || expandAbbrList.length === 0) {
return;
}
- const newLine = editor.document.eol === vscode.EndOfLine.LF ? '\n' : '\r\n';
// Snippet to replace at multiple cursors are not the same
// `editor.insertSnippet` will have to be called for each instance separately
// We will not be able to maintain multiple cursors after snippet insertion
+ let insertPromises = [];
if (!insertSameSnippet) {
expandAbbrList.forEach((expandAbbrInput: ExpandAbbreviationInput) => {
- let expandedText = expandAbbr(expandAbbrInput, newLine);
+ let expandedText = expandAbbr(expandAbbrInput);
if (expandedText) {
- editor.insertSnippet(new vscode.SnippetString(expandedText), expandAbbrInput.rangeToReplace);
+ insertPromises.push(editor.insertSnippet(new vscode.SnippetString(expandedText), expandAbbrInput.rangeToReplace));
}
});
- return;
+ return Promise.all(insertPromises).then(() => Promise.resolve(true));
}
// Snippet to replace at all cursors are the same
// We can pass all ranges to `editor.insertSnippet` in a single call so that
// all cursors are maintained after snippet insertion
const anyExpandAbbrInput = expandAbbrList[0];
- let expandedText = expandAbbr(anyExpandAbbrInput, newLine);
+ let expandedText = expandAbbr(anyExpandAbbrInput);
let allRanges = expandAbbrList.map(value => {
return new vscode.Range(value.rangeToReplace.start.line, value.rangeToReplace.start.character, value.rangeToReplace.end.line, value.rangeToReplace.end.character);
});
if (expandedText) {
- editor.insertSnippet(new vscode.SnippetString(expandedText), allRanges);
+ return editor.insertSnippet(new vscode.SnippetString(expandedText), allRanges);
}
}
/**
* Expands abbreviation as detailed in given input.
- * If there is textToWrap, then given preceedingWhiteSpace is applied
*/
-function expandAbbr(input: ExpandAbbreviationInput, newLine: string): string {
+function expandAbbr(input: ExpandAbbreviationInput): string {
const emmetConfig = vscode.workspace.getConfiguration('emmet');
- const expandOptions = getExpandOptions(emmetConfig['syntaxProfiles'], emmetConfig['variables'], input.syntax, input.textToWrap);
+ const expandOptions = getExpandOptions(input.syntax, emmetConfig['syntaxProfiles'], emmetConfig['variables'], input.filters);
- // Below fixes https://github.com/Microsoft/vscode/issues/29898
- // With this, Emmet formats inline elements as block elements
- // ensuring the wrapped multi line text does not get merged to a single line
- if (input.textToWrap && !input.rangeToReplace.isSingleLine) {
- expandOptions.profile['inlineBreak'] = 1;
+ if (input.textToWrap) {
+ expandOptions['text'] = input.textToWrap;
+
+ // Below fixes https://github.com/Microsoft/vscode/issues/29898
+ // With this, Emmet formats inline elements as block elements
+ // ensuring the wrapped multi line text does not get merged to a single line
+ if (!input.rangeToReplace.isSingleLine) {
+ expandOptions.profile['inlineBreak'] = 1;
+ }
}
- // Expand the abbreviation
- let expandedText;
try {
- expandedText = expand(input.abbreviation, expandOptions);
+ // Expand the abbreviation
+ let expandedText = expand(input.abbreviation, expandOptions);
+
+ // If the expanded text is single line then we dont need the \t we added to $TM_SELECTED_TEXT earlier
+ if (input.textToWrap && expandedText.indexOf('\n') === -1) {
+ expandedText = expandedText.replace(/\s*\$TM_SELECTED_TEXT\s*/, '\$TM_SELECTED_TEXT');
+ }
+ return expandedText;
+
} catch (e) {
vscode.window.showErrorMessage('Failed to expand abbreviation');
}
- if (!expandedText) {
- return;
- }
- // If no text to wrap, then return the expanded text
- if (!input.textToWrap || !input.preceedingWhiteSpace) {
- return expandedText;
- }
-
- // There was text to wrap, and the final expanded text is multi line
- // So add the preceedingWhiteSpace to each line
- if (expandedText.indexOf('\n') > -1) {
- return expandedText.split(newLine).map(line => input.preceedingWhiteSpace + line).join(newLine);
- }
-
- // There was text to wrap and the final expanded text is single line
- // This can happen when the abbreviation was for an inline element
- // Remove the preceeding newLine + tab and the ending newLine, that was added to textToWrap
- // And re-expand the abbreviation
- let regex = newLine === '\n' ? /^\n\t(.*)\n$/ : /^\r\n\t(.*)\r\n$/;
- let matches = input.textToWrap.match(regex);
- if (matches) {
- input.textToWrap = matches[1];
- return expandAbbr(input, newLine);
- }
-
- return input.preceedingWhiteSpace + expandedText;
}
function getSyntaxFromArgs(args: any): string {
@@ -271,9 +235,9 @@ function getSyntaxFromArgs(args: any): string {
}
const mappedModes = getMappingForIncludedLanguages();
- let language: string = (typeof args !== 'object' || !args['language']) ? editor.document.languageId : args['language'];
- let parentMode: string = typeof args === 'object' ? args['parentMode'] : undefined;
- let excludedLanguages = vscode.workspace.getConfiguration('emmet')['exlcudeLanguages'] ? vscode.workspace.getConfiguration('emmet')['exlcudeLanguages'] : [];
+ let language: string = (!args || typeof args !== 'object' || !args['language']) ? editor.document.languageId : args['language'];
+ let parentMode: string = (args && typeof args === 'object') ? args['parentMode'] : undefined;
+ let excludedLanguages = vscode.workspace.getConfiguration('emmet')['excludeLanguages'] ? vscode.workspace.getConfiguration('emmet')['excludeLanguages'] : [];
let syntax = getEmmetMode((mappedModes[language] ? mappedModes[language] : language), excludedLanguages);
if (syntax) {
return syntax;
diff --git a/extensions/emmet/src/balance.ts b/extensions/emmet/src/balance.ts
index 164b4cb0c37..22474be22dd 100644
--- a/extensions/emmet/src/balance.ts
+++ b/extensions/emmet/src/balance.ts
@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import { HtmlNode } from 'EmmetNode';
-import { getNode, parse, validate } from './util';
+import { getNode, parseDocument, validate } from './util';
export function balanceOut() {
balance(true);
@@ -21,7 +21,7 @@ function balance(out: boolean) {
return;
}
- let rootNode = parse(editor.document);
+ let rootNode = parseDocument(editor.document);
if (!rootNode) {
return;
}
@@ -59,15 +59,18 @@ function getRangeToBalanceOut(document: vscode.TextDocument, selection: vscode.S
}
function getRangeToBalanceIn(document: vscode.TextDocument, selection: vscode.Selection, rootNode: HtmlNode): vscode.Selection {
- let nodeToBalance = getNode(rootNode, selection.start);
+ let nodeToBalance = getNode(rootNode, selection.start, true);
if (!nodeToBalance) {
return;
}
+ if (selection.start.isEqual(nodeToBalance.start)
+ && selection.end.isEqual(nodeToBalance.end)
+ && nodeToBalance.close) {
+ return new vscode.Selection(nodeToBalance.open.end, nodeToBalance.close.start);
+ }
+
if (!nodeToBalance.firstChild) {
- if (nodeToBalance.close) {
- return new vscode.Selection(nodeToBalance.open.end, nodeToBalance.close.start);
- }
return;
}
diff --git a/extensions/emmet/src/defaultCompletionProvider.ts b/extensions/emmet/src/defaultCompletionProvider.ts
index 1e35e738cf2..7fd535a3418 100644
--- a/extensions/emmet/src/defaultCompletionProvider.ts
+++ b/extensions/emmet/src/defaultCompletionProvider.ts
@@ -7,7 +7,7 @@ import * as vscode from 'vscode';
import { HtmlNode } from 'EmmetNode';
import { doComplete, isStyleSheet, getEmmetMode } from 'vscode-emmet-helper';
import { isValidLocationForEmmetAbbreviation } from './abbreviationActions';
-import { getNode, getInnerRange, getMappingForIncludedLanguages, parse, getEmmetConfiguration } from './util';
+import { getNode, getInnerRange, getMappingForIncludedLanguages, parseDocument, getEmmetConfiguration } from './util';
export class DefaultCompletionItemProvider implements vscode.CompletionItemProvider {
@@ -16,7 +16,7 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi
const emmetConfig = vscode.workspace.getConfiguration('emmet');
let isSyntaxMapped = mappedLanguages[document.languageId] ? true : false;
- let excludedLanguages = emmetConfig['exlcudeLanguages'] ? emmetConfig['exlcudeLanguages'] : [];
+ let excludedLanguages = emmetConfig['excludeLanguages'] ? emmetConfig['excludeLanguages'] : [];
let syntax = getEmmetMode((isSyntaxMapped ? mappedLanguages[document.languageId] : document.languageId), excludedLanguages);
if (document.languageId === 'html' || isStyleSheet(document.languageId)) {
@@ -61,7 +61,7 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi
if (!syntax) {
return syntax;
}
- let rootNode = parse(document, false);
+ let rootNode = parseDocument(document, false);
if (!rootNode) {
return;
}
diff --git a/extensions/emmet/src/extension.ts b/extensions/emmet/src/extension.ts
index 0357ee05b2f..0c5e74f5052 100644
--- a/extensions/emmet/src/extension.ts
+++ b/extensions/emmet/src/extension.ts
@@ -20,6 +20,8 @@ import { incrementDecrement } from './incrementDecrement';
import { LANGUAGE_MODES, getMappingForIncludedLanguages } from './util';
import { updateExtensionsPath } from 'vscode-emmet-helper';
import { updateImageSize } from './updateImageSize';
+import { reflectCssValue } from './reflectCssValue';
+
import * as path from 'path';
export function activate(context: vscode.ExtensionContext) {
@@ -33,11 +35,11 @@ export function activate(context: vscode.ExtensionContext) {
expandAbbreviation(args);
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.removeTag', () => {
- removeTag();
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.removeTag', () => {
+ return removeTag();
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.updateTag', (inputTag) => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.updateTag', (inputTag) => {
if (inputTag && typeof inputTag === 'string') {
return updateTag(inputTag);
}
@@ -46,78 +48,82 @@ export function activate(context: vscode.ExtensionContext) {
});
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.matchTag', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.matchTag', () => {
matchTag();
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.balanceOut', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.balanceOut', () => {
balanceOut();
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.balanceIn', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.balanceIn', () => {
balanceIn();
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.splitJoinTag', () => {
- splitJoinTag();
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.splitJoinTag', () => {
+ return splitJoinTag();
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.mergeLines', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.mergeLines', () => {
mergeLines();
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.toggleComment', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.toggleComment', () => {
toggleComment();
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.nextEditPoint', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.nextEditPoint', () => {
fetchEditPoint('next');
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.prevEditPoint', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.prevEditPoint', () => {
fetchEditPoint('prev');
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.selectNextItem', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.selectNextItem', () => {
fetchSelectItem('next');
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.selectPrevItem', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.selectPrevItem', () => {
fetchSelectItem('prev');
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.evaluateMathExpression', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.evaluateMathExpression', () => {
evaluateMathExpression();
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.incrementNumberByOneTenth', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.incrementNumberByOneTenth', () => {
return incrementDecrement(.1);
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.incrementNumberByOne', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.incrementNumberByOne', () => {
return incrementDecrement(1);
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.incrementNumberByTen', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.incrementNumberByTen', () => {
return incrementDecrement(10);
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.decrementNumberByOneTenth', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.decrementNumberByOneTenth', () => {
return incrementDecrement(-0.1);
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.decrementNumberByOne', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.decrementNumberByOne', () => {
return incrementDecrement(-1);
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.decrementNumberByTen', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.decrementNumberByTen', () => {
return incrementDecrement(-10);
}));
- context.subscriptions.push(vscode.commands.registerCommand('emmet.updateImageSize', () => {
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.updateImageSize', () => {
return updateImageSize();
}));
+ context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.reflectCSSValue', () => {
+ return reflectCssValue();
+ }));
+
let currentExtensionsPath = undefined;
let resolveUpdateExtensionsPath = () => {
let extensionsPath = vscode.workspace.getConfiguration('emmet')['extensionsPath'];
diff --git a/extensions/emmet/src/matchTag.ts b/extensions/emmet/src/matchTag.ts
index 52222a157c9..7510346b0a9 100644
--- a/extensions/emmet/src/matchTag.ts
+++ b/extensions/emmet/src/matchTag.ts
@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import { HtmlNode } from 'EmmetNode';
-import { getNode, parse, validate } from './util';
+import { getNode, parseDocument, validate } from './util';
export function matchTag() {
let editor = vscode.window.activeTextEditor;
@@ -13,7 +13,7 @@ export function matchTag() {
return;
}
- let rootNode = parse(editor.document);
+ let rootNode = parseDocument(editor.document);
if (!rootNode) {
return;
}
diff --git a/extensions/emmet/src/mergeLines.ts b/extensions/emmet/src/mergeLines.ts
index 927db50d971..430ed14c3b5 100644
--- a/extensions/emmet/src/mergeLines.ts
+++ b/extensions/emmet/src/mergeLines.ts
@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import { Node } from 'EmmetNode';
-import { getNode, parse, validate } from './util';
+import { getNode, parseDocument, validate } from './util';
export function mergeLines() {
let editor = vscode.window.activeTextEditor;
@@ -13,7 +13,7 @@ export function mergeLines() {
return;
}
- let rootNode = parse(editor.document);
+ let rootNode = parseDocument(editor.document);
if (!rootNode) {
return;
}
diff --git a/extensions/emmet/src/reflectCssValue.ts b/extensions/emmet/src/reflectCssValue.ts
new file mode 100644
index 00000000000..ff49da83b7d
--- /dev/null
+++ b/extensions/emmet/src/reflectCssValue.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 { Range, window, TextEditor } from 'vscode';
+import { getCssPropertyFromRule, getCssPropertyFromDocument } from './util';
+import { Property, Rule } from 'EmmetNode';
+
+const vendorPrefixes = ['-webkit-', '-moz-', '-ms-', '-o-', ''];
+
+export function reflectCssValue(): Thenable {
+ let editor = window.activeTextEditor;
+ if (!editor) {
+ window.showInformationMessage('No editor is active.');
+ return;
+ }
+
+ let node = getCssPropertyFromDocument(editor, editor.selection.active);
+ if (!node) {
+ return;
+ }
+
+ return updateCSSNode(editor, node);
+}
+
+function updateCSSNode(editor: TextEditor, property: Property): Thenable {
+ const rule: Rule = property.parent;
+ let currentPrefix = '';
+
+ // Find vendor prefix of given property node
+ for (let i = 0; i < vendorPrefixes.length; i++) {
+ if (property.name.startsWith(vendorPrefixes[i])) {
+ currentPrefix = vendorPrefixes[i];
+ break;
+ }
+ }
+
+ const propertyName = property.name.substr(currentPrefix.length);
+ const propertyValue = property.value;
+
+ return editor.edit(builder => {
+ // Find properties with vendor prefixes, update each
+ vendorPrefixes.forEach(prefix => {
+ if (prefix === currentPrefix) {
+ return;
+ }
+ let vendorProperty = getCssPropertyFromRule(rule, prefix + propertyName);
+ if (vendorProperty) {
+ builder.replace(new Range(vendorProperty.valueToken.start, vendorProperty.valueToken.end), propertyValue);
+ }
+ });
+ });
+}
\ No newline at end of file
diff --git a/extensions/emmet/src/removeTag.ts b/extensions/emmet/src/removeTag.ts
index 4594db42022..db98df8cf62 100644
--- a/extensions/emmet/src/removeTag.ts
+++ b/extensions/emmet/src/removeTag.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
-import { parse, validate, getNode } from './util';
+import { parseDocument, validate, getNode } from './util';
import { HtmlNode } from 'EmmetNode';
export function removeTag() {
@@ -13,7 +13,7 @@ export function removeTag() {
return;
}
- let rootNode = parse(editor.document);
+ let rootNode = parseDocument(editor.document);
if (!rootNode) {
return;
}
@@ -28,7 +28,7 @@ export function removeTag() {
rangesToRemove = rangesToRemove.concat(getRangeToRemove(editor, rootNode, selection, indentInSpaces));
});
- editor.edit(editBuilder => {
+ return editor.edit(editBuilder => {
rangesToRemove.forEach(range => {
editBuilder.replace(range, '');
});
@@ -48,9 +48,6 @@ function getRangeToRemove(editor: vscode.TextEditor, rootNode: HtmlNode, selecti
closeRange = new vscode.Range(nodeToUpdate.close.start, nodeToUpdate.close.end);
}
- 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++) {
diff --git a/extensions/emmet/src/selectItem.ts b/extensions/emmet/src/selectItem.ts
index d15675d59a4..25b918231bd 100644
--- a/extensions/emmet/src/selectItem.ts
+++ b/extensions/emmet/src/selectItem.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
-import { validate, parse } from './util';
+import { validate, parseDocument } from './util';
import { nextItemHTML, prevItemHTML } from './selectItemHTML';
import { nextItemStylesheet, prevItemStylesheet } from './selectItemStylesheet';
import { isStyleSheet } from 'vscode-emmet-helper';
@@ -27,7 +27,7 @@ export function fetchSelectItem(direction: string): void {
prevItem = prevItemHTML;
}
- let rootNode = parse(editor.document);
+ let rootNode = parseDocument(editor.document);
if (!rootNode) {
return;
}
diff --git a/extensions/emmet/src/splitJoinTag.ts b/extensions/emmet/src/splitJoinTag.ts
index cdbed5a572e..619f06af314 100644
--- a/extensions/emmet/src/splitJoinTag.ts
+++ b/extensions/emmet/src/splitJoinTag.ts
@@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import Node from '@emmetio/node';
-import { getNode, parse, validate } from './util';
+import { getNode, parseDocument, validate } from './util';
export function splitJoinTag() {
let editor = vscode.window.activeTextEditor;
@@ -13,12 +13,12 @@ export function splitJoinTag() {
return;
}
- let rootNode = parse(editor.document);
+ let rootNode = parseDocument(editor.document);
if (!rootNode) {
return;
}
- editor.edit(editBuilder => {
+ return editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
let [rangeToReplace, textToReplaceWith] = getRangesToReplace(editor.document, selection, rootNode);
if (rangeToReplace && textToReplaceWith) {
diff --git a/extensions/emmet/src/test/abbreviationAction.test.ts b/extensions/emmet/src/test/abbreviationAction.test.ts
new file mode 100644
index 00000000000..2b7211fac75
--- /dev/null
+++ b/extensions/emmet/src/test/abbreviationAction.test.ts
@@ -0,0 +1,263 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as assert from 'assert';
+import { Selection, workspace } from 'vscode';
+import { withRandomFileEditor, closeAllEditors } from './testUtils';
+import { expandAbbreviation, wrapWithAbbreviation } from '../abbreviationActions';
+
+const cssContents = `
+.boo {
+ margin: 20px 10px;
+ background-image: url('tryme.png');
+ m10
+}
+
+.boo .hoo {
+ margin: 10px;
+ ind
+}
+`;
+
+const bemFilterExample = 'ul.search-form._wide>li.-querystring+li.-btn_large|bem';
+const expectedBemFilterOutput = ``;
+
+const htmlContents = `
+
+
+ - img
+ - hithere
+ ul>li
+ ul>li*2
+ ul>li.item$*2
+ ul>li.item$@44*2
+
+
+ ${bemFilterExample}
+
+`;
+
+const htmlContentsForWrapTests = `
+
+`;
+
+const wrapBlockElementExpected = `
+
+
+
- img
+
+
+
- $hithere
+
+
+`;
+
+const wrapInlineElementExpected = `
+
+`;
+
+const wrapSnippetExpected = `
+
+`;
+
+const wrapMultiLineAbbrExpected = `
+
+`;
+
+suite('Tests for Expand Abbreviations (HTML)', () => {
+ teardown(() => {
+ // Reset config and close all editors
+ return workspace.getConfiguration('emmet').update('excludeLanguages', []).then(closeAllEditors);
+ });
+
+ test('Expand snippets (HTML)', () => {
+ return testHtmlExpandAbbreviation(new Selection(3, 23, 3, 23), 'img', '

');
+ });
+
+ test('Expand abbreviation (HTML)', () => {
+ return testHtmlExpandAbbreviation(new Selection(5, 25, 5, 25), 'ul>li', '
');
+ });
+
+ test('Expand text that is neither an abbreviation nor a snippet to tags (HTML)', () => {
+ return testHtmlExpandAbbreviation(new Selection(4, 20, 4, 27), 'hithere', '
');
+ });
+
+ test('Expand abbreviation with repeaters (HTML)', () => {
+ return testHtmlExpandAbbreviation(new Selection(6, 27, 6, 27), 'ul>li*2', '
');
+ });
+
+ test('Expand abbreviation with numbered repeaters (HTML)', () => {
+ return testHtmlExpandAbbreviation(new Selection(7, 33, 7, 33), 'ul>li.item$*2', '
');
+ });
+
+ test('Expand abbreviation with numbered repeaters with offset (HTML)', () => {
+ return testHtmlExpandAbbreviation(new Selection(8, 36, 8, 36), 'ul>li.item$@44*2', '
');
+ });
+
+ test('Expand tag that is opened, but not closed (HTML)', () => {
+ return testHtmlExpandAbbreviation(new Selection(9, 6, 9, 6), '
');
+ });
+
+ test('No expanding text inside open tag (HTML)', () => {
+ return testHtmlExpandAbbreviation(new Selection(2, 4, 2, 4), '', '', true);
+ });
+
+ test('Expand css when inside style tag (HTML)', () => {
+ return withRandomFileEditor(htmlContents, 'html', (editor, doc) => {
+ editor.selection = new Selection(13, 3, 13, 6);
+ let expandPromise = expandAbbreviation({ language: 'css' });
+ if (!expandPromise) {
+ return Promise.resolve();
+ }
+ return expandPromise.then(() => {
+ assert.equal(editor.document.getText(), htmlContents.replace('m10', 'margin: 10px;'));
+ return Promise.resolve();
+ });
+ });
+ });
+
+ test('No expanding when html is excluded in the settings', () => {
+ return workspace.getConfiguration('emmet').update('excludeLanguages', ['html']).then(() => {
+ return testHtmlExpandAbbreviation(new Selection(9, 6, 9, 6), '', '', true).then(() => {
+ return workspace.getConfiguration('emmet').update('excludeLanguages', []);
+ });
+ });
+ });
+
+ test('Expand using bem filter', () => {
+ return testHtmlExpandAbbreviation(new Selection(16, 55, 16, 55), bemFilterExample, expectedBemFilterOutput);
+ });
+
+});
+
+suite('Tests for Expand Abbreviations (CSS)', () => {
+ teardown(closeAllEditors);
+
+ test('Expand abbreviation (CSS)', () => {
+ return withRandomFileEditor(cssContents, 'css', (editor, doc) => {
+ editor.selection = new Selection(4, 1, 4, 4);
+ return expandAbbreviation(null).then(() => {
+ assert.equal(editor.document.getText(), cssContents.replace('m10', 'margin: 10px;'));
+ return Promise.resolve();
+ });
+ });
+
+ });
+});
+
+suite('Tests for Wrap with Abbreviations', () => {
+ teardown(closeAllEditors);
+
+ const multiCursors = [new Selection(2, 6, 2, 6), new Selection(3, 6, 3, 6)];
+ const multiCursorsWithSelection = [new Selection(2, 2, 2, 28), new Selection(3, 2, 3, 33)];
+ const multiCursorsWithFullLineSelection = [new Selection(2, 0, 2, 28), new Selection(3, 0, 3, 33)];
+
+
+ test('Wrap with block element using multi cursor', () => {
+ return testWrapWithAbbreviation(multiCursors, 'div', wrapBlockElementExpected);
+ });
+
+ test('Wrap with inline element using multi cursor', () => {
+ return testWrapWithAbbreviation(multiCursors, 'span', wrapInlineElementExpected);
+ });
+
+ test('Wrap with snippet using multi cursor', () => {
+ return testWrapWithAbbreviation(multiCursors, 'a', wrapSnippetExpected);
+ });
+
+ test('Wrap with multi line abbreviation using multi cursor', () => {
+ return testWrapWithAbbreviation(multiCursors, 'ul>li', wrapMultiLineAbbrExpected);
+ });
+
+ test('Wrap with block element using multi cursor selection', () => {
+ return testWrapWithAbbreviation(multiCursorsWithSelection, 'div', wrapBlockElementExpected);
+ });
+
+ test('Wrap with inline element using multi cursor selection', () => {
+ return testWrapWithAbbreviation(multiCursorsWithSelection, 'span', wrapInlineElementExpected);
+ });
+
+ test('Wrap with snippet using multi cursor selection', () => {
+ return testWrapWithAbbreviation(multiCursorsWithSelection, 'a', wrapSnippetExpected);
+ });
+
+ test('Wrap with multi line abbreviation using multi cursor selection', () => {
+ return testWrapWithAbbreviation(multiCursorsWithSelection, 'ul>li', wrapMultiLineAbbrExpected);
+ });
+
+ test('Wrap with block element using multi cursor full line selection', () => {
+ return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'div', wrapBlockElementExpected);
+ });
+
+ test('Wrap with inline element using multi cursor full line selection', () => {
+ return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'span', wrapInlineElementExpected);
+ });
+
+ test('Wrap with snippet using multi cursor full line selection', () => {
+ return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'a', wrapSnippetExpected);
+ });
+
+ test('Wrap with multi line abbreviation using multi cursor full line selection', () => {
+ return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'ul>li', wrapMultiLineAbbrExpected);
+ });
+
+});
+
+
+
+function testHtmlExpandAbbreviation(selection: Selection, abbreviation: string, expandedText: string, shouldFail?: boolean): Thenable
{
+ return withRandomFileEditor(htmlContents, 'html', (editor, doc) => {
+ editor.selection = selection;
+ let expandPromise = expandAbbreviation(null);
+ if (!expandPromise) {
+ if (!shouldFail) {
+ assert.equal(1, 2, `Problem with expanding ${abbreviation} to ${expandedText}`);
+ }
+ return Promise.resolve();
+ }
+ return expandPromise.then(() => {
+ assert.equal(editor.document.getText(), htmlContents.replace(abbreviation, expandedText));
+ return Promise.resolve();
+ });
+ });
+}
+
+function testWrapWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string): Thenable {
+ return withRandomFileEditor(htmlContentsForWrapTests, 'html', (editor, doc) => {
+ editor.selections = selections;
+ return wrapWithAbbreviation({ abbreviation: abbreviation }).then(() => {
+ assert.equal(editor.document.getText(), expectedContents);
+ return Promise.resolve();
+ });
+ });
+}
diff --git a/extensions/emmet/src/test/editPointSelectItemBalance.test.ts b/extensions/emmet/src/test/editPointSelectItemBalance.test.ts
new file mode 100644
index 00000000000..427ee166f95
--- /dev/null
+++ b/extensions/emmet/src/test/editPointSelectItemBalance.test.ts
@@ -0,0 +1,229 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as assert from 'assert';
+import { Selection } from 'vscode';
+import { withRandomFileEditor, closeAllEditors } from './testUtils';
+import { fetchEditPoint } from '../editPoint';
+import { fetchSelectItem } from '../selectItem';
+import { balanceOut, balanceIn } from '../balance';
+
+suite('Tests for Next/Previous Select/Edit point and Balance actions', () => {
+ teardown(closeAllEditors);
+
+ const cssContents = `
+.boo {
+ margin: 20px 10px;
+ background-image: url('tryme.png');
+}
+
+.boo .hoo {
+ margin: 10px;
+}
+`;
+
+ const scssContents = `
+.boo {
+ margin: 20px 10px;
+ background-image: url('tryme.png');
+
+ .boo .hoo {
+ margin: 10px;
+ }
+}
+`;
+
+ const htmlContents = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+ test('Emmet Next/Prev Edit point in html file', function (): any {
+ return withRandomFileEditor(htmlContents, '.html', (editor, doc) => {
+ editor.selections = [new Selection(1, 5, 1, 5)];
+
+ let expectedNextEditPoints: [number, number][] = [[4, 16], [6, 8], [10, 2], [20, 0]];
+ expectedNextEditPoints.forEach(([line, col]) => {
+ fetchEditPoint('next');
+ testSelection(editor.selection, col, line);
+ });
+
+ let expectedPrevEditPoints = [[10, 2], [6, 8], [4, 16], [0, 0]];
+ expectedPrevEditPoints.forEach(([line, col]) => {
+ fetchEditPoint('prev');
+ testSelection(editor.selection, col, line);
+ });
+
+ return Promise.resolve();
+ });
+ });
+
+ test('Emmet Select Next/Prev Item in html file', function (): any {
+ return withRandomFileEditor(htmlContents, '.html', (editor, doc) => {
+ editor.selections = [new Selection(2, 2, 2, 2)];
+
+ let expectedNextItemPoints: [number, number, number][] = [
+ [2, 1, 5], // html
+ [2, 6, 15], // lang="en"
+ [2, 12, 14], // en
+ [3, 1, 5], // head
+ [4, 2, 6], // meta
+ [4, 7, 17], // charset=""
+ [5, 2, 6], // meta
+ [5, 7, 22], // name="viewport"
+ [5, 13, 21], // viewport
+ [5, 23, 70], // content="width=device-width, initial-scale=1.0"
+ [5, 32, 69], // width=device-width, initial-scale=1.0
+ [5, 32, 51], // width=device-width,
+ [5, 52, 69], // initial-scale=1.0
+ [6, 2, 7] // title
+ ];
+ expectedNextItemPoints.forEach(([line, colstart, colend]) => {
+ fetchSelectItem('next');
+ testSelection(editor.selection, colstart, line, colend);
+ });
+
+ editor.selections = [new Selection(6, 15, 6, 15)];
+ expectedNextItemPoints.reverse().forEach(([line, colstart, colend]) => {
+ fetchSelectItem('prev');
+ testSelection(editor.selection, colstart, line, colend);
+ });
+
+ return Promise.resolve();
+ });
+ });
+
+ test('Emmet Select Next/Prev Item in css file', function (): any {
+ return withRandomFileEditor(cssContents, '.css', (editor, doc) => {
+ editor.selections = [new Selection(0, 0, 0, 0)];
+
+ let expectedNextItemPoints: [number, number, number][] = [
+ [1, 0, 4], // .boo
+ [2, 1, 19], // margin: 20px 10px;
+ [2, 9, 18], // 20px 10px
+ [2, 9, 13], // 20px
+ [2, 14, 18], // 10px
+ [3, 1, 36], // background-image: url('tryme.png');
+ [3, 19, 35], // url('tryme.png')
+ [6, 0, 9], // .boo .hoo
+ [7, 1, 14], // margin: 10px;
+ [7, 9, 13], // 10px
+ ];
+ expectedNextItemPoints.forEach(([line, colstart, colend]) => {
+ fetchSelectItem('next');
+ testSelection(editor.selection, colstart, line, colend);
+ });
+
+ editor.selections = [new Selection(9, 0, 9, 0)];
+ expectedNextItemPoints.reverse().forEach(([line, colstart, colend]) => {
+ fetchSelectItem('prev');
+ testSelection(editor.selection, colstart, line, colend);
+ });
+
+ return Promise.resolve();
+ });
+ });
+
+ test('Emmet Select Next/Prev Item in scss file with nested rules', function (): any {
+ return withRandomFileEditor(scssContents, '.scss', (editor, doc) => {
+ editor.selections = [new Selection(0, 0, 0, 0)];
+
+ let expectedNextItemPoints: [number, number, number][] = [
+ [1, 0, 4], // .boo
+ [2, 1, 19], // margin: 20px 10px;
+ [2, 9, 18], // 20px 10px
+ [2, 9, 13], // 20px
+ [2, 14, 18], // 10px
+ [3, 1, 36], // background-image: url('tryme.png');
+ [3, 19, 35], // url('tryme.png')
+ [5, 1, 10], // .boo .hoo
+ [6, 2, 15], // margin: 10px;
+ [6, 10, 14], // 10px
+ ];
+ expectedNextItemPoints.forEach(([line, colstart, colend]) => {
+ fetchSelectItem('next');
+ testSelection(editor.selection, colstart, line, colend);
+ });
+
+ editor.selections = [new Selection(8, 0, 8, 0)];
+ expectedNextItemPoints.reverse().forEach(([line, colstart, colend]) => {
+ fetchSelectItem('prev');
+ testSelection(editor.selection, colstart, line, colend);
+ });
+
+ return Promise.resolve();
+ });
+ });
+
+ test('Emmet Balance Out in html file', function (): any {
+ return withRandomFileEditor(htmlContents, 'html', (editor, doc) => {
+
+ editor.selections = [new Selection(14, 6, 14, 10)];
+ let expectedBalanceOutRanges: [number, number, number, number][] = [
+ [14, 3, 14, 32], // - Item 1
+ [13, 23, 16, 2], // inner contents of
+ [13, 2, 16, 7], // outer contents of
+ [12, 21, 17, 1], // inner contents of