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 = ` + +