Merge branch 'master' into electron/1.7.4

This commit is contained in:
Rob Lourens 2017-08-08 07:21:28 -07:00
commit 48ed021dc5
650 changed files with 9206 additions and 4980 deletions

View file

@ -1,5 +1,5 @@
{
newReleaseLabel: 'new release',
newReleases: ['1.14'],
newReleases: ['1.15'],
perform: true
}

15
.vscode/launch.json vendored
View file

@ -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",

View file

@ -1,3 +1,5 @@
===================BEGIN GENERATOR LOG
===================END GENERATOR LOG
microsoft-vscode
THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
@ -13,7 +15,7 @@ This project incorporates components from the projects listed below. The origina
6. atom/language-objective-c (https://github.com/atom/language-objective-c)
7. atom/language-sass version 0.52.0 (https://github.com/atom/language-sass)
8. atom/language-xml (https://github.com/atom/language-xml)
9. Benvie/JavaScriptNext.tmLanguage (https://github.com/Benvie/JavaScriptNext.tmLanguage)
9. Benvie/JavaScriptNext.tmLanguage (https://github.com/Microsoft/vscode-JSON.tmLanguage)
10. chjj-marked version 0.3.6 (https://github.com/npmcomponent/chjj-marked)
11. chriskempson/tomorrow-theme (https://github.com/chriskempson/tomorrow-theme)
12. Colorsublime-Themes version 0.1.0 (https://github.com/Colorsublime/Colorsublime-Themes)
@ -22,45 +24,46 @@ This project incorporates components from the projects listed below. The origina
15. definitelytyped (https://github.com/DefinitelyTyped/DefinitelyTyped)
16. demyte/language-cshtml (https://github.com/demyte/language-cshtml)
17. dotnet/csharp-tmLanguage version 0.1.0 (https://github.com/dotnet/csharp-tmLanguage)
18. freebroccolo/atom-language-swift (https://github.com/freebroccolo/atom-language-swift)
19. HTML 5.1 W3C Working Draft version 08 October 2015 (http://www.w3.org/TR/2015/WD-html51-20151008/)
20. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site)
21. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar)
22. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify)
23. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert)
24. language-docker (https://github.com/moby/moby)
25. language-go version 0.39.0 (https://github.com/atom/language-go)
26. language-less (https://github.com/atom/language-less)
27. language-php (https://github.com/atom/language-php)
28. language-rust version 0.4.9 (https://github.com/zargony/atom-language-rust)
29. MagicStack/MagicPython (https://github.com/MagicStack/MagicPython)
30. Microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/Microsoft/TypeScript-TmLanguage)
31. octicons-code version 3.1.0 (https://octicons.github.com)
32. octicons-font version 3.1.0 (https://octicons.github.com)
33. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui)
34. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage)
35. string_scorer version 0.1.20 (https://github.com/joshaven/string_score)
36. sublimehq/Packages (https://github.com/sublimehq/Packages)
37. SublimeText/PowerShell (https://github.com/SublimeText/PowerShell)
38. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle)
39. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle)
40. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle)
41. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle)
42. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle)
43. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle)
44. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle)
45. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle)
46. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle)
47. textmate/make.tmbundle (https://github.com/textmate/make.tmbundle)
48. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle)
49. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle)
50. textmate/r.tmbundle (https://github.com/textmate/r.tmbundle)
51. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle)
52. textmate/shellscript.tmbundle (https://github.com/textmate/shellscript.tmbundle)
53. textmate/sql.tmbundle (https://github.com/textmate/sql.tmbundle)
54. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle)
55. TypeScript-TmLanguage version 0.1.8 (https://github.com/Microsoft/TypeScript-TmLanguage)
56. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift)
18. expand-abbreviation version 0.5.8 (https://github.com/emmetio/expand-abbreviation)
19. freebroccolo/atom-language-swift (https://github.com/freebroccolo/atom-language-swift)
20. HTML 5.1 W3C Working Draft version 08 October 2015 (http://www.w3.org/TR/2015/WD-html51-20151008/)
21. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site)
22. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar)
23. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify)
24. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert)
25. language-docker (https://github.com/moby/moby)
26. language-go version 0.39.0 (https://github.com/atom/language-go)
27. language-less (https://github.com/atom/language-less)
28. language-php (https://github.com/atom/language-php)
29. language-rust version 0.4.9 (https://github.com/zargony/atom-language-rust)
30. MagicStack/MagicPython (https://github.com/MagicStack/MagicPython)
31. Microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/Microsoft/TypeScript-TmLanguage)
32. octicons-code version 3.1.0 (https://octicons.github.com)
33. octicons-font version 3.1.0 (https://octicons.github.com)
34. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui)
35. shaders-tmLanguage version 0.1.0 (https://github.com/tgjones/shaders-tmLanguage)
36. string_scorer version 0.1.20 (https://github.com/joshaven/string_score)
37. sublimehq/Packages (https://github.com/sublimehq/Packages)
38. SublimeText/PowerShell (https://github.com/SublimeText/PowerShell)
39. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle)
40. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle)
41. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle)
42. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle)
43. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle)
44. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle)
45. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle)
46. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle)
47. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle)
48. textmate/make.tmbundle (https://github.com/textmate/make.tmbundle)
49. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle)
50. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle)
51. textmate/r.tmbundle (https://github.com/textmate/r.tmbundle)
52. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle)
53. textmate/shellscript.tmbundle (https://github.com/textmate/shellscript.tmbundle)
54. textmate/sql.tmbundle (https://github.com/textmate/sql.tmbundle)
55. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle)
56. TypeScript-TmLanguage version 0.1.8 (https://github.com/Microsoft/TypeScript-TmLanguage)
57. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift)
%% atom/language-c NOTICES AND INFORMATION BEGIN HERE
@ -608,6 +611,32 @@ SOFTWARE.
=========================================
END OF dotnet/csharp-tmLanguage NOTICES AND INFORMATION
%% expand-abbreviation NOTICES AND INFORMATION BEGIN HERE
=========================================
MIT License
Copyright (c) 2017 Emmet.io
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
=========================================
END OF expand-abbreviation NOTICES AND INFORMATION
%% freebroccolo/atom-language-swift NOTICES AND INFORMATION BEGIN HERE
=========================================
The MIT License (MIT)

View file

@ -43,8 +43,8 @@ const nodeModules = ['electron', 'original-fs']
// Build
const builtInExtensions = [
{ name: 'ms-vscode.node-debug', version: '1.15.14' },
{ name: 'ms-vscode.node-debug2', version: '1.14.5' }
{ name: 'ms-vscode.node-debug', version: '1.15.19' },
{ name: 'ms-vscode.node-debug2', version: '1.15.5' }
];
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']))

View file

@ -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"
}
}
}

View file

@ -145,4 +145,39 @@ function updateLaunchJsonDecorations(editor: vscode.TextEditor | undefined): voi
});
editor.setDecorations(decoration, ranges);
}
}
vscode.languages.registerDocumentSymbolProvider({ pattern: '**/launch.json', language: 'json' }, {
provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.ProviderResult<vscode.SymbolInformation[]> {
const result: vscode.SymbolInformation[] = [];
let name: string = '';
let lastProperty = '';
let startOffset = 0;
let depthInObjects = 0;
visit(document.getText(), {
onObjectProperty: (property, offset, length) => {
lastProperty = property;
},
onLiteralValue: (value: any, offset: number, length: number) => {
if (lastProperty === 'name') {
name = value;
}
},
onObjectBegin: (offset: number, length: number) => {
depthInObjects++;
if (depthInObjects === 2) {
startOffset = offset;
}
},
onObjectEnd: (offset: number, length: number) => {
if (name && depthInObjects === 2) {
result.push(new vscode.SymbolInformation(name, vscode.SymbolKind.Object, new vscode.Range(document.positionAt(startOffset), document.positionAt(offset))));
}
depthInObjects--;
},
});
return result;
}
});

View file

@ -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*$"
}
}

View file

@ -6,4 +6,32 @@
"repositoryURL": "https://github.com/atom/language-css",
"description": "The file syntaxes/css.tmLanguage.json was derived from the Atom package https://github.com/atom/language-css which was originally converted from the TextMate bundle https://github.com/textmate/css.tmbundle."
}]
},
{
"isLicense": true,
"name": "color-convert",
"repositoryURL": "https+ssh://git@github.com/harthur/color-convert",
"license": "MIT",
"licenseDetail": [
"Copyright (c) 2011-2016 Heather Arthur <fayearthur@gmail.com>",
"",
"Permission is hereby granted, free of charge, to any person obtaining",
"a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including",
"without limitation the rights to use, copy, modify, merge, publish,",
"distribute, sublicense, and/or sell copies of the Software, and to",
"permit persons to whom the Software is furnished to do so, subject to",
"the following conditions:",
"",
"The above copyright notice and this permission notice shall be",
"included in all copies or substantial portions of the Software.",
"",
"THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,",
"EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF",
"MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND",
"NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE",
"LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION",
"OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION",
"WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
]
}
]

View file

@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { window, workspace, DecorationOptions, DecorationRenderOptions, Disposable, Range, TextDocument } from 'vscode';
import * as parse from 'parse-color';
import { window, workspace, DecorationOptions, DecorationRenderOptions, Disposable, Range, TextDocument, DocumentColorProvider, Color, ColorFormat, ColorInfo } from 'vscode';
const MAX_DECORATORS = 500;
@ -107,6 +108,12 @@ export function activateColorDecorations(decoratorProvider: (uri: string) => The
if (document && document.version === documentVersion && contentUri === document.uri.toString()) {
let decorations = ranges.slice(0, MAX_DECORATORS).map(range => {
let color = document.getText(range);
if (color[0] === '#' && (color.length === 5 || color.length === 9)) {
let c = Color.fromHex(color);
if (c) {
color = `rgba(${c.red}, ${c.green}, ${c.blue}, ${c.alpha})`;
}
}
return <DecorationOptions>{
range: range,
renderOptions: {
@ -124,3 +131,53 @@ export function activateColorDecorations(decoratorProvider: (uri: string) => The
return Disposable.from(...disposables);
}
const CSSColorFormats = {
Hex: '#{red:X}{green:X}{blue:X}',
RGB: {
opaque: 'rgb({red}, {green}, {blue})',
transparent: 'rgba({red}, {green}, {blue}, {alpha:2f[0-1]})'
},
HSL: {
opaque: 'hsl({hue:d[0-360]}, {saturation:d[0-100]}%, {luminosity:d[0-100]}%)',
transparent: 'hsla({hue:d[0-360]}, {saturation:d[0-100]}%, {luminosity:d[0-100]}%, {alpha:2f[0-1]})'
}
};
function detectFormat(value: string): ColorFormat {
if (/^rgb/i.test(value)) {
return CSSColorFormats.RGB;
} else if (/^hsl/i.test(value)) {
return CSSColorFormats.HSL;
} else {
return CSSColorFormats.Hex;
}
}
export class ColorProvider implements DocumentColorProvider {
constructor(private decoratorProvider: (uri: string) => Thenable<Range[]>) { }
async provideDocumentColors(document: TextDocument): Promise<ColorInfo[]> {
const ranges = await this.decoratorProvider(document.uri.toString());
const result = [];
for (let range of ranges) {
let color;
const value = document.getText(range);
if (value[0] === '#') {
color = Color.fromHex(value);
} else {
const parsedColor = parse(value);
if (parsedColor && parsedColor.rgba) {
const [red, green, blue, alpha] = parsedColor.rgba;
color = new Color(red, green, blue, alpha);
}
}
if (color) {
const format = detectFormat(value);
result.push(new ColorInfo(range, color, format, [CSSColorFormats.Hex, CSSColorFormats.RGB, CSSColorFormats.HSL]));
}
}
return result;
}
}

View file

@ -8,7 +8,7 @@ import * as path from 'path';
import { languages, window, commands, workspace, ExtensionContext } from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, Range, TextEdit } from 'vscode-languageclient';
import { activateColorDecorations } from './colorDecorators';
import { activateColorDecorations, ColorProvider } from './colorDecorators';
import * as nls from 'vscode-nls';
let localize = nls.loadMessageBundle();
@ -34,7 +34,7 @@ export function activate(context: ExtensionContext) {
// Options to control the language client
let clientOptions: LanguageClientOptions = {
documentSelector: ['css', 'less', 'scss'],
documentSelector: ['css', 'scss', 'less'],
synchronize: {
configurationSection: ['css', 'scss', 'less']
},
@ -57,8 +57,9 @@ export function activate(context: ExtensionContext) {
let isDecoratorEnabled = (languageId: string) => {
return workspace.getConfiguration().get<boolean>(languageId + '.colorDecorators.enable');
};
disposable = activateColorDecorations(colorRequestor, { css: true, scss: true, less: true }, isDecoratorEnabled);
context.subscriptions.push(disposable);
context.subscriptions.push(languages.registerColorProvider(['css', 'scss', 'less'], new ColorProvider(colorRequestor)));
context.subscriptions.push(activateColorDecorations(colorRequestor, { css: true, scss: true, less: true }, isDecoratorEnabled));
});
let indentationRules = {

View file

@ -0,0 +1,21 @@
declare module "parse-color" {
interface Color {
rgb: [number, number, number],
hsl: [number, number, number],
hsv: [number, number, number],
cmyk: [number, number, number, number],
keyword: string,
hex: string,
rgba: [number, number, number, number],
hsla: [number, number, number, number],
hsva: [number, number, number, number],
cmyka: [number, number, number, number, number]
}
function parse(value: string): Color;
module parse { }
export = parse;
}

View file

@ -3,4 +3,5 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../../src/vs/vscode.proposed.d.ts'/>

View file

@ -2,6 +2,16 @@
"name": "css",
"version": "0.1.0",
"dependencies": {
"color-convert": {
"version": "0.5.3",
"from": "color-convert@>=0.5.0 <0.6.0",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz"
},
"parse-color": {
"version": "1.0.0",
"from": "parse-color@latest",
"resolved": "https://registry.npmjs.org/parse-color/-/parse-color-1.0.0.tgz"
},
"vscode-jsonrpc": {
"version": "3.2.0",
"from": "vscode-jsonrpc@>=3.2.0 <4.0.0",

View file

@ -11,6 +11,7 @@
"onLanguage:scss",
"onCommand:_css.applyCodeAction"
],
"enableProposedApi": true,
"main": "./client/out/cssMain",
"scripts": {
"compile": "gulp compile-extension:css-client && gulp compile-extension:css-server",
@ -672,10 +673,11 @@
}
},
"dependencies": {
"parse-color": "^1.0.0",
"vscode-languageclient": "^3.2.0",
"vscode-nls": "^2.0.2"
},
"devDependencies": {
"@types/node": "^6.0.51"
}
}
}

View file

@ -3,9 +3,9 @@
"version": "1.0.0",
"dependencies": {
"vscode-css-languageservice": {
"version": "2.1.1",
"version": "2.1.2",
"from": "vscode-css-languageservice@next",
"resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-2.1.1.tgz"
"resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-2.1.2.tgz"
},
"vscode-jsonrpc": {
"version": "3.2.0",

View file

@ -8,7 +8,7 @@
"node": "*"
},
"dependencies": {
"vscode-css-languageservice": "^2.1.1",
"vscode-css-languageservice": "^2.1.2",
"vscode-languageserver": "^3.2.0"
},
"devDependencies": {

View file

@ -0,0 +1,8 @@
// ATTENTION - THIS DIRECTORY CONTAINS THIRD PARTY OPEN SOURCE MATERIALS:
[{
"name": "expand-abbreviation",
"version": "0.5.8",
"license": "MIT",
"repositoryURL": "https://github.com/emmetio/expand-abbreviation"
}
]

View file

@ -2,101 +2,26 @@
"name": "emmet",
"version": "0.0.1",
"dependencies": {
"@emmetio/abbreviation": {
"version": "0.6.1",
"from": "@emmetio/abbreviation@>=0.6.1 <0.7.0",
"resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-0.6.1.tgz"
},
"@emmetio/css-abbreviation": {
"version": "0.3.1",
"from": "@emmetio/css-abbreviation@>=0.3.1 <0.4.0",
"resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-0.3.1.tgz"
},
"@emmetio/css-parser": {
"version": "0.3.0",
"from": "@emmetio/css-parser@>=0.3.0 <0.4.0",
"resolved": "https://registry.npmjs.org/@emmetio/css-parser/-/css-parser-0.3.0.tgz"
},
"@emmetio/css-snippets-resolver": {
"version": "0.2.5",
"from": "@emmetio/css-snippets-resolver@>=0.2.5 <0.3.0",
"resolved": "https://registry.npmjs.org/@emmetio/css-snippets-resolver/-/css-snippets-resolver-0.2.5.tgz"
},
"@emmetio/expand-abbreviation": {
"version": "0.5.8",
"from": "@emmetio/expand-abbreviation@>=0.5.8 <0.6.0",
"resolved": "https://registry.npmjs.org/@emmetio/expand-abbreviation/-/expand-abbreviation-0.5.8.tgz"
"version": "0.4.0",
"from": "@emmetio/css-parser@>=0.4.0 <0.5.0",
"resolved": "https://registry.npmjs.org/@emmetio/css-parser/-/css-parser-0.4.0.tgz"
},
"@emmetio/extract-abbreviation": {
"version": "0.1.2",
"from": "@emmetio/extract-abbreviation@>=0.1.1 <0.2.0",
"resolved": "https://registry.npmjs.org/@emmetio/extract-abbreviation/-/extract-abbreviation-0.1.2.tgz"
},
"@emmetio/field-parser": {
"version": "0.3.0",
"from": "@emmetio/field-parser@>=0.3.0 <0.4.0",
"resolved": "https://registry.npmjs.org/@emmetio/field-parser/-/field-parser-0.3.0.tgz"
},
"@emmetio/html-matcher": {
"version": "0.3.2",
"from": "@emmetio/html-matcher@>=0.3.1 <0.4.0",
"resolved": "https://registry.npmjs.org/@emmetio/html-matcher/-/html-matcher-0.3.2.tgz"
},
"@emmetio/html-snippets-resolver": {
"version": "0.1.4",
"from": "@emmetio/html-snippets-resolver@>=0.1.4 <0.2.0",
"resolved": "https://registry.npmjs.org/@emmetio/html-snippets-resolver/-/html-snippets-resolver-0.1.4.tgz"
},
"@emmetio/html-transform": {
"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.3.tgz"
},
"@emmetio/implicit-tag": {
"version": "1.0.0",
"from": "@emmetio/implicit-tag@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/@emmetio/implicit-tag/-/implicit-tag-1.0.0.tgz"
},
"@emmetio/lorem": {
"version": "1.0.1",
"from": "@emmetio/lorem@>=1.0.1 <2.0.0",
"resolved": "https://registry.npmjs.org/@emmetio/lorem/-/lorem-1.0.1.tgz"
},
"@emmetio/markup-formatters": {
"version": "0.3.3",
"from": "@emmetio/markup-formatters@>=0.3.3 <0.4.0",
"resolved": "https://registry.npmjs.org/@emmetio/markup-formatters/-/markup-formatters-0.3.3.tgz"
},
"@emmetio/math-expression": {
"version": "0.1.1",
"from": "@emmetio/math-expression@>=0.1.1 <0.2.0",
"resolved": "https://registry.npmjs.org/@emmetio/math-expression/-/math-expression-0.1.1.tgz"
},
"@emmetio/node": {
"version": "0.1.2",
"from": "@emmetio/node@>=0.1.2 <0.2.0",
"resolved": "https://registry.npmjs.org/@emmetio/node/-/node-0.1.2.tgz"
},
"@emmetio/output-profile": {
"version": "0.1.5",
"from": "@emmetio/output-profile@>=0.1.5 <0.2.0",
"resolved": "https://registry.npmjs.org/@emmetio/output-profile/-/output-profile-0.1.5.tgz"
},
"@emmetio/output-renderer": {
"version": "0.1.1",
"from": "@emmetio/output-renderer@>=0.1.0 <0.2.0",
"resolved": "https://registry.npmjs.org/@emmetio/output-renderer/-/output-renderer-0.1.1.tgz"
},
"@emmetio/snippets": {
"version": "0.2.3",
"from": "@emmetio/snippets@>=0.2.3 <0.3.0",
"resolved": "https://registry.npmjs.org/@emmetio/snippets/-/snippets-0.2.3.tgz"
},
"@emmetio/snippets-registry": {
"version": "0.3.1",
"from": "@emmetio/snippets-registry@>=0.3.1 <0.4.0",
"resolved": "https://registry.npmjs.org/@emmetio/snippets-registry/-/snippets-registry-0.3.1.tgz"
},
"@emmetio/stream-reader": {
"version": "2.2.0",
"from": "@emmetio/stream-reader@>=2.2.0 <3.0.0",
@ -107,30 +32,25 @@
"from": "@emmetio/stream-reader-utils@>=0.1.0 <0.2.0",
"resolved": "https://registry.npmjs.org/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz"
},
"@emmetio/stylesheet-formatters": {
"version": "0.1.2",
"from": "@emmetio/stylesheet-formatters@>=0.1.2 <0.2.0",
"resolved": "https://registry.npmjs.org/@emmetio/stylesheet-formatters/-/stylesheet-formatters-0.1.2.tgz"
},
"@emmetio/variable-resolver": {
"version": "0.2.1",
"from": "@emmetio/variable-resolver@>=0.2.1 <0.3.0",
"resolved": "https://registry.npmjs.org/@emmetio/variable-resolver/-/variable-resolver-0.2.1.tgz"
},
"image-size": {
"version": "0.5.5",
"from": "image-size@>=0.5.2 <0.6.0",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz"
},
"vscode-emmet-helper": {
"version": "0.0.29",
"from": "vscode-emmet-helper@0.0.29",
"resolved": "https://registry.npmjs.org/vscode-emmet-helper/-/vscode-emmet-helper-0.0.29.tgz"
"version": "1.0.10",
"from": "vscode-emmet-helper@>=1.0.10 <2.0.0",
"resolved": "https://registry.npmjs.org/vscode-emmet-helper/-/vscode-emmet-helper-1.0.10.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.2",
"resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-2.0.2.tgz"
}
}
}

View file

@ -24,26 +24,28 @@
"title": "Emmet",
"properties": {
"emmet.showExpandedAbbreviation": {
"type": ["string"],
"type": [
"string"
],
"enum": [
"never",
"always",
"inMarkupAndStylesheetFilesOnly"
],
"default": "inMarkupAndStylesheetFilesOnly",
"description": "Applicable only when emmet.useNewEmmet is set to true.\nShows expanded emmet abbreviations as suggestions.\nThe option \"inMarkupAndStylesheetFilesOnly\" applies to html, haml, jade, slim, xml, xsl, css, scss, sass, less and stylus.\nThe option \"always\" applies to all parts of the file regardless of markup/css."
"default": "always",
"description": "%emmetShowExpandedAbbreviation%"
},
"emmet.showAbbreviationSuggestions": {
"type": "boolean",
"default": true,
"description": "Applicable only when emmet.useNewEmmet is set to true.\nShows possible emmet abbreviations as suggestions. Not applicable in stylesheets or when emmet.showExpandedAbbreviation is set to \"never\"."
"description": "%emmetShowAbbreviationSuggestions%"
},
"emmet.includeLanguages": {
"type": "object",
"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":{
"description": "%emmetIncludeLanguages%"
},
"emmet.variables": {
"type": "object",
"properties": {
"lang": {
@ -55,27 +57,159 @@
"default": "UTF-8"
}
},
"default":{},
"description": "Applicable only when emmet.useNewEmmet is set to true.\nVariables to be used in emmet snippets"
"default": {},
"description": "%emmetVariables%"
},
"emmet.syntaxProfiles":{
"type": "object",
"default": {},
"description": "%emmetSyntaxProfiles%"
},
"emmet.excludeLanguages":{
"type": "array",
"default": ["markdown"],
"description": "%emmetExclude%"
},
"emmet.extensionsPath": {
"type": "string",
"default": null,
"description": "%emmetExtensionsPath%"
}
}
}
},
"commands": [
{
"command": "editor.emmet.action.wrapIndividualLinesWithAbbreviation",
"title": "%command.wrapIndividualLinesWithAbbreviation%",
"category": "Emmet"
},
{
"command": "editor.emmet.action.wrapWithAbbreviation",
"title": "%command.wrapWithAbbreviation%",
"category": "Emmet"
},
{
"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",
"@emmetio/extract-abbreviation": "^0.1.1",
"@emmetio/html-matcher": "^0.3.1",
"@emmetio/css-parser": "^0.3.0",
"@emmetio/css-parser": "^0.4.0",
"@emmetio/math-expression": "^0.1.1",
"vscode-emmet-helper":"^0.0.29",
"vscode-emmet-helper": "^1.0.10",
"vscode-languageserver-types": "^3.0.3",
"image-size": "^0.5.2"
"image-size": "^0.5.2",
"vscode-nls": "2.0.2"
}
}

View file

@ -0,0 +1,32 @@
{
"command.wrapWithAbbreviation": "Wrap with Abbreviation",
"command.wrapIndividualLinesWithAbbreviation": "Wrap Individual Lines with Abbreviation",
"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",
"emmetSyntaxProfiles": "Define profile for specified syntax or use your own profile with specific rules.",
"emmetExclude": "An array of languages where emmet abbreviations should not be expanded.",
"emmetExtensionsPath": "Path to a folder containing emmet profiles and snippets.'",
"emmetShowExpandedAbbreviation": "Shows expanded emmet abbreviations as suggestions.\nThe option \"inMarkupAndStylesheetFilesOnly\" applies to html, haml, jade, slim, xml, xsl, css, scss, sass, less and stylus.\nThe option \"always\" applies to all parts of the file regardless of markup/css.",
"emmetShowAbbreviationSuggestions": "Shows possible emmet abbreviations as suggestions. Not applicable in stylesheets or when emmet.showExpandedAbbreviation is set to \"never\".",
"emmetIncludeLanguages": "Enable 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\"}",
"emmetVariables": "Variables to be used in emmet snippets"
}

View file

@ -4,83 +4,85 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { expand } from '@emmetio/expand-abbreviation';
import { Node, HtmlNode, Rule } from 'EmmetNode';
import { getNode, getInnerRange, getMappingForIncludedLanguages, parseDocument, validate } from './util';
import { getExpandOptions, extractAbbreviation, isStyleSheet, isAbbreviationValid, getEmmetMode } from 'vscode-emmet-helper';
import { getExpandOptions, extractAbbreviation, extractAbbreviationFromText, isStyleSheet, isAbbreviationValid, getEmmetMode, expandAbbreviation } from 'vscode-emmet-helper';
const trimRegex = /[\u00a0]*[\d|#|\-|\*|\u2022]+\.?/;
interface ExpandAbbreviationInput {
syntax: string;
abbreviation: string;
rangeToReplace: vscode.Range;
textToWrap?: string;
preceedingWhiteSpace?: string;
textToWrap?: string[];
filters?: string[];
}
export function wrapWithAbbreviation(args) {
const syntax = getSyntaxFromArgs(args);
if (!syntax || !validate()) {
if (!validate(false)) {
return;
}
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' });
const syntax = getSyntaxFromArgs({ language: editor.document.languageId });
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);
});
}
export function expandAbbreviation(args) {
export function wrapIndividualLinesWithAbbreviation(args) {
if (!validate(false)) {
return;
}
const editor = vscode.window.activeTextEditor;
if (editor.selection.isEmpty) {
vscode.window.showInformationMessage('Select more than 1 line and try again.');
return;
}
const abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation' });
const syntax = getSyntaxFromArgs({ language: editor.document.languageId });
const lines = editor.document.getText(editor.selection).split('\n').map(x => x.trim());
return abbreviationPromise.then(inputAbbreviation => {
if (!inputAbbreviation || !inputAbbreviation.trim() || !isAbbreviationValid(syntax, inputAbbreviation)) { return; }
let { abbreviation, filters } = extractAbbreviationFromText(inputAbbreviation);
let input: ExpandAbbreviationInput = {
syntax,
abbreviation,
rangeToReplace: editor.selection,
textToWrap: lines,
filters
};
return expandAbbreviationInRange(editor, [input], true);
});
}
export function expandEmmetAbbreviation(args) {
const syntax = getSyntaxFromArgs(args);
if (!syntax || !validate()) {
return;
@ -97,11 +99,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 <div to <div></div> explicitly
@ -111,17 +114,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 +142,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 +158,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 +166,16 @@ export function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: s
return true;
}
const currentCssNode = <Rule>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 +193,77 @@ 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<boolean> {
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) {
if (input.filters && input.filters.indexOf('t') > -1) {
input.textToWrap = input.textToWrap.map(line => {
return line.replace(trimRegex, '').trim();
});
}
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 = expandAbbreviation(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 && input.textToWrap.length === 1 && 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,13 +274,17 @@ 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;
if (!syntax) {
syntax = getEmmetMode((mappedModes[parentMode] ? mappedModes[parentMode] : parentMode), excludedLanguages);
}
return getEmmetMode((mappedModes[parentMode] ? mappedModes[parentMode] : parentMode), excludedLanguages);
// Final fallback to html
if (!syntax) {
syntax = getEmmetMode('html', excludedLanguages);
}
return syntax;
}

View file

@ -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 = <HtmlNode>getNode(rootNode, selection.start);
let nodeToBalance = <HtmlNode>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;
}

View file

@ -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)) {

View file

@ -25,6 +25,7 @@ export function evaluateMathExpression() {
const result = String(evaluate(stream, true));
editBuilder.replace(new vscode.Range(stream.pos, pos), result);
} catch (err) {
vscode.window.showErrorMessage('Could not evaluate expression');
// Ignore error since most likely its because of non-math expression
console.warn('Math evaluation error', err);
}

View file

@ -5,7 +5,7 @@
import * as vscode from 'vscode';
import { DefaultCompletionItemProvider } from './defaultCompletionProvider';
import { expandAbbreviation, wrapWithAbbreviation } from './abbreviationActions';
import { expandEmmetAbbreviation, wrapWithAbbreviation, wrapIndividualLinesWithAbbreviation } from './abbreviationActions';
import { removeTag } from './removeTag';
import { updateTag } from './updateTag';
import { matchTag } from './matchTag';
@ -27,19 +27,23 @@ import * as path from 'path';
export function activate(context: vscode.ExtensionContext) {
registerCompletionProviders(context, true);
context.subscriptions.push(vscode.commands.registerCommand('emmet.wrapWithAbbreviation', (args) => {
context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.wrapWithAbbreviation', (args) => {
wrapWithAbbreviation(args);
}));
context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.wrapIndividualLinesWithAbbreviation', (args) => {
wrapIndividualLinesWithAbbreviation(args);
}));
context.subscriptions.push(vscode.commands.registerCommand('emmet.expandAbbreviation', (args) => {
expandAbbreviation(args);
expandEmmetAbbreviation(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);
}
@ -48,79 +52,79 @@ 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('emmet.reflectCssValue', () => {
context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.reflectCSSValue', () => {
return reflectCssValue();
}));

View file

@ -18,17 +18,17 @@ export function mergeLines() {
return;
}
editor.edit(editBuilder => {
return editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
let [rangeToReplace, textToReplaceWith] = getRangesToReplace(editor.document, selection, rootNode);
if (rangeToReplace && textToReplaceWith) {
editBuilder.replace(rangeToReplace, textToReplaceWith);
let textEdit = getRangesToReplace(editor.document, selection, rootNode);
if (textEdit) {
editBuilder.replace(textEdit.range, textEdit.newText);
}
});
});
}
function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range, string] {
function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.TextEdit {
let startNodeToUpdate: Node;
let endNodeToUpdate: Node;
@ -39,12 +39,15 @@ function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Sel
endNodeToUpdate = getNode(rootNode, selection.end, true);
}
if (!startNodeToUpdate || !endNodeToUpdate) {
return [null, null];
if (!startNodeToUpdate || !endNodeToUpdate || startNodeToUpdate.start.line === endNodeToUpdate.end.line) {
return;
}
let rangeToReplace = new vscode.Range(startNodeToUpdate.start, endNodeToUpdate.end);
let textToReplaceWith = document.getText(rangeToReplace).replace(/\r\n|\n/g, '').replace(/>\s*</g, '><');
let textToReplaceWith = document.lineAt(startNodeToUpdate.start.line).text.substr(startNodeToUpdate.start.character);
for (let i = startNodeToUpdate.start.line + 1; i <= endNodeToUpdate.end.line; i++) {
textToReplaceWith += document.lineAt(i).text.trim();
}
return [rangeToReplace, textToReplaceWith];
return new vscode.TextEdit(rangeToReplace, textToReplaceWith);
}

View file

@ -9,7 +9,7 @@ import { Property, Rule } from 'EmmetNode';
const vendorPrefixes = ['-webkit-', '-moz-', '-ms-', '-o-', ''];
export function reflectCssValue() {
export function reflectCssValue(): Thenable<boolean> {
let editor = window.activeTextEditor;
if (!editor) {
window.showInformationMessage('No editor is active.');
@ -24,7 +24,7 @@ export function reflectCssValue() {
return updateCSSNode(editor, node);
}
function updateCSSNode(editor: TextEditor, property: Property) {
function updateCSSNode(editor: TextEditor, property: Property): Thenable<boolean> {
const rule: Rule = property.parent;
let currentPrefix = '';

View file

@ -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++) {

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import Node from '@emmetio/node';
import { HtmlNode } from 'EmmetNode';
import { getNode, parseDocument, validate } from './util';
export function splitJoinTag() {
@ -13,28 +13,28 @@ export function splitJoinTag() {
return;
}
let rootNode = parseDocument(editor.document);
let rootNode = <HtmlNode>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) {
editBuilder.replace(rangeToReplace, textToReplaceWith);
let textEdit = getRangesToReplace(editor.document, selection, rootNode);
if (textEdit) {
editBuilder.replace(textEdit.range, textEdit.newText);
}
});
});
}
function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range, string] {
let nodeToUpdate: Node = getNode(rootNode, selection.start);
function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Selection, rootNode: HtmlNode): vscode.TextEdit {
let nodeToUpdate = <HtmlNode>getNode(rootNode, selection.start);
let rangeToReplace: vscode.Range;
let textToReplaceWith: string;
if (!nodeToUpdate) {
return [null, null];
return;
}
if (!nodeToUpdate.close) {
@ -54,5 +54,5 @@ function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Sel
textToReplaceWith = '/>';
}
return [rangeToReplace, textToReplaceWith];
return new vscode.TextEdit(rangeToReplace, textToReplaceWith);
}

View file

@ -0,0 +1,325 @@
/*---------------------------------------------------------------------------------------------
* 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 { expandEmmetAbbreviation, wrapWithAbbreviation, wrapIndividualLinesWithAbbreviation } 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 = `<ul class="search-form search-form_wide">
<li class="search-form__querystring"></li>
<li class="search-form__btn search-form__btn_large"></li>
</ul>`;
const htmlContents = `
<body class="header">
<ul class="nav main">
<li class="item1">img</li>
<li class="item2">hithere</li>
ul>li
ul>li*2
ul>li.item$*2
ul>li.item$@44*2
<div
</ul>
<style>
.boo {
m10
}
</style>
${bemFilterExample}
(ul>li.item$)*2
(ul>li.item$)*2+span
(div>dl>(dt+dd)*2)
</body>
`;
const htmlContentsForWrapTests = `
<ul class="nav main">
<li class="item1">img</li>
<li class="item2">$hithere</li>
</ul>
`;
const wrapBlockElementExpected = `
<ul class="nav main">
<div>
<li class="item1">img</li>
</div>
<div>
<li class="item2">$hithere</li>
</div>
</ul>
`;
const wrapInlineElementExpected = `
<ul class="nav main">
<span><li class="item1">img</li></span>
<span><li class="item2">$hithere</li></span>
</ul>
`;
const wrapSnippetExpected = `
<ul class="nav main">
<a href=""><li class="item1">img</li></a>
<a href=""><li class="item2">$hithere</li></a>
</ul>
`;
const wrapMultiLineAbbrExpected = `
<ul class="nav main">
<ul>
<li>
<li class="item1">img</li>
</li>
</ul>
<ul>
<li>
<li class="item2">$hithere</li>
</li>
</ul>
</ul>
`;
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', '<img src=\"\" alt=\"\">');
});
test('Expand abbreviation (HTML)', () => {
return testHtmlExpandAbbreviation(new Selection(5, 25, 5, 25), 'ul>li', '<ul>\n\t\t\t<li></li>\n\t\t</ul>');
});
test('Expand text that is neither an abbreviation nor a snippet to tags (HTML)', () => {
return testHtmlExpandAbbreviation(new Selection(4, 20, 4, 27), 'hithere', '<hithere></hithere>');
});
test('Expand abbreviation with repeaters (HTML)', () => {
return testHtmlExpandAbbreviation(new Selection(6, 27, 6, 27), 'ul>li*2', '<ul>\n\t\t\t<li></li>\n\t\t\t<li></li>\n\t\t</ul>');
});
test('Expand abbreviation with numbered repeaters (HTML)', () => {
return testHtmlExpandAbbreviation(new Selection(7, 33, 7, 33), 'ul>li.item$*2', '<ul>\n\t\t\t<li class="item1"></li>\n\t\t\t<li class="item2"></li>\n\t\t</ul>');
});
test('Expand abbreviation with numbered repeaters with offset (HTML)', () => {
return testHtmlExpandAbbreviation(new Selection(8, 36, 8, 36), 'ul>li.item$@44*2', '<ul>\n\t\t\t<li class="item44"></li>\n\t\t\t<li class="item45"></li>\n\t\t</ul>');
});
test('Expand abbreviation with numbered repeaters in groups (HTML)', () => {
return testHtmlExpandAbbreviation(new Selection(17, 16, 17, 16), '(ul>li.item$)*2', '<ul>\n\t\t<li class="item1"></li>\n\t</ul>\n\t<ul>\n\t\t<li class="item2"></li>\n\t</ul>');
});
test('Expand abbreviation with numbered repeaters in groups with sibling in the end (HTML)', () => {
return testHtmlExpandAbbreviation(new Selection(18, 21, 18, 21), '(ul>li.item$)*2+span', '<ul>\n\t\t<li class="item1"></li>\n\t</ul>\n\t<ul>\n\t\t<li class="item2"></li>\n\t</ul>\n\t<span></span>');
});
test('Expand abbreviation with nested groups (HTML)', () => {
return testHtmlExpandAbbreviation(new Selection(19, 19, 19, 19), '(div>dl>(dt+dd)*2)', '<div>\n\t\t<dl>\n\t\t\t<dt></dt>\n\t\t\t<dd></dd>\n\t\t\t<dt></dt>\n\t\t\t<dd></dd>\n\t\t</dl>\n\t</div>');
});
test('Expand tag that is opened, but not closed (HTML)', () => {
return testHtmlExpandAbbreviation(new Selection(9, 6, 9, 6), '<div', '<div></div>');
});
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 = expandEmmetAbbreviation({ 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 expandEmmetAbbreviation(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);
});
test('Wrap individual lines with abbreviation', () => {
const contents = `
<ul class="nav main">
<li class="item1">img</li>
<li class="item2">hithere</li>
</ul>
`;
const wrapIndividualLinesExpected = `
<ul class="nav main">
<ul>
<li class="hello1"><li class="item1">img</li></li>
<li class="hello2"><li class="item2">hithere</li></li>
</ul>
</ul>
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [new Selection(2, 2, 3, 33)];
return wrapIndividualLinesWithAbbreviation({ abbreviation: 'ul>li.hello$*' }).then(() => {
assert.equal(editor.document.getText(), wrapIndividualLinesExpected);
return Promise.resolve();
});
});
});
test('Wrap individual lines with abbreviation and trim', () => {
const contents = `
<ul class="nav main">
lorem ipsum
lorem ipsum
</ul>
`;
const wrapIndividualLinesExpected = `
<ul class="nav main">
<ul>
<li class="hello1">lorem ipsum</li>
<li class="hello2">lorem ipsum</li>
</ul>
</ul>
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [new Selection(2, 3, 3, 16)];
return wrapIndividualLinesWithAbbreviation({ abbreviation: 'ul>li.hello$*|t' }).then(() => {
assert.equal(editor.document.getText(), wrapIndividualLinesExpected);
return Promise.resolve();
});
});
});
});
function testHtmlExpandAbbreviation(selection: Selection, abbreviation: string, expandedText: string, shouldFail?: boolean): Thenable<any> {
return withRandomFileEditor(htmlContents, 'html', (editor, doc) => {
editor.selection = selection;
let expandPromise = expandEmmetAbbreviation(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<any> {
return withRandomFileEditor(htmlContentsForWrapTests, 'html', (editor, doc) => {
editor.selections = selections;
return wrapWithAbbreviation({ abbreviation: abbreviation }).then(() => {
assert.equal(editor.document.getText(), expectedContents);
return Promise.resolve();
});
});
}

View file

@ -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 = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
</head>
<body>
<div>
</div>
<div class="header">
<ul class="nav main">
<li class="item1">Item 1</li>
<li class="item2">Item 2</li>
</ul>
</div>
</body>
</html>
`;
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], // <li class="item1">Item 1</li>
[13, 23, 16, 2], // inner contents of <ul class="nav main">
[13, 2, 16, 7], // outer contents of <ul class="nav main">
[12, 21, 17, 1], // inner contents of <div class="header">
[12, 1, 17, 7], // outer contents of <div class="header">
[8, 6, 18, 0], // inner contents of <body>
[8, 0, 18, 7], // outer contents of <body>
[2, 16, 19, 0], // inner contents of <html>
[2, 0, 19, 7], // outer contents of <html>
];
expectedBalanceOutRanges.forEach(([linestart, colstart, lineend, colend]) => {
balanceOut();
testSelection(editor.selection, colstart, linestart, colend, lineend);
});
editor.selections = [new Selection(12, 7, 12, 7)];
let expectedBalanceInRanges: [number, number, number, number][] = [
[13, 2, 16, 7], // outer contents of <ul class="nav main">
[13, 23, 16, 2], // inner contents of <ul class="nav main">
[14, 3, 14, 32], // <li class="item1">Item 1</li>
[14, 21, 14, 27] // Item 1
];
expectedBalanceInRanges.forEach(([linestart, colstart, lineend, colend]) => {
balanceIn();
testSelection(editor.selection, colstart, linestart, colend, lineend);
});
return Promise.resolve();
});
});
});
function testSelection(selection: Selection, startChar: number, startline: number, endChar?: number, endLine?: number) {
assert.equal(selection.anchor.line, startline);
assert.equal(selection.anchor.character, startChar);
if (!endLine && endLine !== 0) {
assert.equal(selection.isSingleLine, true);
} else {
assert.equal(selection.active.line, endLine);
}
if (!endChar && endChar !== 0) {
assert.equal(selection.isEmpty, true);
} else {
assert.equal(selection.active.character, endChar);
}
}

View file

@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Selection, commands } from 'vscode';
import { Selection } from 'vscode';
import { withRandomFileEditor, closeAllEditors } from './testUtils';
import { incrementDecrement } from '../incrementDecrement';
suite('Tests for Increment/Decrement Emmet Commands', () => {
teardown(closeAllEditors);
@ -19,7 +20,7 @@ suite('Tests for Increment/Decrement Emmet Commands', () => {
test('incrementNumberByOne', function (): any {
return withRandomFileEditor(contents, 'txt', (editor, doc) => {
editor.selections = [new Selection(1, 7, 1, 10), new Selection(2, 7, 2, 10)];
return commands.executeCommand('emmet.incrementNumberByOne').then(() => {
return incrementDecrement(1).then(() => {
assert.equal(doc.getText(), contents.replace('123', '124').replace('999', '1000'));
return Promise.resolve();
});
@ -29,7 +30,7 @@ suite('Tests for Increment/Decrement Emmet Commands', () => {
test('incrementNumberByTen', function (): any {
return withRandomFileEditor(contents, 'txt', (editor, doc) => {
editor.selections = [new Selection(1, 7, 1, 10), new Selection(2, 7, 2, 10)];
return commands.executeCommand('emmet.incrementNumberByTen').then(() => {
return incrementDecrement(10).then(() => {
assert.equal(doc.getText(), contents.replace('123', '133').replace('999', '1009'));
return Promise.resolve();
});
@ -39,7 +40,7 @@ suite('Tests for Increment/Decrement Emmet Commands', () => {
test('incrementNumberByOneTenth', function (): any {
return withRandomFileEditor(contents, 'txt', (editor, doc) => {
editor.selections = [new Selection(1, 7, 1, 13), new Selection(2, 7, 2, 12)];
return commands.executeCommand('emmet.incrementNumberByOneTenth').then(() => {
return incrementDecrement(0.1).then(() => {
assert.equal(doc.getText(), contents.replace('123.43', '123.53').replace('999.9', '1000'));
return Promise.resolve();
});
@ -49,7 +50,7 @@ suite('Tests for Increment/Decrement Emmet Commands', () => {
test('decrementNumberByOne', function (): any {
return withRandomFileEditor(contents, 'txt', (editor, doc) => {
editor.selections = [new Selection(1, 7, 1, 10), new Selection(3, 7, 3, 10)];
return commands.executeCommand('emmet.decrementNumberByOne').then(() => {
return incrementDecrement(-1).then(() => {
assert.equal(doc.getText(), contents.replace('123', '122').replace('100', '99'));
return Promise.resolve();
});
@ -59,7 +60,7 @@ suite('Tests for Increment/Decrement Emmet Commands', () => {
test('decrementNumberByTen', function (): any {
return withRandomFileEditor(contents, 'txt', (editor, doc) => {
editor.selections = [new Selection(1, 7, 1, 10), new Selection(3, 7, 3, 10)];
return commands.executeCommand('emmet.decrementNumberByTen').then(() => {
return incrementDecrement(-10).then(() => {
assert.equal(doc.getText(), contents.replace('123', '113').replace('100', '90'));
return Promise.resolve();
});
@ -69,7 +70,7 @@ suite('Tests for Increment/Decrement Emmet Commands', () => {
test('decrementNumberByOneTenth', function (): any {
return withRandomFileEditor(contents, 'txt', (editor, doc) => {
editor.selections = [new Selection(1, 7, 1, 13), new Selection(3, 7, 3, 10)];
return commands.executeCommand('emmet.decrementNumberByOneTenth').then(() => {
return incrementDecrement(-0.1).then(() => {
assert.equal(doc.getText(), contents.replace('123.43', '123.33').replace('100', '99.9'));
return Promise.resolve();
});

View file

@ -6,6 +6,7 @@
import * as assert from 'assert';
import { Selection, commands } from 'vscode';
import { withRandomFileEditor, closeAllEditors } from './testUtils';
import { reflectCssValue } from '../reflectCssValue';
suite('Tests for Emmet: Reflect CSS Value command', () => {
teardown(closeAllEditors);
@ -41,7 +42,7 @@ suite('Tests for Emmet: Reflect CSS Value command', () => {
test('Reflect Css Value in css file', function (): any {
return withRandomFileEditor(cssContents, '.css', (editor, doc) => {
editor.selections = [new Selection(5, 10, 5, 10)];
return commands.executeCommand('emmet.reflectCssValue').then(() => {
return reflectCssValue().then(() => {
assert.equal(doc.getText(), cssContents.replace(/\(50deg\)/g, '(20deg)'));
return Promise.resolve();
});
@ -51,7 +52,7 @@ suite('Tests for Emmet: Reflect CSS Value command', () => {
test('Reflect Css Value in html file', function (): any {
return withRandomFileEditor(htmlContents, '.html', (editor, doc) => {
editor.selections = [new Selection(7, 20, 7, 20)];
return commands.executeCommand('emmet.reflectCssValue').then(() => {
return reflectCssValue().then(() => {
assert.equal(doc.getText(), htmlContents.replace(/\(50deg\)/g, '(20deg)'));
return Promise.resolve();
});

View file

@ -0,0 +1,162 @@
/*---------------------------------------------------------------------------------------------
* 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 { removeTag } from '../removeTag';
import { updateTag } from '../updateTag';
import { matchTag } from '../matchTag';
import { splitJoinTag } from '../splitJoinTag';
import { mergeLines } from '../mergeLines';
suite('Tests for Emmet actions on html tags', () => {
teardown(closeAllEditors);
const contents = `
<div class="hello">
<ul>
<li><span>Hello</span></li>
<li><span>There</span></li>
<div><li><span>Bye</span></li></div>
</ul>
<span/>
</div>
`;
test('update tag with multiple cursors', () => {
const expectedContents = `
<div class="hello">
<ul>
<li><section>Hello</section></li>
<section><span>There</span></section>
<section><li><span>Bye</span></li></section>
</ul>
<span/>
</div>
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [
new Selection(3, 17, 3, 17), // cursor inside tags
new Selection(4, 5, 4, 5), // cursor inside opening tag
new Selection(5, 35, 5, 35), // cursor inside closing tag
];
return updateTag('section').then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
test('remove tag with mutliple cursors', () => {
const expectedContents = `
<div class="hello">
<ul>
<li>Hello</li>
<span>There</span>
<li><span>Bye</span></li>
</ul>
<span/>
</div>
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [
new Selection(3, 17, 3, 17), // cursor inside tags
new Selection(4, 5, 4, 5), // cursor inside opening tag
new Selection(5, 35, 5, 35), // cursor inside closing tag
];
return removeTag().then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
test('split/join tag with mutliple cursors', () => {
const expectedContents = `
<div class="hello">
<ul>
<li><span/></li>
<li><span>There</span></li>
<div><li><span>Bye</span></li></div>
</ul>
<span></span>
</div>
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [
new Selection(3, 17, 3, 17), // join tag
new Selection(7, 5, 7, 5), // split tag
];
return splitJoinTag().then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
test('match tag with mutliple cursors', () => {
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [
new Selection(1, 0, 1, 0), // just before tag starts, i.e before <
new Selection(1, 1, 1, 1), // just before tag name starts
new Selection(1, 2, 1, 2), // inside tag name
new Selection(1, 6, 1, 6), // after tag name but before opening tag ends
new Selection(1, 18, 1, 18), // just before opening tag ends
new Selection(1, 19, 1, 19), // just after opening tag ends
];
matchTag();
editor.selections.forEach(selection => {
assert.equal(selection.active.line, 8);
assert.equal(selection.active.character, 3);
assert.equal(selection.anchor.line, 8);
assert.equal(selection.anchor.character, 3);
});
return Promise.resolve();
});
});
test('merge lines of tag with children when empty selection', () => {
const expectedContents = `
<div class="hello">
<ul><li><span>Hello</span></li><li><span>There</span></li><div><li><span>Bye</span></li></div></ul>
<span/>
</div>
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [
new Selection(2, 3, 2, 3)
];
return mergeLines().then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
test('merge lines is no-op when start and end nodes are on the same line', () => {
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [
new Selection(3, 9, 3, 9), // cursor is inside the <span> in <li><span>Hello</span></li>
new Selection(4, 5, 4, 5), // cursor is inside the <li> in <li><span>Hello</span></li>
new Selection(5, 5, 5, 20) // selection spans multiple nodes in the same line
];
return mergeLines().then(() => {
assert.equal(doc.getText(), contents);
return Promise.resolve();
});
});
});
});

View file

@ -0,0 +1,584 @@
/*---------------------------------------------------------------------------------------------
* 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 { toggleComment } from '../toggleComment';
suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
teardown(closeAllEditors);
const contents = `
<div class="hello">
<ul>
<li><span>Hello</span></li>
<li><span>There</span></li>
<div><li><span>Bye</span></li></div>
</ul>
<ul>
<!--<li>Previously Commented Node</li>-->
<li>Another Node</li>
</ul>
<span/>
<style>
.boo {
margin: 10px;
padding: 20px;
}
.hoo {
margin: 10px;
padding: 20px;
}
</style>
</div>
`;
test('toggle comment with multiple cursors, but no selection (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<li><!--<span>Hello</span>--></li>
<!--<li><span>There</span></li>-->
<!--<div><li><span>Bye</span></li></div>-->
</ul>
<!--<ul>
<li>Previously Commented Node</li>
<li>Another Node</li>
</ul>-->
<span/>
<style>
.boo {
/*margin: 10px;*/
padding: 20px;
}
/*.hoo {
margin: 10px;
padding: 20px;
}*/
</style>
</div>
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [
new Selection(3, 17, 3, 17), // cursor inside the inner span element
new Selection(4, 5, 4, 5), // cursor inside opening tag
new Selection(5, 35, 5, 35), // cursor inside closing tag
new Selection(7, 3, 7, 3), // cursor inside open tag of <ul> one of of whose children is already commented
new Selection(14, 8, 14, 8), // cursor inside the css property inside the style tag
new Selection(18, 3, 18, 3) // cursor inside the css rule inside the style tag
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
test('toggle comment with multiple cursors and whole node selected (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<li><!--<span>Hello</span>--></li>
<!--<li><span>There</span></li>-->
<div><li><span>Bye</span></li></div>
</ul>
<!--<ul>
<li>Previously Commented Node</li>
<li>Another Node</li>
</ul>-->
<span/>
<style>
.boo {
/*margin: 10px;*/
padding: 20px;
}
/*.hoo {
margin: 10px;
padding: 20px;
}*/
</style>
</div>
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [
new Selection(3, 7, 3, 25), // <span>Hello</span><
new Selection(4, 3, 4, 30), // <li><span>There</span></li>
new Selection(7, 2, 10, 7), // The <ul> one of of whose children is already commented
new Selection(14, 4, 14, 17), // css property inside the style tag
new Selection(17, 3, 20, 4) // the css rule inside the style tag
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
test('toggle comment when multiple nodes are completely under single selection (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<!--<li><span>Hello</span></li>
<li><span>There</span></li>-->
<div><li><span>Bye</span></li></div>
</ul>
<ul>
<!--<li>Previously Commented Node</li>-->
<li>Another Node</li>
</ul>
<span/>
<style>
.boo {
/*margin: 10px;
padding: 20px;*/
}
.hoo {
margin: 10px;
padding: 20px;
}
</style>
</div>
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [
new Selection(3, 4, 4, 30),
new Selection(14, 4, 15, 18) // 2 css properties inside the style tag
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
test('toggle comment when multiple nodes are partially under single selection (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<!--<li><span>Hello</span></li>
<li><span>There</span></li>-->
<div><li><span>Bye</span></li></div>
</ul>
<!--<ul>
<li>Previously Commented Node</li>
<li>Another Node</li>
</ul>-->
<span/>
<style>
.boo {
margin: 10px;
padding: 20px;
}
.hoo {
margin: 10px;
padding: 20px;
}
</style>
</div>
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [
new Selection(3, 24, 4, 20),
new Selection(7, 2, 9, 10) // The <ul> one of of whose children is already commented
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
});
suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
teardown(closeAllEditors);
const contents = `
.one {
margin: 10px;
padding: 10px;
}
.two {
height: 42px;
display: none;
}
.three {
width: 42px;
}`;
test('toggle comment with multiple cursors, but no selection (CSS)', () => {
const expectedContents = `
.one {
/*margin: 10px;*/
padding: 10px;
}
/*.two {
height: 42px;
display: none;
}*/
.three {
width: 42px;
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(2, 5, 2, 5), // cursor inside a property
new Selection(5, 4, 5, 4), // cursor inside selector
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return toggleComment().then(() => {
assert.equal(doc.getText(), contents);
return Promise.resolve();
});
});
});
});
test('toggle comment with multiple cursors and whole node selected (CSS)', () => {
const expectedContents = `
.one {
/*margin: 10px;*/
/*padding: 10px;*/
}
/*.two {
height: 42px;
display: none;
}*/
.three {
width: 42px;
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(2, 2, 2, 15), // A property completely selected
new Selection(3, 0, 3, 16), // A property completely selected along with whitespace
new Selection(5, 1, 8, 2), // A rule completely selected
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
//return toggleComment().then(() => {
//assert.equal(doc.getText(), contents);
return Promise.resolve();
//});
});
});
});
test('toggle comment when multiple nodes of same parent are completely under single selection (CSS)', () => {
const expectedContents = `
.one {
/* margin: 10px;
padding: 10px;*/
}
/*.two {
height: 42px;
display: none;
}
.three {
width: 42px;
}*/`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(2, 0, 3, 16), // 2 properties completely under a single selection along with whitespace
new Selection(5, 1, 11, 2), // 2 rules completely under a single selection
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return toggleComment().then(() => {
assert.equal(doc.getText(), contents);
return Promise.resolve();
});
});
});
});
test('toggle comment when start and end of selection is inside properties of separate rules (CSS)', () => {
const expectedContents = `
.one {
margin: 10px;
/*padding: 10px;
}
.two {
height: 42px;*/
display: none;
}
.three {
width: 42px;
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(3, 7, 6, 6)
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return toggleComment().then(() => {
assert.equal(doc.getText(), contents);
return Promise.resolve();
});
});
});
});
test('toggle comment when selection spans properties of separate rules, with start in whitespace and end inside the property (CSS)', () => {
const expectedContents = `
.one {
margin: 10px;
/*padding: 10px;
}
.two {
height: 42px;*/
display: none;
}
.three {
width: 42px;
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(3, 0, 6, 6)
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return toggleComment().then(() => {
assert.equal(doc.getText(), contents);
return Promise.resolve();
});
});
});
});
test('toggle comment when selection spans properties of separate rules, with end in whitespace and start inside the property (CSS)', () => {
const expectedContents = `
.one {
margin: 10px;
/*padding: 10px;
}
.two {
height: 42px;*/
display: none;
}
.three {
width: 42px;
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(3, 7, 7, 0)
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return toggleComment().then(() => {
assert.equal(doc.getText(), contents);
return Promise.resolve();
});
});
});
});
test('toggle comment when selection spans properties of separate rules, with both start and end in whitespace (CSS)', () => {
const expectedContents = `
.one {
margin: 10px;
/*padding: 10px;
}
.two {
height: 42px;*/
display: none;
}
.three {
width: 42px;
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(3, 0, 7, 0)
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return toggleComment().then(() => {
assert.equal(doc.getText(), contents);
return Promise.resolve();
});
});
});
});
test('toggle comment when multiple nodes of same parent are partially under single selection (CSS)', () => {
const expectedContents = `
.one {
/*margin: 10px;
padding: 10px;*/
}
/*.two {
height: 42px;
display: none;
}
.three {
width: 42px;
*/ }`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(2, 7, 3, 10), // 2 properties partially under a single selection
new Selection(5, 2, 11, 0), // 2 rules partially under a single selection
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return toggleComment().then(() => {
assert.equal(doc.getText(), contents);
return Promise.resolve();
});
});
});
});
});
suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
teardown(closeAllEditors);
const contents = `
.one {
height: 42px;
.two {
width: 42px;
}
.three {
padding: 10px;
}
}`;
test('toggle comment with multiple cursors, but no selection (SCSS)', () => {
const expectedContents = `
.one {
/*height: 42px;*/
/*.two {
width: 42px;
}*/
.three {
/*padding: 10px;*/
}
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(2, 5, 2, 5), // cursor inside a property
new Selection(4, 4, 4, 4), // cursor inside a nested rule
new Selection(9, 5, 9, 5) // cursor inside a property inside a nested rule
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
//return toggleComment().then(() => {
// assert.equal(doc.getText(), contents);
return Promise.resolve();
//});
});
});
});
test('toggle comment with multiple cursors and whole node selected (CSS)', () => {
const expectedContents = `
.one {
/*height: 42px;*/
/*.two {
width: 42px;
}*/
.three {
/*padding: 10px;*/
}
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(2, 2, 2, 15), // A property completely selected
new Selection(4, 2, 6, 3), // A rule completely selected
new Selection(9, 3, 9, 17) // A property inside a nested rule completely selected
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return toggleComment().then(() => {
assert.equal(doc.getText(), contents);
return Promise.resolve();
});
});
});
});
test('toggle comment when multiple nodes are completely under single selection (CSS)', () => {
const expectedContents = `
.one {
/*height: 42px;
.two {
width: 42px;
}*/
.three {
padding: 10px;
}
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(2, 2, 6, 3), // A properties and a nested rule completely under a single selection
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return toggleComment().then(() => {
assert.equal(doc.getText(), contents);
return Promise.resolve();
});
});
});
});
test('toggle comment when multiple nodes are partially under single selection (CSS)', () => {
const expectedContents = `
.one {
/*height: 42px;
.two {
width: 42px;
*/ }
.three {
padding: 10px;
}
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(2, 6, 6, 1), // A properties and a nested rule partially under a single selection
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return toggleComment().then(() => {
assert.equal(doc.getText(), contents);
return Promise.resolve();
});
});
});
});
});

View file

@ -0,0 +1,151 @@
/*---------------------------------------------------------------------------------------------
* 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 { updateImageSize } from '../updateImageSize';
import * as path from 'path';
suite('Tests for Emmet actions on html tags', () => {
teardown(closeAllEditors);
const filePath = path.join(__dirname, '../../../../resources/linux/code.png');
test('update image css with multiple cursors in css file', () => {
const cssContents = `
.one {
margin: 10px;
padding: 10px;
background-image: url(https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png);
}
.two {
background-image: url(https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png);
height: 42px;
}
.three {
background-image: url(${filePath});
width: 42px;
}
`;
const expectedContents = `
.one {
margin: 10px;
padding: 10px;
background-image: url(https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png);
width: 32px;
height: 32px;
}
.two {
background-image: url(https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png);
width: 32px;
height: 32px;
}
.three {
background-image: url(${filePath});
height: 1024px;
width: 1024px;
}
`;
return withRandomFileEditor(cssContents, 'css', (editor, doc) => {
editor.selections = [
new Selection(4, 50, 4, 50),
new Selection(7, 50, 7, 50),
new Selection(11, 50, 11, 50)
];
return updateImageSize().then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
test('update image size in css in html file with multiple cursors', () => {
const htmlWithCssContents = `
<html>
<style>
.one {
margin: 10px;
padding: 10px;
background-image: url(https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png);
}
.two {
background-image: url(https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png);
height: 42px;
}
.three {
background-image: url(${filePath});
width: 42px;
}
</style>
</html>
`;
const expectedContents = `
<html>
<style>
.one {
margin: 10px;
padding: 10px;
background-image: url(https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png);
width: 32px;
height: 32px;
}
.two {
background-image: url(https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png);
width: 32px;
height: 32px;
}
.three {
background-image: url(${filePath});
height: 1024px;
width: 1024px;
}
</style>
</html>
`;
return withRandomFileEditor(htmlWithCssContents, 'html', (editor, doc) => {
editor.selections = [
new Selection(6, 50, 6, 50),
new Selection(9, 50, 9, 50),
new Selection(13, 50, 13, 50)
];
return updateImageSize().then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
test('update image size in img tag in html file with multiple cursors', () => {
const htmlwithimgtag = `
<html>
<img id="one" src="${filePath}" />
<img id="two" src="https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png" width="56" />
<img id="three" src="https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png" height="56" />
</html>
`;
const expectedContents = `
<html>
<img id="one" src="${filePath}" width="1024" height="1024" />
<img id="two" src="https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png" width="32" height="32" />
<img id="three" src="https://github.com/Microsoft/vscode/blob/master/resources/linux/code.png" height="32" width="32" />
</html>
`;
return withRandomFileEditor(htmlwithimgtag, 'html', (editor, doc) => {
editor.selections = [
new Selection(2, 50, 2, 50),
new Selection(3, 50, 3, 50),
new Selection(4, 50, 4, 50)
];
return updateImageSize().then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
});

View file

@ -1,46 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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, commands } from 'vscode';
import { withRandomFileEditor, closeAllEditors } from './testUtils';
suite('Tests for Emmet: Update Tag', () => {
teardown(closeAllEditors);
const contents = `
<div>
<ul>
<li><span>Hello</span></li>
<li><span>There</span></li>
<div><li><span>Bye</span></li></div>
</ul>
</div>
`;
/* test('update tag with multiple cursors', () => {
const expectedContents = `
<div>
<ul>
<li><section>Hello</section></li>
<section><span>There</span></section>
<section><li><span>Bye</span></li></section>
</ul>
</div>
`;
return withRandomFileEditor(contents, (editor, doc) => {
editor.selections = [
new Selection(3, 17, 3, 17), // cursor inside tags
new Selection(4, 14, 4, 14), // cursor inside opening tag
new Selection(5, 47, 5, 47), // cursor inside closing tag
];
return commands.executeCommand('emmet.updateTag', 'section').then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
}); */
});

View file

@ -4,16 +4,18 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getNodesInBetween, getNode, parseDocument } from './util';
import { Node, Stylesheet } from 'EmmetNode';
import { getNodesInBetween, getNode, parseDocument, sameNodes } from './util';
import { Node, Stylesheet, Rule, HtmlNode } from 'EmmetNode';
import { isStyleSheet } from 'vscode-emmet-helper';
import parseStylesheet from '@emmetio/css-parser';
import { DocumentStreamReader } from './bufferStream';
const startCommentStylesheet = '/*';
const endCommentStylesheet = '*/';
const startCommentHTML = '<!--';
const endCommentHTML = '-->';
export function toggleComment() {
export function toggleComment(): Thenable<boolean> {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active');
@ -21,17 +23,11 @@ export function toggleComment() {
}
let toggleCommentInternal;
let startComment;
let endComment;
if (isStyleSheet(editor.document.languageId)) {
toggleCommentInternal = toggleCommentStylesheet;
startComment = startCommentStylesheet;
endComment = endCommentStylesheet;
} else {
toggleCommentInternal = toggleCommentHTML;
startComment = startCommentHTML;
endComment = endCommentHTML;
}
let rootNode = parseDocument(editor.document);
@ -39,92 +35,175 @@ export function toggleComment() {
return;
}
editor.edit(editBuilder => {
return editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
let [rangesToUnComment, rangeToComment] = toggleCommentInternal(editor.document, selection, rootNode);
rangesToUnComment.forEach((rangeToUnComment: vscode.Range) => {
editBuilder.delete(new vscode.Range(rangeToUnComment.start, rangeToUnComment.start.translate(0, startComment.length)));
editBuilder.delete(new vscode.Range(rangeToUnComment.end.translate(0, -endComment.length), rangeToUnComment.end));
let edits = toggleCommentInternal(editor.document, selection, rootNode);
edits.forEach(x => {
editBuilder.replace(x.range, x.newText);
});
if (rangeToComment) {
editBuilder.insert(rangeToComment.start, startComment);
editBuilder.insert(rangeToComment.end, endComment);
}
});
});
}
function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): [vscode.Range[], vscode.Range] {
function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.TextEdit[] {
const selectionStart = selection.isReversed ? selection.active : selection.anchor;
const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
let startNode = getNode(rootNode, selectionStart, true);
let endNode = getNode(rootNode, selectionEnd, true);
let startNode = <HtmlNode>getNode(rootNode, selectionStart, true);
let endNode = <HtmlNode>getNode(rootNode, selectionEnd, true);
if (!startNode || !endNode) {
return [[], null];
return [];
}
if (sameNodes(startNode, endNode) && startNode.name === 'style'
&& startNode.open.end.isBefore(selectionStart)
&& startNode.close.start.isAfter(selectionEnd)) {
let buffer = new DocumentStreamReader(document, startNode.open.end, new vscode.Range(startNode.open.end, startNode.close.start));
let cssRootNode = parseStylesheet(buffer);
return toggleCommentStylesheet(document, selection, cssRootNode);
}
let allNodes: Node[] = getNodesInBetween(startNode, endNode);
let rangesToUnComment: vscode.Range[] = [];
let edits: vscode.TextEdit[] = [];
allNodes.forEach(node => {
rangesToUnComment = rangesToUnComment.concat(getRangesToUnCommentHTML(node, document));
edits = edits.concat(getRangesToUnCommentHTML(node, document));
});
if (startNode.type === 'comment') {
return [rangesToUnComment, null];
return edits;
}
let rangeToComment = new vscode.Range(allNodes[0].start, allNodes[allNodes.length - 1].end);
return [rangesToUnComment, rangeToComment];
edits.push(new vscode.TextEdit(new vscode.Range(allNodes[0].start, allNodes[0].start), startCommentHTML));
edits.push(new vscode.TextEdit(new vscode.Range(allNodes[allNodes.length - 1].end, allNodes[allNodes.length - 1].end), endCommentHTML));
return edits;
}
function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vscode.Range[] {
let rangesToUnComment = [];
function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vscode.TextEdit[] {
let unCommentTextEdits: vscode.TextEdit[] = [];
// If current node is commented, then uncomment and return
if (node.type === 'comment') {
rangesToUnComment.push(new vscode.Range(node.start, node.end));
return rangesToUnComment;
unCommentTextEdits.push(new vscode.TextEdit(new vscode.Range(node.start, node.start.translate(0, startCommentHTML.length)), ''));
unCommentTextEdits.push(new vscode.TextEdit(new vscode.Range(node.end.translate(0, -endCommentHTML.length), node.end), ''));
return unCommentTextEdits;
}
// All children of current node should be uncommented
node.children.forEach(childNode => {
rangesToUnComment = rangesToUnComment.concat(getRangesToUnCommentHTML(childNode, document));
unCommentTextEdits = unCommentTextEdits.concat(getRangesToUnCommentHTML(childNode, document));
});
return rangesToUnComment;
return unCommentTextEdits;
}
function toggleCommentStylesheet(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Stylesheet): [vscode.Range[], vscode.Range] {
const selectionStart = selection.isReversed ? selection.active : selection.anchor;
const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
function toggleCommentStylesheet(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Stylesheet): vscode.TextEdit[] {
let selectionStart = selection.isReversed ? selection.active : selection.anchor;
let selectionEnd = selection.isReversed ? selection.anchor : selection.active;
let startNode = getNode(rootNode, selectionStart, true);
let endNode = getNode(rootNode, selectionEnd, true);
let rangesToUnComment: vscode.Range[] = [];
let isFirstNodeCommented = false;
if (!selection.isEmpty || startNode) {
selectionStart = selection.isEmpty ? startNode.start : adjustStartNodeCss(startNode, selectionStart, rootNode);
selectionEnd = selection.isEmpty ? startNode.end : adjustEndNodeCss(endNode, selectionEnd, rootNode);
selection = new vscode.Selection(selectionStart, selectionEnd);
}
// Uncomment the comments that intersect with the selection.
let rangesToUnComment: vscode.Range[] = [];
let edits: vscode.TextEdit[] = [];
rootNode.comments.forEach(comment => {
if (!isFirstNodeCommented) {
isFirstNodeCommented = (selectionStart.isAfterOrEqual(comment.start) && selectionEnd.isBefore(comment.end));
}
if (selection.contains(comment.start)
|| selection.contains(comment.end)
|| (selectionStart.isAfterOrEqual(comment.start) && selectionEnd.isBefore(comment.end))) {
rangesToUnComment.push(new vscode.Range(comment.start, comment.end));
let commentRange = new vscode.Range(comment.start, comment.end);
if (selection.intersection(commentRange)) {
rangesToUnComment.push(commentRange);
edits.push(new vscode.TextEdit(new vscode.Range(comment.start, comment.start.translate(0, startCommentStylesheet.length)), ''));
edits.push(new vscode.TextEdit(new vscode.Range(comment.end.translate(0, -endCommentStylesheet.length), comment.end), ''));
}
});
let rangeToComment = isFirstNodeCommented ? null : new vscode.Range(startNode ? startNode.start : selectionStart, endNode ? endNode.end : selectionEnd);
return [rangesToUnComment, rangeToComment];
if (edits.length > 0) {
return edits;
}
return [
new vscode.TextEdit(new vscode.Range(selection.start, selection.start), startCommentStylesheet),
new vscode.TextEdit(new vscode.Range(selection.end, selection.end), endCommentStylesheet)
];
}
function adjustStartNodeCss(node: Node, pos: vscode.Position, rootNode: Stylesheet): vscode.Position {
for (let i = 0; i < rootNode.comments.length; i++) {
let commentRange = new vscode.Range(rootNode.comments[i].start, rootNode.comments[i].end);
if (commentRange.contains(pos)) {
return pos;
}
}
if (!node) {
return pos;
}
if (node.type === 'property') {
return node.start;
}
const rule = <Rule>node;
if (pos.isBefore(rule.contentStartToken.end) || !rule.firstChild) {
return rule.start;
}
if (pos.isBefore(rule.firstChild.start)) {
return pos;
}
let newStartNode = rule.firstChild;
while (newStartNode.nextSibling && pos.isAfter(newStartNode.end)) {
newStartNode = newStartNode.nextSibling;
}
return newStartNode.start;
}
function adjustEndNodeCss(node: Node, pos: vscode.Position, rootNode: Stylesheet): vscode.Position {
for (let i = 0; i < rootNode.comments.length; i++) {
let commentRange = new vscode.Range(rootNode.comments[i].start, rootNode.comments[i].end);
if (commentRange.contains(pos)) {
return pos;
}
}
if (!node) {
return pos;
}
if (node.type === 'property') {
return node.end;
}
const rule = <Rule>node;
if (pos.isEqual(rule.contentEndToken.end) || !rule.firstChild) {
return rule.end;
}
if (pos.isAfter(rule.children[rule.children.length - 1].end)) {
return pos;
}
let newEndNode = rule.children[rule.children.length - 1];
while (newEndNode.previousSibling && pos.isBefore(newEndNode.start)) {
newEndNode = newEndNode.previousSibling;
}
return newEndNode.end;
}

View file

@ -7,7 +7,7 @@
'use strict';
import { TextEditor, Range, Position, window } from 'vscode';
import { TextEditor, Range, Position, window, TextEdit } from 'vscode';
import * as path from 'path';
import { getImageSize } from './imageSizeHelper';
import { isStyleSheet } from 'vscode-emmet-helper';
@ -27,79 +27,92 @@ export function updateImageSize() {
return;
}
if (!isStyleSheet(editor.document.languageId)) {
return updateImageSizeHTML(editor);
} else {
return updateImageSizeCSSFile(editor);
}
let allUpdatesPromise = editor.selections.reverse().map(selection => {
let position = selection.isReversed ? selection.active : selection.anchor;
if (!isStyleSheet(editor.document.languageId)) {
return updateImageSizeHTML(editor, position);
} else {
return updateImageSizeCSSFile(editor, position);
}
});
return Promise.all(allUpdatesPromise).then((updates) => {
return editor.edit(builder => {
updates.forEach(update => {
update.forEach((textEdit: TextEdit) => {
builder.replace(textEdit.range, textEdit.newText);
});
});
});
});
}
/**
* Updates image size of context tag of HTML model
*/
function updateImageSizeHTML(editor: TextEditor) {
const src = getImageSrcHTML(getImageHTMLNode(editor));
function updateImageSizeHTML(editor: TextEditor, position: Position): Promise<TextEdit[]> {
const src = getImageSrcHTML(getImageHTMLNode(editor, position));
if (!src) {
return updateImageSizeStyleTag(editor);
return updateImageSizeStyleTag(editor, position);
}
locateFile(path.dirname(editor.document.fileName), src)
return locateFile(path.dirname(editor.document.fileName), src)
.then(getImageSize)
.then((size: any) => {
// since this action is asynchronous, we have to ensure that editor wasnt
// changed and user didnt moved caret outside <img> node
const img = getImageHTMLNode(editor);
const img = getImageHTMLNode(editor, position);
if (getImageSrcHTML(img) === src) {
updateHTMLTag(editor, img, size.width, size.height);
return updateHTMLTag(editor, img, size.width, size.height);
}
})
.catch(err => console.warn('Error while updating image size:', err));
.catch(err => { console.warn('Error while updating image size:', err); return []; });
}
function updateImageSizeStyleTag(editor: TextEditor) {
function updateImageSizeStyleTag(editor: TextEditor, position: Position): Promise<TextEdit[]> {
let getPropertyInsiderStyleTag = (editor) => {
const rootNode = parseDocument(editor.document);
const currentNode = <HtmlNode>getNode(rootNode, editor.selection.active);
const currentNode = <HtmlNode>getNode(rootNode, position);
if (currentNode && currentNode.name === 'style'
&& currentNode.open.end.isBefore(editor.selection.active)
&& currentNode.close.start.isAfter(editor.selection.active)) {
let buffer = new DocumentStreamReader(editor.document, currentNode.start, new Range(currentNode.start, currentNode.end));
&& currentNode.open.end.isBefore(position)
&& currentNode.close.start.isAfter(position)) {
let buffer = new DocumentStreamReader(editor.document, currentNode.open.end, new Range(currentNode.open.end, currentNode.close.start));
let rootNode = parseStylesheet(buffer);
const node = getNode(rootNode, editor.selection.active);
const node = getNode(rootNode, position);
return (node && node.type === 'property') ? <Property>node : null;
}
};
return updateImageSizeCSS(editor, getPropertyInsiderStyleTag);
return updateImageSizeCSS(editor, position, getPropertyInsiderStyleTag);
}
function updateImageSizeCSSFile(editor: TextEditor) {
return updateImageSizeCSS(editor, getImageCSSNode);
function updateImageSizeCSSFile(editor: TextEditor, position: Position): Promise<TextEdit[]> {
return updateImageSizeCSS(editor, position, getImageCSSNode);
}
/**
* Updates image size of context rule of stylesheet model
*/
function updateImageSizeCSS(editor: TextEditor, fetchNode: (editor) => Property) {
function updateImageSizeCSS(editor: TextEditor, position: Position, fetchNode: (editor, position) => Property): Promise<TextEdit[]> {
const src = getImageSrcCSS(fetchNode(editor), editor.selection.active);
const src = getImageSrcCSS(fetchNode(editor, position), position);
if (!src) {
return Promise.reject(new Error('No valid image source'));
}
locateFile(path.dirname(editor.document.fileName), src)
return locateFile(path.dirname(editor.document.fileName), src)
.then(getImageSize)
.then((size: any) => {
// since this action is asynchronous, we have to ensure that editor wasnt
// changed and user didnt moved caret outside <img> node
const prop = fetchNode(editor);
if (getImageSrcCSS(prop, editor.selection.active) === src) {
updateCSSNode(editor, prop, size.width, size.height);
const prop = fetchNode(editor, position);
if (getImageSrcCSS(prop, position) === src) {
return updateCSSNode(editor, prop, size.width, size.height);
}
})
.catch(err => console.warn('Error while updating image size:', err));
.catch(err => { console.warn('Error while updating image size:', err); return []; });
}
/**
@ -108,9 +121,9 @@ function updateImageSizeCSS(editor: TextEditor, fetchNode: (editor) => Property)
* @param {TextEditor} editor
* @return {HtmlNode}
*/
function getImageHTMLNode(editor: TextEditor): HtmlNode {
function getImageHTMLNode(editor: TextEditor, position: Position): HtmlNode {
const rootNode = parseDocument(editor.document);
const node = <HtmlNode>getNode(rootNode, editor.selection.active, true);
const node = <HtmlNode>getNode(rootNode, position, true);
return node && node.name.toLowerCase() === 'img' ? node : null;
}
@ -121,9 +134,9 @@ function getImageHTMLNode(editor: TextEditor): HtmlNode {
* @param {TextEditor} editor
* @return {Property}
*/
function getImageCSSNode(editor: TextEditor): Property {
function getImageCSSNode(editor: TextEditor, position: Position): Property {
const rootNode = parseDocument(editor.document);
const node = getNode(rootNode, editor.selection.active, true);
const node = getNode(rootNode, position, true);
return node && node.type === 'property' ? <Property>node : null;
}
@ -135,7 +148,6 @@ function getImageCSSNode(editor: TextEditor): Property {
function getImageSrcHTML(node: HtmlNode): string {
const srcAttr = getAttribute(node, 'src');
if (!srcAttr) {
console.warn('No "src" attribute in', node && node.open);
return;
}
@ -173,35 +185,31 @@ function getImageSrcCSS(node: Property, position: Position): string {
* @param {number} width
* @param {number} height
*/
function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height: number) {
function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height: number): TextEdit[] {
const srcAttr = getAttribute(node, 'src');
const widthAttr = getAttribute(node, 'width');
const heightAttr = getAttribute(node, 'height');
const quote = getAttributeQuote(editor, srcAttr);
const endOfAttributes = node.attributes[node.attributes.length - 1].end;
let edits: [Range, string][] = [];
let edits: TextEdit[] = [];
let textToAdd = '';
if (!widthAttr) {
textToAdd += ` width=${quote}${width}${quote}`;
} else {
edits.push([new Range(widthAttr.value.start, widthAttr.value.end), String(width)]);
edits.push(new TextEdit(new Range(widthAttr.value.start, widthAttr.value.end), String(width)));
}
if (!heightAttr) {
textToAdd += ` height=${quote}${height}${quote}`;
} else {
edits.push([new Range(heightAttr.value.start, heightAttr.value.end), String(height)]);
edits.push(new TextEdit(new Range(heightAttr.value.start, heightAttr.value.end), String(height)));
}
if (textToAdd) {
edits.push([new Range(endOfAttributes, endOfAttributes), textToAdd]);
edits.push(new TextEdit(new Range(endOfAttributes, endOfAttributes), textToAdd));
}
return editor.edit(builder => {
edits.forEach(([rangeToReplace, textToReplace]) => {
builder.replace(rangeToReplace, textToReplace);
});
});
return edits;
}
/**
@ -211,7 +219,7 @@ function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height
* @param {number} width
* @param {number} height
*/
function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, height: number) {
function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, height: number): TextEdit[] {
const rule = srcProp.parent;
const widthProp = getCssPropertyFromRule(rule, 'width');
const heightProp = getCssPropertyFromRule(rule, 'height');
@ -220,31 +228,27 @@ function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, hei
const separator = srcProp.separator || ': ';
const before = getPropertyDelimitor(editor, srcProp);
let edits: [Range, string][] = [];
let edits: TextEdit[] = [];
if (!srcProp.terminatorToken) {
edits.push([new Range(srcProp.end, srcProp.end), ';']);
edits.push(new TextEdit(new Range(srcProp.end, srcProp.end), ';'));
}
let textToAdd = '';
if (!widthProp) {
textToAdd += `${before}width${separator}${width}px;`;
} else {
edits.push([new Range(widthProp.valueToken.start, widthProp.valueToken.end), `${width}px`]);
edits.push(new TextEdit(new Range(widthProp.valueToken.start, widthProp.valueToken.end), `${width}px`));
}
if (!heightProp) {
textToAdd += `${before}height${separator}${height}px;`;
} else {
edits.push([new Range(heightProp.valueToken.start, heightProp.valueToken.end), `${height}px`]);
edits.push(new TextEdit(new Range(heightProp.valueToken.start, heightProp.valueToken.end), `${height}px`));
}
if (textToAdd) {
edits.push([new Range(srcProp.end, srcProp.end), textToAdd]);
edits.push(new TextEdit(new Range(srcProp.end, srcProp.end), textToAdd));
}
return editor.edit(builder => {
edits.forEach(([rangeToReplace, textToReplace]) => {
builder.replace(rangeToReplace, textToReplace);
});
});
return edits;
}
/**

View file

@ -255,7 +255,7 @@ export function sameNodes(node1: Node, node2: Node): boolean {
export function getEmmetConfiguration() {
const emmetConfig = vscode.workspace.getConfiguration('emmet');
return {
useNewEmmet: emmetConfig['useNewEmmet'],
useNewEmmet: true,
showExpandedAbbreviation: emmetConfig['showExpandedAbbreviation'],
showAbbreviationSuggestions: emmetConfig['showAbbreviationSuggestions'],
syntaxProfiles: emmetConfig['syntaxProfiles'],

View file

@ -22,14 +22,14 @@ export function activate(context: vscode.ExtensionContext) {
const _linkProvider = new class implements vscode.DocumentLinkProvider {
private _cachedResult: { version: number; links: vscode.DocumentLink[] };
private _cachedResult: { key: string; links: vscode.DocumentLink[] };
private _linkPattern = /[^!]\[.*?\]\(#(.*?)\)/g;
provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentLink[] {
const { version } = document;
if (!this._cachedResult || this._cachedResult.version !== version) {
const key = `${document.uri.toString()}@${document.version}`;
if (!this._cachedResult || this._cachedResult.key !== key) {
const links = this._computeDocumentLinks(document);
this._cachedResult = { version, links };
this._cachedResult = { key, links };
}
return this._cachedResult.links;
}
@ -115,4 +115,4 @@ function registerPackageDocumentCompletions(): vscode.Disposable {
return new PackageDocument(document).provideCompletionItems(position, token);
}
});
}
}

View file

@ -22,7 +22,9 @@ const httpsRequired = localize('httpsRequired', "Images must use the HTTPS proto
const svgsNotValid = localize('svgsNotValid', "SVGs are not a valid image source.");
const embeddedSvgsNotValid = localize('embeddedSvgsNotValid', "Embedded SVGs are not a valid image source.");
const dataUrlsNotValid = localize('dataUrlsNotValid', "Data URLs are not a valid image source.");
const relativeUrlRequiresHttpsRepository = localize('relativeUrlRequiresHttpsRepository', "Relative image URLs require a repository with HTTPS protocol in the package.json.");
const relativeUrlRequiresHttpsRepository = localize('relativeUrlRequiresHttpsRepository', "Relative image URLs require a repository with HTTPS protocol to be specified in the package.json.");
const relativeIconUrlRequiresHttpsRepository = localize('relativeIconUrlRequiresHttpsRepository', "An icon requires a repository with HTTPS protocol to be specified in this package.json.");
const relativeBadgeUrlRequiresHttpsRepository = localize('relativeBadgeUrlRequiresHttpsRepository', "Relative badge URLs require a repository with HTTPS protocol to be specified in this package.json.");
enum Context {
ICON,
@ -294,7 +296,14 @@ export class ExtensionLinter {
if (!scheme && !info.hasHttpsRepository) {
const range = new Range(document.positionAt(begin), document.positionAt(end));
diagnostics.push(new Diagnostic(range, relativeUrlRequiresHttpsRepository, DiagnosticSeverity.Warning));
let message = (() => {
switch (context) {
case Context.ICON: return relativeIconUrlRequiresHttpsRepository;
case Context.BADGE: return relativeBadgeUrlRequiresHttpsRepository;
default: return relativeUrlRequiresHttpsRepository;
}
})();
diagnostics.push(new Diagnostic(range, message, DiagnosticSeverity.Warning));
}
if (endsWith(uri.path.toLowerCase(), '.svg') && allowedBadgeProviders.indexOf(uri.authority.toLowerCase()) === -1) {

View file

@ -23,5 +23,9 @@
["\"", "\""],
["'", "'"],
["`", "`"]
]
],
"indentationRules": {
"increaseIndentPattern": "^.*(\\bcase\\b.*:|\\bdefault\\b:|(\\b(func|if|else|switch|select|for|struct)\\b.*)?{[^}]*|\\([^)]*)$",
"decreaseIndentPattern": "^\\s*(\\bcase\\b.*:|\\bdefault\\b:|}[),]?|\\)[,]?)$"
}
}

View file

@ -3,5 +3,5 @@
"name": "Benvie/JavaScriptNext.tmLanguage",
"version": "0.0.0",
"license": "MIT",
"repositoryURL": "https://github.com/Benvie/JavaScriptNext.tmLanguage"
"repositoryURL": "https://github.com/Microsoft/vscode-JSON.tmLanguage"
}]

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { window, workspace, DecorationOptions, DecorationRenderOptions, Disposable, Range, TextDocument } from 'vscode';
import { window, workspace, DecorationOptions, DecorationRenderOptions, Disposable, Range, TextDocument, DocumentColorProvider, Color, ColorInfo } from 'vscode';
const MAX_DECORATORS = 500;
@ -110,13 +110,13 @@ export function activateColorDecorations(decoratorProvider: (uri: string) => The
let range = ranges[i];
let text = document.getText(range);
let value = <string>JSON.parse(text);
let color = hex2CSSColor(value);
if (color) {
let c = Color.fromHex(value);
if (c) {
decorations.push(<DecorationOptions>{
range: range,
renderOptions: {
before: {
backgroundColor: color
backgroundColor: `rgba(${c.red}, ${c.green}, ${c.blue}, ${c.alpha})`
}
}
});
@ -131,35 +131,27 @@ export function activateColorDecorations(decoratorProvider: (uri: string) => The
return Disposable.from(...disposables);
}
const colorPattern = /^#[0-9A-Fa-f]{3,8}$/;
const ColorFormat_HEX = {
opaque: '#{red:X}{green:X}{blue:X}',
transparent: '#{red:X}{green:X}{blue:X}{alpha:X}'
};
function hex2CSSColor(hex: string): string {
if (!hex || !colorPattern.test(hex)) {
return null;
}
export class ColorProvider implements DocumentColorProvider {
if (hex.length === 4 || hex.length === 7) {
// #RGB or #RRGGBB format
return hex;
constructor(private decoratorProvider: (uri: string) => Thenable<Range[]>) { }
async provideDocumentColors(document: TextDocument): Promise<ColorInfo[]> {
const ranges = await this.decoratorProvider(document.uri.toString());
const result = [];
for (let range of ranges) {
let text = document.getText(range);
let value = <string>JSON.parse(text);
let color = Color.fromHex(value);
if (color) {
let r = new Range(range.start.line, range.start.character + 1, range.end.line, range.end.character - 1);
result.push(new ColorInfo(r, color, ColorFormat_HEX, [ColorFormat_HEX]));
}
}
return result;
}
if (hex.length === 5) {
// #RGBA format
let val = parseInt(hex.substr(1), 16);
let r = (val >> 12) & 0xF;
let g = (val >> 8) & 0xF;
let b = (val >> 4) & 0xF;
let a = val & 0xF;
return `rgba(${r + r * 16}, ${g + g * 16}, ${b + b + 16}, ${+(a / 16).toFixed(2)})`;
}
if (hex.length === 9) {
// #RRGGBBAA format
let val = parseInt(hex.substr(1), 16);
let r = (val >> 24) & 0xFF;
let g = (val >> 16) & 0xFF;
let b = (val >> 8) & 0xFF;
let a = val & 0xFF;
return `rgba(${r}, ${g}, ${b}, ${+(a / 255).toFixed(2)})`;
}
return null;
}

View file

@ -9,7 +9,7 @@ import * as path from 'path';
import { workspace, languages, ExtensionContext, extensions, Uri, Range } from 'vscode';
import { LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType } from 'vscode-languageclient';
import TelemetryReporter from 'vscode-extension-telemetry';
import { activateColorDecorations } from "./colorDecorators";
import { activateColorDecorations, ColorProvider } from "./colorDecorators";
import * as nls from 'vscode-nls';
let localize = nls.loadMessageBundle();
@ -95,6 +95,8 @@ export function activate(context: ExtensionContext) {
};
disposable = activateColorDecorations(colorRequestor, { json: true }, isDecoratorEnabled);
context.subscriptions.push(disposable);
context.subscriptions.push(languages.registerColorProvider('json', new ColorProvider(colorRequestor)));
});
// Push the disposable to the context's subscriptions so that the

View file

@ -3,4 +3,5 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../../src/vs/vscode.proposed.d.ts'/>

View file

@ -9,6 +9,7 @@
"activationEvents": [
"onLanguage:json"
],
"enableProposedApi": true,
"main": "./client/out/jsonMain",
"scripts": {
"compile": "gulp compile-extension:json-client && gulp compile-extension:json-server",

View file

@ -43,9 +43,9 @@
"resolved": "https://registry.npmjs.org/request-light/-/request-light-0.2.1.tgz"
},
"vscode-json-languageservice": {
"version": "2.0.12",
"version": "2.0.13",
"from": "vscode-json-languageservice@next",
"resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-2.0.12.tgz"
"resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-2.0.13.tgz"
},
"vscode-jsonrpc": {
"version": "3.1.0-alpha.1",

View file

@ -10,7 +10,7 @@
"dependencies": {
"jsonc-parser": "^0.4.2",
"request-light": "^0.2.1",
"vscode-json-languageservice": "^2.0.12",
"vscode-json-languageservice": "^2.0.13",
"vscode-languageserver": "^3.1.0-alpha.1",
"vscode-nls": "^2.0.2"
},

View file

@ -21,5 +21,9 @@
["(", ")"],
["\"", "\""],
["'", "'"]
]
],
"indentationRules": {
"increaseIndentPattern": "(^.*\\{[^}]*$)",
"decreaseIndentPattern": "^\\s*\\}"
}
}

View file

@ -11,7 +11,7 @@
let didShow = false;
document.addEventListener('securitypolicyviolation', () => {
const showCspWarning = () => {
if (didShow) {
return;
}
@ -28,5 +28,15 @@
notification.setAttribute('href', `command:markdown.showPreviewSecuritySelector?${encodeURIComponent(JSON.stringify(args))}`);
document.body.appendChild(notification);
};
document.addEventListener('securitypolicyviolation', () => {
showCspWarning();
});
window.addEventListener('message', (event) => {
if (event && event.data && event.data.name === 'vscode-did-block-svg') {
showCspWarning();
}
});
}());

View file

@ -157,7 +157,7 @@
}
}
if (document.readyState === 'loading') {
if (document.readyState === 'loading' || document.readyState === 'uninitialized') {
document.addEventListener('DOMContentLoaded', onLoad);
} else {
onLoad();

View file

@ -14,6 +14,7 @@
],
"activationEvents": [
"onLanguage:markdown",
"onCommand:markdown.refreshPreview",
"onCommand:markdown.showPreview",
"onCommand:markdown.showPreviewToSide",
"onCommand:markdown.showSource",
@ -71,6 +72,11 @@
"dark": "./media/ViewSource_inverse.svg"
}
},
{
"command": "markdown.refreshPreview",
"title": "%markdown.refreshPreview.title%",
"category": "Markdown"
},
{
"command": "markdown.showPreviewSecuritySelector",
"title": "%markdown.showPreviewSecuritySelector.title%",
@ -90,6 +96,10 @@
"when": "resourceScheme == markdown",
"group": "navigation"
},
{
"command": "markdown.refreshPreview",
"when": "resourceScheme == markdown"
},
{
"command": "markdown.showPreviewSecuritySelector",
"when": "resourceScheme == markdown"
@ -168,6 +178,11 @@
"default": false,
"description": "%markdown.preview.breaks.desc%"
},
"markdown.preview.linkify": {
"type": "boolean",
"default": true,
"description": "%markdown.preview.linkify%"
},
"markdown.preview.fontFamily": {
"type": "string",
"default": "-apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', 'HelveticaNeue-Light', 'Ubuntu', 'Droid Sans', sans-serif",

View file

@ -1,5 +1,6 @@
{
"markdown.preview.breaks.desc": "Sets how line-breaks are rendered in the markdown preview. Setting it to 'true' creates a <br> for every newline.",
"markdown.preview.linkify": "Enable or disable conversion of URL-like text to links in the markdown preview.",
"markdown.preview.doubleClickToSwitchToEditor.desc": "Double click in the markdown preview to switch to the editor.",
"markdown.preview.fontFamily.desc": "Controls the font family used in the markdown preview.",
"markdown.preview.fontSize.desc": "Controls the font size in pixels used in the markdown preview.",
@ -12,6 +13,7 @@
"markdown.previewSide.title" : "Open Preview to the Side",
"markdown.showSource.title" : "Show Source",
"markdown.styles.dec": "A list of URLs or local paths to CSS style sheets to use from the markdown preview. Relative paths are interpreted relative to the folder open in the explorer. If there is no open folder, they are interpreted relative to the location of the markdown file. All '\\' need to be written as '\\\\'.",
"markdown.showPreviewSecuritySelector.title": "Change Markdown Preview Security Settings",
"markdown.trace.desc": "Enable debug logging for the markdown extension."
"markdown.showPreviewSecuritySelector.title": "Change Preview Security Settings",
"markdown.trace.desc": "Enable debug logging for the markdown extension.",
"markdown.refreshPreview.title": "Refresh Preview"
}

View file

@ -98,8 +98,8 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.languages.registerDocumentLinkProvider('markdown', new DocumentLinkProvider()));
context.subscriptions.push(vscode.commands.registerCommand('markdown.showPreview', showPreview));
context.subscriptions.push(vscode.commands.registerCommand('markdown.showPreviewToSide', uri => showPreview(uri, true)));
context.subscriptions.push(vscode.commands.registerCommand('markdown.showPreview', (uri) => showPreview(cspArbiter, uri, false)));
context.subscriptions.push(vscode.commands.registerCommand('markdown.showPreviewToSide', uri => showPreview(cspArbiter, uri, true)));
context.subscriptions.push(vscode.commands.registerCommand('markdown.showSource', showSource));
context.subscriptions.push(vscode.commands.registerCommand('_markdown.revealLine', (uri, line) => {
@ -168,6 +168,22 @@ export function activate(context: vscode.ExtensionContext) {
}
}));
context.subscriptions.push(vscode.commands.registerCommand('markdown.refreshPreview', (resource: string | undefined) => {
if (resource) {
const source = vscode.Uri.parse(resource);
contentProvider.update(source);
} else if (vscode.window.activeTextEditor && isMarkdownFile(vscode.window.activeTextEditor.document)) {
contentProvider.update(getMarkdownUri(vscode.window.activeTextEditor.document.uri));
} else if (!vscode.window.activeTextEditor) {
// update all generated md documents
for (const document of vscode.workspace.textDocuments) {
if (document.uri.scheme === 'markdown') {
contentProvider.update(document.uri);
}
}
}
}));
context.subscriptions.push(vscode.commands.registerCommand('_markdown.onPreviewStyleLoadError', (resources: string[]) => {
vscode.window.showWarningMessage(localize('onPreviewStyleLoadError', "Could not load 'markdown.styles': {0}", resources.join(', ')));
}));
@ -206,7 +222,7 @@ export function activate(context: vscode.ExtensionContext) {
}
function showPreview(uri?: vscode.Uri, sideBySide: boolean = false) {
function showPreview(cspArbiter: ExtensionContentSecurityPolicyArbiter, uri?: vscode.Uri, sideBySide: boolean = false) {
let resource = uri;
if (!(resource instanceof vscode.Uri)) {
if (vscode.window.activeTextEditor) {
@ -228,7 +244,10 @@ function showPreview(uri?: vscode.Uri, sideBySide: boolean = false) {
getMarkdownUri(resource),
getViewColumn(sideBySide),
`Preview '${path.basename(resource.fsPath)}'`,
{ allowScripts: true, allowSvgs: true });
{
allowScripts: true,
allowSvgs: cspArbiter.shouldAllowSvgsForResource(resource)
});
if (telemetryReporter) {
telemetryReporter.sendTelemetryEvent('openPreview', {

View file

@ -65,7 +65,12 @@ export class MarkdownEngine {
this.addLinkNormalizer(this.md);
this.addLinkValidator(this.md);
}
this.md.set({ breaks: vscode.workspace.getConfiguration('markdown').get('preview.breaks', false) });
const config = vscode.workspace.getConfiguration('markdown');
this.md.set({
breaks: config.get('preview.breaks', false),
linkify: config.get('preview.linkify', true)
});
return this.md;
}

View file

@ -252,11 +252,11 @@ export class MDDocumentContentProvider implements vscode.TextDocumentContentProv
if (!this.config.isEqualTo(newConfig)) {
this.config = newConfig;
// update all generated md documents
vscode.workspace.textDocuments.forEach(document => {
for (const document of vscode.workspace.textDocuments) {
if (document.uri.scheme === 'markdown') {
this.update(document.uri);
}
});
}
}
}

View file

@ -22,6 +22,8 @@ export interface ContentSecurityPolicyArbiter {
getSecurityLevelForResource(resource: vscode.Uri): MarkdownPreviewSecurityLevel;
setSecurityLevelForResource(resource: vscode.Uri, level: MarkdownPreviewSecurityLevel): Thenable<void>;
shouldAllowSvgsForResource(resource: vscode.Uri): void;
}
export class ExtensionContentSecurityPolicyArbiter implements ContentSecurityPolicyArbiter {
@ -50,6 +52,11 @@ export class ExtensionContentSecurityPolicyArbiter implements ContentSecurityPol
return this.globalState.update(this.security_level_key + this.getRoot(resource), level);
}
public shouldAllowSvgsForResource(resource: vscode.Uri) {
const securityLevel = this.getSecurityLevelForResource(resource);
return securityLevel === MarkdownPreviewSecurityLevel.AllowInsecureContent || securityLevel === MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent;
}
private getRoot(resource: vscode.Uri): vscode.Uri {
if (vscode.workspace.workspaceFolders) {
const folderForResource = vscode.workspace.getWorkspaceFolder(resource);
@ -79,36 +86,25 @@ export class PreviewSecuritySelector {
) { }
public async showSecutitySelectorForResource(resource: vscode.Uri): Promise<void> {
function markActiveWhen(when: boolean): string {
return when ? '• ' : '';
}
const currentSecurityLevel = this.cspArbiter.getSecurityLevelForResource(resource);
const selection = await vscode.window.showQuickPick<PreviewSecurityPickItem>(
[
{
level: MarkdownPreviewSecurityLevel.Strict,
label: localize(
'preview.showPreviewSecuritySelector.strictTitle',
'Strict. Only load secure content.'),
description: '',
detail: currentSecurityLevel === MarkdownPreviewSecurityLevel.Strict
? localize('preview.showPreviewSecuritySelector.currentSelection', 'Current setting')
: ''
label: markActiveWhen(currentSecurityLevel === MarkdownPreviewSecurityLevel.Strict) + localize('strict.title', 'Strict'),
description: localize('strict.description', 'Only load secure content'),
}, {
level: MarkdownPreviewSecurityLevel.AllowInsecureContent,
label: localize(
'preview.showPreviewSecuritySelector.insecureContentTitle',
'Allow loading content over http.'),
description: '',
detail: currentSecurityLevel === MarkdownPreviewSecurityLevel.AllowInsecureContent
? localize('preview.showPreviewSecuritySelector.currentSelection', 'Current setting')
: ''
label: markActiveWhen(currentSecurityLevel === MarkdownPreviewSecurityLevel.AllowInsecureContent) + localize('insecureContent.title', 'Allow insecure content'),
description: localize('insecureContent.description', 'Enable loading content over http'),
}, {
level: MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent,
label: localize(
'preview.showPreviewSecuritySelector.scriptsAndAllContent',
'Allow all content and script execution. Not recommend.'),
description: '',
detail: currentSecurityLevel === MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent
? localize('preview.showPreviewSecuritySelector.currentSelection', 'Current setting')
: ''
label: markActiveWhen(currentSecurityLevel === MarkdownPreviewSecurityLevel.AllowScriptsAndAllContent) + localize('disable.title', 'Disable'),
description: localize('disable.description', 'Allow all content and script execution. Not recommended'),
},
], {
placeHolder: localize(
@ -123,6 +119,14 @@ export class PreviewSecuritySelector {
await this.cspArbiter.setSecurityLevelForResource(resource, selection.level);
const sourceUri = getMarkdownUri(resource);
await vscode.commands.executeCommand('_workbench.htmlPreview.updateOptions',
sourceUri,
{
allowScripts: true,
allowSvgs: this.cspArbiter.shouldAllowSvgsForResource(resource)
});
this.contentProvider.update(sourceUri);
}
}

View file

@ -14,6 +14,9 @@
{ "open": "(", "close": ")", "notIn": ["string"] },
{ "open": "'", "close": "'", "notIn": ["string", "comment"] },
{ "open": "\"", "close": "\"", "notIn": ["string"] }
]
}
],
"indentationRules": {
"increaseIndentPattern": "({(?!.+}).*|\\(|\\[|((else(\\s)?)?if|else|for(each)?|while|switch).*:)\\s*(/[/*].*)?$",
"decreaseIndentPattern": "^(.*\\*\\/)?\\s*((\\})|(\\)+[;,])|(\\][;,])|\\b(else:)|\\b((end(if|for(each)?|while|switch));))"
}
}

View file

@ -1,10 +1,10 @@
{
"information_for_contributors": [
"This file has been converted from https://github.com/atom/language-php/blob/master/grammars/php.cson",
"This file has been converted from https://github.com/roblourens/language-php/blob/vscode_1.15/grammars/php.cson",
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
"Once accepted there, we are happy to receive an update request."
],
"version": "https://github.com/atom/language-php/commit/9cb0db1e4fcb14532e93c873187406e6ba829af4",
"version": "https://github.com/roblourens/language-php/commit/91d50ab5f871ea2d11b4c511dc0b9a972e4ac5ce",
"scopeName": "text.html.php",
"name": "PHP",
"fileTypes": [
@ -154,20 +154,23 @@
"class-body": {
"patterns": [
{
"match": "(?xi)\n\\b(use)\\s+\n([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)\n((?:\\s*,\\s*[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)*)\n(?=\\s*;)",
"match": "(?xi)\n\\b(use)\\s+\n([a-z_\\x{7f}-\\x{ff}\\\\][a-z0-9_\\x{7f}-\\x{ff}\\\\]*)\n((?:\\s*,\\s*[a-z_\\x{7f}-\\x{ff}\\\\][a-z0-9_\\x{7f}-\\x{ff}\\\\]*)*)\n(?=\\s*;)",
"name": "meta.use.php",
"captures": {
"1": {
"name": "keyword.other.use.php"
},
"2": {
"name": "support.class.php"
"patterns": [
{
"include": "#class-name"
}
]
},
"3": {
"patterns": [
{
"match": "(?i)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*",
"name": "support.class.php"
"include": "#class-name"
},
{
"match": ",",
@ -193,16 +196,19 @@
"name": "meta.use.php",
"patterns": [
{
"match": "(?xi)\n([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)\n((?:\\s*,\\s*[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)*)",
"match": "(?xi)\n([a-z_\\x{7f}-\\x{ff}\\\\][a-z0-9_\\x{7f}-\\x{ff}\\\\]*)\n((?:\\s*,\\s*[a-z_\\x{7f}-\\x{ff}\\\\][a-z0-9_\\x{7f}-\\x{ff}\\\\]*)*)",
"captures": {
"1": {
"name": "support.class.php"
"patterns": [
{
"include": "#class-name"
}
]
},
"2": {
"patterns": [
{
"match": "(?i)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*",
"name": "support.class.php"
"include": "#class-name"
},
{
"match": ",",

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-fg{fill:#f0eff1}.icon-folder{fill:#656565}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M14 .969H6.884l-1 2H3c-.97 0-2 .701-2 2v2H0v1.196l.516 1.288A4.437 4.437 0 0 0 0 11.5C0 13.981 2.019 16 4.5 16c1.569 0 2.95-.81 3.755-2.031H14s.86-.021 1.43-.565c.344-.332.57-.817.57-1.435v-9c0-1.303-1.005-2-2-2z" id="outline" style="display: none;"/><path class="icon-folder" d="M11 8l3 5H8.724c.168-.471.276-.971.276-1.5 0-1.421-.675-2.675-1.706-3.5H11zM1.706 8H1l.19.476c.159-.174.333-.329.516-.476zM14 2H7.5l-1 2H3c.236 0-1 0-1 1v2.762a4.49 4.49 0 0 1 1-.485V5h4l1-2h6v9.984S15 13 15 12V3c0-1-1-1-1-1zm-9.5 8a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM8 11.5a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0zm-1 0a2.5 2.5 0 1 0-5 0 2.5 2.5 0 0 0 5 0z" id="iconBg"/><g id="iconFg"><path id="iconFg_1_" class="icon-vs-fg" d="M3 7.969v-3h4l1-2h6v10l-3-5H3z" style="display: none;"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-fg{fill:#2b282e}.icon-folder{fill:#C5C5C5}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M14 .969H6.884l-1 2H3c-.97 0-2 .701-2 2v2H0v1.196l.516 1.288A4.437 4.437 0 0 0 0 11.5C0 13.981 2.019 16 4.5 16c1.569 0 2.95-.81 3.755-2.031H14s.86-.021 1.43-.565c.344-.332.57-.817.57-1.435v-9c0-1.303-1.005-2-2-2z" id="outline" style="display: none;"/><path class="icon-folder" d="M11 8l3 5H8.724c.168-.471.276-.971.276-1.5 0-1.421-.675-2.675-1.706-3.5H11zM1.706 8H1l.19.476c.159-.174.333-.329.516-.476zM14 2H7.5l-1 2H3c.236 0-1 0-1 1v2.762a4.49 4.49 0 0 1 1-.485V5h4l1-2h6v9.984S15 13 15 12V3c0-1-1-1-1-1zm-9.5 8a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM8 11.5a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0zm-1 0a2.5 2.5 0 1 0-5 0 2.5 2.5 0 0 0 5 0z" id="iconBg"/><g id="iconFg"><path id="iconFg_1_" class="icon-vs-fg" d="M3 7.969v-3h4l1-2h6v10l-3-5H3z" style="display: none;"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-fg{fill:#f0eff1}.icon-folder{fill:#656565}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M14.5 1H6.39l-1 2H2.504c-.827 0-1.5.673-1.5 1.5v4.202A4.454 4.454 0 0 0 0 11.5C0 13.981 2.019 16 4.5 16c1.557 0 2.93-.795 3.738-2H14.5c.827 0 1.5-.673 1.5-1.5v-10c0-.827-.673-1.5-1.5-1.5z" id="outline" style="display: none;"/><path class="icon-folder" d="M14.5 2H7.008l-1 2H2.504a.5.5 0 0 0-.5.5v3.26A4.47 4.47 0 0 1 4.5 7C6.981 7 9 9.019 9 11.5c0 .529-.108 1.029-.276 1.5H14.5a.5.5 0 0 0 .5-.5v-10a.5.5 0 0 0-.5-.5zm-.496 2H7.508l.5-1h5.996v1zM4.5 8a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7zM7 11.5a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0zm-1 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z" id="iconBg"/><g id="iconFg"><path class="icon-vs-fg" d="M14 3v1H7.5L8 3h6z" style="display: none;"/></g></svg>

After

Width:  |  Height:  |  Size: 989 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-fg{fill:#2b282e}.icon-folder{fill:#C5C5C5}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M14.5 1H6.39l-1 2H2.504c-.827 0-1.5.673-1.5 1.5v4.202A4.454 4.454 0 0 0 0 11.5C0 13.981 2.019 16 4.5 16c1.557 0 2.93-.795 3.738-2H14.5c.827 0 1.5-.673 1.5-1.5v-10c0-.827-.673-1.5-1.5-1.5z" id="outline" style="display: none;"/><path class="icon-folder" d="M14.5 2H7.008l-1 2H2.504a.5.5 0 0 0-.5.5v3.26A4.47 4.47 0 0 1 4.5 7C6.981 7 9 9.019 9 11.5c0 .529-.108 1.029-.276 1.5H14.5a.5.5 0 0 0 .5-.5v-10a.5.5 0 0 0-.5-.5zm-.496 2H7.508l.5-1h5.996v1zM4.5 8a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7zM7 11.5a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0zm-1 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z" id="iconBg"/><g id="iconFg"><path class="icon-vs-fg" d="M14 3v1H7.5L8 3h6z" style="display: none;"/></g></svg>

After

Width:  |  Height:  |  Size: 989 B

View file

@ -1,5 +1,11 @@
{
"iconDefinitions": {
"_root_folder_dark": {
"iconPath": "./images/RootFolder_16x_inverse.svg"
},
"_root_folder_open_dark": {
"iconPath": "./images/RootFolderOpen_16x_inverse.svg"
},
"_folder_dark": {
"iconPath": "./images/Folder_16x_inverse.svg"
},
@ -9,6 +15,12 @@
"_file_dark": {
"iconPath": "./images/Document_16x_inverse.svg"
},
"_root_folder": {
"iconPath": "./images/RootFolder_16x.svg"
},
"_root_folder_open": {
"iconPath": "./images/RootFolderOpen_16x.svg"
},
"_folder_light": {
"iconPath": "./images/Folder_16x.svg"
},
@ -23,6 +35,8 @@
"folderExpanded": "_folder_open_dark",
"folder": "_folder_dark",
"file": "_file_dark",
"rootFolderExpanded": "_root_folder_open_dark",
"rootFolder": "_root_folder_dark",
"fileExtensions": {
// icons by file extension
},
@ -35,6 +49,8 @@
"light": {
"folderExpanded": "_folder_open_light",
"folder": "_folder_light",
"rootFolderExpanded": "_root_folder_open",
"rootFolder": "_root_folder",
"file": "_file_light",
"fileExtensions": {
// icons by file extension

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command, commands, Uri, workspace, WorkspaceEdit, TextEdit, FormattingOptions, window, ProviderResult } from 'vscode';
import { CodeActionProvider, TextDocument, Range, CancellationToken, CodeActionContext, Command, commands, Uri, workspace, WorkspaceEdit, TextEdit, FormattingOptions, window } from 'vscode';
import * as Proto from '../protocol';
import { ITypescriptServiceClient } from '../typescriptService';
@ -32,7 +32,7 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider
commands.registerCommand(this.commandId, this.onCodeAction, this);
}
public provideCodeActions(document: TextDocument, range: Range, context: CodeActionContext, token: CancellationToken): ProviderResult<Command[]> {
public async provideCodeActions(document: TextDocument, range: Range, context: CodeActionContext, token: CancellationToken): Promise<Command[]> {
if (!this.client.apiVersion.has213Features()) {
return [];
}
@ -41,6 +41,12 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider
if (!file) {
return [];
}
const supportedActions = await this.getSupportedActionsForContext(context);
if (!supportedActions.size) {
return [];
}
let formattingOptions: FormattingOptions | undefined = undefined;
for (const editor of window.visibleTextEditors) {
if (editor.document.fileName === document.fileName) {
@ -48,27 +54,23 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider
break;
}
}
const source: Source = {
uri: document.uri,
version: document.version,
range: range,
formattingOptions: formattingOptions
};
return this.getSupportedActionsForContext(context)
.then(supportedActions => {
if (!supportedActions.size) {
return [];
}
return this.client.execute('getCodeFixes', {
file: file,
startLine: range.start.line + 1,
endLine: range.end.line + 1,
startOffset: range.start.character + 1,
endOffset: range.end.character + 1,
errorCodes: Array.from(supportedActions)
} as Proto.CodeFixRequestArgs, token).then(response => response.body || []);
})
.then(codeActions => codeActions.map(action => this.actionToEdit(source, action)));
const args: Proto.CodeFixRequestArgs = {
file: file,
startLine: range.start.line + 1,
endLine: range.end.line + 1,
startOffset: range.start.character + 1,
endOffset: range.end.character + 1,
errorCodes: Array.from(supportedActions)
};
const response = await this.client.execute('getCodeFixes', args, token);
return (response.body || []).map(action => this.actionToEdit(source, action));
}
private get supportedCodeActions(): Thenable<NumberSet> {
@ -110,38 +112,36 @@ export default class TypeScriptCodeActionProvider implements CodeActionProvider
};
}
private onCodeAction(source: Source, workspaceEdit: WorkspaceEdit) {
workspace.applyEdit(workspaceEdit).then(success => {
if (!success) {
return Promise.reject<boolean>(false);
}
private async onCodeAction(source: Source, workspaceEdit: WorkspaceEdit): Promise<boolean> {
const success = workspace.applyEdit(workspaceEdit);
if (!success) {
return false;
}
let firstEdit: TextEdit | null = null;
for (const [uri, edits] of workspaceEdit.entries()) {
if (uri.fsPath === source.uri.fsPath) {
firstEdit = edits[0];
break;
}
let firstEdit: TextEdit | undefined = undefined;
for (const [uri, edits] of workspaceEdit.entries()) {
if (uri.fsPath === source.uri.fsPath) {
firstEdit = edits[0];
break;
}
}
if (!firstEdit) {
return true;
}
if (!firstEdit) {
return true;
}
const newLines = firstEdit.newText.match(/\n/g);
const editedRange = new Range(
firstEdit.range.start.line, 0,
firstEdit.range.end.line + 1 + (newLines ? newLines.length : 0), 0);
// TODO: Workaround for https://github.com/Microsoft/TypeScript/issues/12249
// apply formatting to the source range until TS returns formatted results
return commands.executeCommand('vscode.executeFormatRangeProvider', source.uri, editedRange, source.formattingOptions || {}).then((edits: TextEdit[]) => {
if (!edits || !edits.length) {
return false;
}
const workspaceEdit = new WorkspaceEdit();
workspaceEdit.set(source.uri, edits);
return workspace.applyEdit(workspaceEdit);
});
});
const newLines = firstEdit.newText.match(/\n/g);
const editedRange = new Range(
firstEdit.range.start.line, 0,
firstEdit.range.end.line + 1 + (newLines ? newLines.length : 0), 0);
// TODO: Workaround for https://github.com/Microsoft/TypeScript/issues/12249
// apply formatting to the source range until TS returns formatted results
const edits = (await commands.executeCommand('vscode.executeFormatRangeProvider', source.uri, editedRange, source.formattingOptions || {})) as TextEdit[];
if (!edits || !edits.length) {
return false;
}
const formattingEdit = new WorkspaceEdit();
formattingEdit.set(source.uri, edits);
return workspace.applyEdit(formattingEdit);
}
}

View file

@ -14,27 +14,29 @@ export default class TypeScriptHoverProvider implements HoverProvider {
public constructor(
private client: ITypescriptServiceClient) { }
public provideHover(document: TextDocument, position: Position, token: CancellationToken): Promise<Hover | undefined | null> {
public async provideHover(document: TextDocument, position: Position, token: CancellationToken): Promise<Hover | undefined> {
const filepath = this.client.normalizePath(document.uri);
if (!filepath) {
return Promise.resolve(null);
return undefined;
}
const args: Proto.FileLocationRequestArgs = {
file: filepath,
line: position.line + 1,
offset: position.character + 1
};
return this.client.execute('quickinfo', args, token).then((response): Hover | undefined => {
try {
const response = await this.client.execute('quickinfo', args, token);
if (response && response.body) {
const data = response.body;
return new Hover(
TypeScriptHoverProvider.getContents(data),
new Range(data.start.line - 1, data.start.offset - 1, data.end.line - 1, data.end.offset - 1));
}
return undefined;
}, () => {
return null;
});
} catch (e) {
// noop
}
return undefined;
}
private static getContents(data: Proto.QuickInfoResponseBody) {

View file

@ -42,10 +42,6 @@ class TscTaskProvider implements vscode.TaskProvider {
this.tsconfigProvider = new TsConfigProvider();
}
dispose() {
this.tsconfigProvider.dispose();
}
public async provideTasks(token: vscode.CancellationToken): Promise<vscode.Task[]> {
const folders = vscode.workspace.workspaceFolders;
if (!folders || !folders.length) {
@ -83,10 +79,9 @@ class TscTaskProvider implements vscode.TaskProvider {
if (editor) {
if (path.basename(editor.document.fileName).match(/^tsconfig\.(.\.)?json$/)) {
const path = editor.document.uri;
const folder = vscode.workspace.getWorkspaceFolder(path);
return [{
path: path.fsPath,
workspaceFolder: folder
workspaceFolder: vscode.workspace.getWorkspaceFolder(path)
}];
}
}
@ -96,23 +91,28 @@ class TscTaskProvider implements vscode.TaskProvider {
return [];
}
const res: Proto.ProjectInfoResponse = await this.lazyClient().execute(
'projectInfo',
{ file, needFileNameList: false } as protocol.ProjectInfoRequestArgs,
token);
try {
const res: Proto.ProjectInfoResponse = await this.lazyClient().execute(
'projectInfo',
{ file, needFileNameList: false } as protocol.ProjectInfoRequestArgs,
token);
if (!res || !res.body) {
return [];
if (!res || !res.body) {
return [];
}
const { configFileName } = res.body;
if (configFileName && !isImplicitProjectConfigFile(configFileName)) {
const path = vscode.Uri.file(configFileName);
const folder = vscode.workspace.getWorkspaceFolder(path);
return [{
path: configFileName,
workspaceFolder: folder
}];
}
}
const { configFileName } = res.body;
if (configFileName && !isImplicitProjectConfigFile(configFileName)) {
const path = vscode.Uri.file(configFileName);
const folder = vscode.workspace.getWorkspaceFolder(path);
return [{
path: configFileName,
workspaceFolder: folder
}];
catch (e) {
// noop
}
return [];
}
@ -170,8 +170,8 @@ class TscTaskProvider implements vscode.TaskProvider {
}
}
const watch = this.shouldUseWatchForBuild(project);
const identifier: TypeScriptTaskDefinition = { type: 'typescript', tsconfig: project.path, watch: watch };
const watch = false && this.shouldUseWatchForBuild(project);
const identifier: TypeScriptTaskDefinition = { type: 'typescript', tsconfig: label };
const buildTask = new vscode.Task(
identifier,
watch
@ -179,8 +179,12 @@ class TscTaskProvider implements vscode.TaskProvider {
: localize('buildTscLabel', 'build - {0}', label),
'tsc',
new vscode.ShellExecution(`${command} ${watch ? '--watch' : ''} -p "${project.path}"`),
'$tsc');
watch
? '$tsc-watch'
: '$tsc'
);
buildTask.group = vscode.TaskGroup.Build;
buildTask.isBackground = watch;
return buildTask;
}
}

View file

@ -25,7 +25,7 @@ export default class TypeScriptWorkspaceSymbolProvider implements WorkspaceSymbo
private client: ITypescriptServiceClient,
private modeId: string) { }
public provideWorkspaceSymbols(search: string, token: CancellationToken): Promise<SymbolInformation[]> {
public async provideWorkspaceSymbols(search: string, token: CancellationToken): Promise<SymbolInformation[]> {
// typescript wants to have a resource even when asking
// general questions so we check the active editor. If this
// doesn't match we take the first TS document.
@ -48,37 +48,34 @@ export default class TypeScriptWorkspaceSymbolProvider implements WorkspaceSymbo
}
if (!uri) {
return Promise.resolve<SymbolInformation[]>([]);
return [];
}
const filepath = this.client.normalizePath(uri);
if (!filepath) {
return Promise.resolve<SymbolInformation[]>([]);
return [];
}
const args: Proto.NavtoRequestArgs = {
file: filepath,
searchValue: search
};
return this.client.execute('navto', args, token).then((response): SymbolInformation[] => {
const result: SymbolInformation[] = [];
let data = response.body;
if (data) {
for (let item of data) {
if (!item.containerName && item.kind === 'alias') {
continue;
}
const range = new Range(item.start.line - 1, item.start.offset - 1, item.end.line - 1, item.end.offset - 1);
let label = item.name;
if (item.kind === 'method' || item.kind === 'function') {
label += '()';
}
result.push(new SymbolInformation(label, getSymbolKind(item), item.containerName || '',
new Location(this.client.asUrl(item.file), range)));
const response = await this.client.execute('navto', args, token);
const result: SymbolInformation[] = [];
const data = response.body;
if (data) {
for (const item of data) {
if (!item.containerName && item.kind === 'alias') {
continue;
}
const range = new Range(item.start.line - 1, item.start.offset - 1, item.end.line - 1, item.end.offset - 1);
let label = item.name;
if (item.kind === 'method' || item.kind === 'function') {
label += '()';
}
result.push(new SymbolInformation(label, getSymbolKind(item), item.containerName || '',
new Location(this.client.asUrl(item.file), range)));
}
return result;
}, () => {
return [];
});
}
return result;
}
}

View file

@ -327,6 +327,12 @@ class LanguageProvider {
// e.g. *-----*/|
beforeText: /^(\t|(\ \ ))*\ \*[^/]*\*\/\s*$/,
action: { indentAction: IndentAction.None, removeText: 1 }
},
{
// e.g. if (...) | {}
beforeText: /^\s*(for|while|if|else)\s*/,
afterText: /^\s*{/,
action: { indentAction: IndentAction.None }
}
]
}));
@ -417,16 +423,20 @@ class LanguageProvider {
}
public syntaxDiagnosticsReceived(file: string, diagnostics: Diagnostic[]): void {
this.syntaxDiagnostics[file] = diagnostics;
if (this._validate) {
this.syntaxDiagnostics[file] = diagnostics;
}
}
public semanticDiagnosticsReceived(file: string, diagnostics: Diagnostic[]): void {
const syntaxMarkers = this.syntaxDiagnostics[file];
if (syntaxMarkers) {
delete this.syntaxDiagnostics[file];
diagnostics = syntaxMarkers.concat(diagnostics);
if (this._validate) {
const syntaxMarkers = this.syntaxDiagnostics[file];
if (syntaxMarkers) {
delete this.syntaxDiagnostics[file];
diagnostics = syntaxMarkers.concat(diagnostics);
}
this.currentDiagnostics.set(this.client.asUrl(file), diagnostics);
}
this.currentDiagnostics.set(this.client.asUrl(file), diagnostics);
}
public configFileDiagnosticsReceived(file: string, diagnostics: Diagnostic[]): void {
@ -480,16 +490,16 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost {
return;
}
const langauges = new Set<string>();
const languages = new Set<string>();
for (const plugin of plugins) {
for (const language of plugin.languages) {
langauges.add(language);
languages.add(language);
}
}
if (langauges.size) {
if (languages.size) {
const description: LanguageDescription = {
id: 'typescript-plugins',
modeIds: Array.from(langauges.values()),
modeIds: Array.from(languages.values()),
diagnosticSource: 'ts-plugins',
isExternal: true
};

View file

@ -180,10 +180,10 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
this.versionProvider = new TypeScriptVersionProvider(this.configuration);
this.versionPicker = new TypeScriptVersionPicker(this.versionProvider, this.workspaceState);
this._apiVersion = new API('1.0.0');
this._apiVersion = API.defaultVersion;
this.tracer = new Tracer(this.logger);
this.disposables.push(workspace.onDidChangeConfiguration(() => {
workspace.onDidChangeConfiguration(() => {
const oldConfiguration = this.configuration;
this.configuration = TypeScriptServiceConfiguration.loadFromWorkspace();
@ -199,7 +199,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
this.restartTsServer();
}
}
}));
}, this, this.disposables);
this.telemetryReporter = new TelemetryReporter();
this.disposables.push(this.telemetryReporter);
this.startService();
@ -303,14 +303,14 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
return this.servicePromise = new Promise<cp.ChildProcess>((resolve, reject) => {
this.info(`Using tsserver from: ${currentVersion.path}`);
if (!fs.existsSync(currentVersion.path)) {
window.showWarningMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.', currentVersion.path ? path.dirname(currentVersion.path) : ''));
if (!fs.existsSync(currentVersion.tsServerPath)) {
window.showWarningMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.', currentVersion.path));
this.versionPicker.useBundledVersion();
currentVersion = this.versionPicker.currentVersion;
}
this._apiVersion = this.versionPicker.currentVersion.version;
this._apiVersion = this.versionPicker.currentVersion.version || API.defaultVersion;
const label = this._apiVersion.versionString;
const tooltip = currentVersion.path;
@ -376,7 +376,7 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
}
}
electron.fork(currentVersion.path, args, options, this.logger, (err: any, childProcess: cp.ChildProcess) => {
electron.fork(currentVersion.tsServerPath, args, options, this.logger, (err: any, childProcess: cp.ChildProcess) => {
if (err) {
this.lastError = err;
this.error('Starting TSServer failed with error.', err);
@ -599,10 +599,6 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient
}
private get mainWorkspaceRootPath(): string | undefined {
if (workspace.rootPath) {
return workspace.rootPath;
}
if (workspace.workspaceFolders && workspace.workspaceFolders.length) {
return workspace.workspaceFolders[0].uri.fsPath;
}

View file

@ -6,6 +6,7 @@
import * as semver from 'semver';
export default class API {
public static readonly defaultVersion = new API('1.0.0');
private readonly _version: string;

View file

@ -71,18 +71,15 @@ export class TypeScriptServiceConfiguration {
}
private static extractGlobalTsdk(configuration: WorkspaceConfiguration): string | null {
let inspect = configuration.inspect('typescript.tsdk');
const inspect = configuration.inspect('typescript.tsdk');
if (inspect && inspect.globalValue && 'string' === typeof inspect.globalValue) {
return inspect.globalValue;
}
if (inspect && inspect.defaultValue && 'string' === typeof inspect.defaultValue) {
return inspect.defaultValue;
}
return null;
}
private static extractLocalTsdk(configuration: WorkspaceConfiguration): string | null {
let inspect = configuration.inspect('typescript.tsdk');
const inspect = configuration.inspect('typescript.tsdk');
if (inspect && inspect.workspaceValue && 'string' === typeof inspect.workspaceValue) {
return inspect.workspaceValue;
}

View file

@ -18,7 +18,7 @@ export function makeRandomHexString(length: number): string {
let chars = ['0', '1', '2', '3', '4', '5', '6', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
let result = '';
for (let i = 0; i < length; i++) {
let idx = Math.floor(chars.length * Math.random());
const idx = Math.floor(chars.length * Math.random());
result += chars[idx];
}
return result;
@ -27,6 +27,7 @@ export function makeRandomHexString(length: number): string {
function generatePipeName(): string {
return getPipeName(makeRandomHexString(40));
}
function getPipeName(name: string): string {
const fullName = 'vscode-' + name;
if (process.platform === 'win32') {
@ -43,19 +44,22 @@ export function getTempFile(name: string): string {
}
function generatePatchedEnv(env: any, stdInPipeName: string, stdOutPipeName: string, stdErrPipeName: string): any {
function generatePatchedEnv(
env: any,
stdInPipeName: string,
stdOutPipeName: string,
stdErrPipeName: string
): any {
const newEnv = Object.assign({}, env);
// Set the two unique pipe names and the electron flag as process env
var newEnv: any = {};
for (var key in env) {
newEnv[key] = env[key];
}
newEnv['STDIN_PIPE_NAME'] = stdInPipeName;
newEnv['STDOUT_PIPE_NAME'] = stdOutPipeName;
newEnv['STDERR_PIPE_NAME'] = stdErrPipeName;
newEnv['ELECTRON_RUN_AS_NODE'] = '1';
// Ensure we always have a PATH set
newEnv['PATH'] = newEnv['PATH'] || process.env.PATH;
return newEnv;
}
@ -67,7 +71,7 @@ export function fork(
callback: (error: any, cp: cp.ChildProcess | null) => void,
): void {
var callbackCalled = false;
let callbackCalled = false;
const resolve = (result: cp.ChildProcess) => {
if (callbackCalled) {
return;
@ -90,7 +94,7 @@ export function fork(
const newEnv = generatePatchedEnv(process.env, stdInPipeName, stdOutPipeName, stdErrPipeName);
var childProcess: cp.ChildProcess;
let childProcess: cp.ChildProcess;
// Begin listening to stderr pipe
let stdErrServer = net.createServer((stdErrStream) => {
@ -115,7 +119,7 @@ export function fork(
});
stdOutServer.listen(stdOutPipeName);
var serverClosed = false;
let serverClosed = false;
const closeServer = () => {
if (serverClosed) {
return;
@ -128,8 +132,8 @@ export function fork(
// Create the process
logger.info('Forking TSServer', `PATH: ${newEnv['PATH']}`);
const bootstrapperPath = path.join(__dirname, 'electronForkStart');
childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), <any>{
const bootstrapperPath = require.resolve('./electronForkStart');
childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), {
silent: true,
cwd: options.cwd,
env: newEnv,

View file

@ -24,6 +24,7 @@ interface ProjectHintedMap {
const fileLimit = 500;
class ExcludeHintItem {
public configFileName?: string;
private _item: vscode.StatusBarItem;
private _client: ITypescriptServiceClient;
private _currentHint: Hint;
@ -133,24 +134,14 @@ function createLargeProjectMonitorFromTypeScript(item: ExcludeHintItem, client:
item.show();
const configFileName = body.projectName;
if (configFileName) {
item.configFileName = configFileName;
vscode.window.showWarningMessage<LargeProjectMessageItem>(item.getCurrentHint().message,
{
title: localize('large.label', "Configure Excludes"),
index: 0
}).then(selected => {
if (!selected || selected.index !== 0) {
return;
}
if (!isImplicitProjectConfigFile(configFileName)) {
vscode.workspace.openTextDocument(configFileName)
.then(vscode.window.showTextDocument);
} else {
const root = client.getWorkspaceRootForResource(vscode.Uri.file(configFileName));
if (root) {
openOrCreateConfigFile(
configFileName.match(/tsconfig\.?.*\.json/) !== null,
root);
}
if (selected && selected.index === 0) {
onConfigureExcludesSelected(client, configFileName);
}
});
}
@ -158,11 +149,28 @@ function createLargeProjectMonitorFromTypeScript(item: ExcludeHintItem, client:
});
}
function onConfigureExcludesSelected(client: ITypescriptServiceClient, configFileName: string) {
if (!isImplicitProjectConfigFile(configFileName)) {
vscode.workspace.openTextDocument(configFileName)
.then(vscode.window.showTextDocument);
} else {
const root = client.getWorkspaceRootForResource(vscode.Uri.file(configFileName));
if (root) {
openOrCreateConfigFile(
configFileName.match(/tsconfig\.?.*\.json/) !== null,
root);
}
}
}
export function create(client: ITypescriptServiceClient, isOpen: (path: string) => Promise<boolean>, memento: vscode.Memento) {
const toDispose: vscode.Disposable[] = [];
let item = new ExcludeHintItem(client);
const item = new ExcludeHintItem(client);
toDispose.push(vscode.commands.registerCommand('js.projectStatus.command', () => {
if (item.configFileName) {
onConfigureExcludesSelected(client, item.configFileName);
}
let { message } = item.getCurrentHint();
return vscode.window.showInformationMessage(message);
}));

View file

@ -9,67 +9,21 @@ export interface TSConfig {
workspaceFolder?: vscode.WorkspaceFolder;
}
export default class TsConfigProvider extends vscode.Disposable {
private readonly tsconfigs = new Map<string, TSConfig>();
private activated: boolean = false;
private disposables: vscode.Disposable[] = [];
constructor() {
super(() => this.dispose());
}
dispose(): void {
this.disposables.forEach(d => d.dispose());
}
export default class TsConfigProvider {
public async getConfigsForWorkspace(): Promise<Iterable<TSConfig>> {
if (!vscode.workspace.workspaceFolders) {
return [];
}
await this.ensureActivated();
return this.tsconfigs.values();
}
private async ensureActivated(): Promise<this> {
if (this.activated) {
return this;
}
this.activated = true;
this.reloadWorkspaceConfigs();
const configFileWatcher = vscode.workspace.createFileSystemWatcher('**/tsconfig*.json');
this.disposables.push(configFileWatcher);
configFileWatcher.onDidCreate(this.handleProjectCreate, this, this.disposables);
configFileWatcher.onDidDelete(this.handleProjectDelete, this, this.disposables);
vscode.workspace.onDidChangeWorkspaceFolders(() => {
this.reloadWorkspaceConfigs();
}, this, this.disposables);
return this;
}
private async reloadWorkspaceConfigs(): Promise<this> {
this.tsconfigs.clear();
const configs = new Map<string, TSConfig>();
for (const config of await vscode.workspace.findFiles('**/tsconfig*.json', '**/node_modules/**')) {
this.handleProjectCreate(config);
const root = vscode.workspace.getWorkspaceFolder(config);
if (root) {
configs.set(config.fsPath, {
path: config.fsPath,
workspaceFolder: root
});
}
}
return this;
}
private handleProjectCreate(config: vscode.Uri) {
const root = vscode.workspace.getWorkspaceFolder(config);
if (root) {
this.tsconfigs.set(config.fsPath, {
path: config.fsPath,
workspaceFolder: root
});
}
}
private handleProjectDelete(e: vscode.Uri) {
this.tsconfigs.delete(e.fsPath);
return configs.values();
}
}

View file

@ -5,7 +5,7 @@
import * as nls from 'vscode-nls';
import { TypeScriptVersionProvider, TypeScriptVersion } from "./versionProvider";
import { Memento, commands, Uri, window, QuickPickItem } from "vscode";
import { Memento, commands, Uri, window, QuickPickItem, workspace } from "vscode";
const localize = nls.loadMessageBundle();
@ -13,6 +13,7 @@ const useWorkspaceTsdkStorageKey = 'typescript.useWorkspaceTsdk';
interface MyQuickPickItem extends QuickPickItem {
id: MessageAction;
version?: TypeScriptVersion;
}
enum MessageAction {
@ -30,7 +31,7 @@ export class TypeScriptVersionPicker {
) {
this._currentVersion = this.versionProvider.defaultVersion;
if (workspaceState.get<boolean>(useWorkspaceTsdkStorageKey, false)) {
if (this.useWorkspaceTsdkSetting) {
const localVersion = this.versionProvider.localVersion;
if (localVersion) {
this._currentVersion = localVersion;
@ -38,6 +39,10 @@ export class TypeScriptVersionPicker {
}
}
public get useWorkspaceTsdkSetting(): boolean {
return this.workspaceState.get<boolean>(useWorkspaceTsdkStorageKey, false);
}
public get currentVersion(): TypeScriptVersion {
return this._currentVersion;
}
@ -46,30 +51,28 @@ export class TypeScriptVersionPicker {
this._currentVersion = this.versionProvider.bundledVersion;
}
public show(firstRun?: boolean): Thenable<{ oldVersion?: TypeScriptVersion, newVersion?: TypeScriptVersion }> {
const useWorkspaceVersionSetting = this.workspaceState.get<boolean>(useWorkspaceTsdkStorageKey, false);
const shippedVersion = this.versionProvider.defaultVersion;
const localVersion = this.versionProvider.localVersion;
public async show(firstRun?: boolean): Promise<{ oldVersion?: TypeScriptVersion, newVersion?: TypeScriptVersion }> {
const pickOptions: MyQuickPickItem[] = [];
const shippedVersion = this.versionProvider.defaultVersion;
pickOptions.push({
label: (this.currentVersion.path === shippedVersion.path && (this.currentVersion.path !== (localVersion && localVersion.path) || !useWorkspaceVersionSetting)
label: (!this.useWorkspaceTsdkSetting
? '• '
: '') + localize('useVSCodeVersionOption', 'Use VSCode\'s Version'),
description: shippedVersion.version.versionString,
detail: shippedVersion.label,
: '') + localize('useVSCodeVersionOption', 'Use VS Code\'s Version'),
description: shippedVersion.versionString,
detail: shippedVersion.pathLabel,
id: MessageAction.useBundled
});
if (localVersion) {
for (const version of this.versionProvider.localVersions) {
pickOptions.push({
label: ((this.currentVersion.path === localVersion.path && (this.currentVersion.path !== shippedVersion.path || useWorkspaceVersionSetting)
label: (this.useWorkspaceTsdkSetting && this.currentVersion.path === version.path
? '• '
: '')) + localize('useWorkspaceVersionOption', 'Use Workspace Version'),
description: localVersion.version.versionString,
detail: localVersion.label,
id: MessageAction.useLocal
: '') + localize('useWorkspaceVersionOption', 'Use Workspace Version'),
description: version.versionString,
detail: version.pathLabel,
id: MessageAction.useLocal,
version: version
});
}
@ -79,45 +82,43 @@ export class TypeScriptVersionPicker {
id: MessageAction.learnMore
});
return window.showQuickPick<MyQuickPickItem>(pickOptions, {
const selected = await window.showQuickPick<MyQuickPickItem>(pickOptions, {
placeHolder: localize(
'selectTsVersion',
'Select the TypeScript version used for JavaScript and TypeScript language features'),
ignoreFocusOut: firstRun
})
.then(selected => {
if (!selected) {
return { oldVersion: this.currentVersion };
});
if (!selected) {
return { oldVersion: this.currentVersion };
}
switch (selected.id) {
case MessageAction.useLocal:
await this.workspaceState.update(useWorkspaceTsdkStorageKey, true);
if (selected.version) {
const tsConfig = workspace.getConfiguration('typescript');
await tsConfig.update('tsdk', selected.version.pathLabel, false);
const previousVersion = this.currentVersion;
this._currentVersion = selected.version;
return { oldVersion: previousVersion, newVersion: selected.version };
}
switch (selected.id) {
case MessageAction.useLocal:
return this.workspaceState.update(useWorkspaceTsdkStorageKey, true)
.then(_ => {
if (localVersion) {
const previousVersion = this.currentVersion;
return { oldVersion: this.currentVersion };
this._currentVersion = localVersion;
return { oldVersion: previousVersion, newVersion: localVersion };
}
return { oldVersion: this.currentVersion };
});
case MessageAction.useBundled:
await this.workspaceState.update(useWorkspaceTsdkStorageKey, false);
const previousVersion = this.currentVersion;
this._currentVersion = shippedVersion;
return { oldVersion: previousVersion, newVersion: shippedVersion };
case MessageAction.useBundled:
return this.workspaceState.update(useWorkspaceTsdkStorageKey, false)
.then(_ => {
const previousVersion = this.currentVersion;
this._currentVersion = shippedVersion;
return { oldVersion: previousVersion, newVersion: shippedVersion };
});
case MessageAction.learnMore:
commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919'));
return { oldVersion: this.currentVersion };
default:
return { oldVersion: this.currentVersion };
}
});
case MessageAction.learnMore:
commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919'));
return { oldVersion: this.currentVersion };
default:
return { oldVersion: this.currentVersion };
}
}
}

View file

@ -14,10 +14,72 @@ import { TypeScriptServiceConfiguration } from "./configuration";
import API from './api';
export interface TypeScriptVersion {
label?: string;
version: API;
path: string;
export class TypeScriptVersion {
constructor(
public readonly path: string,
private readonly _pathLabel?: string
) { }
public get tsServerPath(): string {
return path.join(this.path, 'tsserver.js');
}
public get pathLabel(): string {
return typeof this._pathLabel === 'undefined' ? this.path : this._pathLabel;
}
public get isValid(): boolean {
return this.version !== undefined;
}
public get version(): API | undefined {
const version = this.getTypeScriptVersion(this.tsServerPath);
if (version) {
return version;
}
// Allow TS developers to provide custom version
const tsdkVersion = workspace.getConfiguration().get<string | undefined>('typescript.tsdk_version', undefined);
if (tsdkVersion) {
return new API(tsdkVersion);
}
return undefined;
}
public get versionString(): string {
const version = this.version;
return version ? version.versionString : localize(
'couldNotLoadTsVersion', 'Could not load the TypeScript version at this path');
}
private getTypeScriptVersion(serverPath: string): API | undefined {
if (!fs.existsSync(serverPath)) {
return undefined;
}
let p = serverPath.split(path.sep);
if (p.length <= 2) {
return undefined;
}
let p2 = p.slice(0, -2);
let modulePath = p2.join(path.sep);
let fileName = path.join(modulePath, 'package.json');
if (!fs.existsSync(fileName)) {
return undefined;
}
let contents = fs.readFileSync(fileName).toString();
let desc: any = null;
try {
desc = JSON.parse(contents);
} catch (err) {
return undefined;
}
if (!desc || !desc.version) {
return undefined;
}
return desc.version ? new API(desc.version) : undefined;
}
}
@ -51,16 +113,30 @@ export class TypeScriptVersionProvider {
}
const nodeVersions = this.localNodeModulesVersions;
if (nodeVersions && nodeVersions.length) {
if (nodeVersions && nodeVersions.length === 1) {
return nodeVersions[0];
}
return undefined;
}
public get localVersions(): TypeScriptVersion[] {
const allVersions = this.localTsdkVersions.concat(this.localNodeModulesVersions);
const paths = new Set<string>();
return allVersions.filter(x => {
if (paths.has(x.path)) {
return false;
}
paths.add(x.path);
return true;
});
}
public get bundledVersion(): TypeScriptVersion {
try {
const bundledVersion = this.loadFromPath(require.resolve('typescript/lib/tsserver.js'));
if (bundledVersion) {
const bundledVersion = new TypeScriptVersion(
path.dirname(require.resolve('typescript/lib/tsserver.js')),
'');
if (bundledVersion.isValid) {
return bundledVersion;
}
} catch (e) {
@ -68,7 +144,7 @@ export class TypeScriptVersionProvider {
}
window.showErrorMessage(localize(
'noBundledServerFound',
'VSCode\'s tsserver was deleted by another application such as a misbehaving virus detection tool. Please reinstall VS Code.'));
'VS Code\'s tsserver was deleted by another application such as a misbehaving virus detection tool. Please reinstall VS Code.'));
throw new Error('Could not find bundled tsserver.js');
}
@ -79,97 +155,41 @@ export class TypeScriptVersionProvider {
private loadVersionsFromSetting(tsdkPathSetting: string): TypeScriptVersion[] {
if (path.isAbsolute(tsdkPathSetting)) {
return this.getTypeScriptsFromPaths(tsdkPathSetting);
return [new TypeScriptVersion(tsdkPathSetting)];
}
for (const root of workspace.workspaceFolders || []) {
const rootPrefix = `./${root.name}/`;
const winRootPrefix = `.\\${root.name}\\`;
if (tsdkPathSetting.startsWith(rootPrefix) || tsdkPathSetting.startsWith(winRootPrefix)) {
const workspacePath = path.join(root.uri.fsPath, tsdkPathSetting.replace(rootPrefix, ''));
return this.getTypeScriptsFromPaths(workspacePath);
const rootPrefixes = [`./${root.name}/`, `${root.name}/`, `.\\${root.name}\\`, `${root.name}\\`];
for (const rootPrefix of rootPrefixes) {
if (tsdkPathSetting.startsWith(rootPrefix)) {
const workspacePath = path.join(root.uri.fsPath, tsdkPathSetting.replace(rootPrefix, ''));
return [new TypeScriptVersion(workspacePath, tsdkPathSetting)];
}
}
}
return this.getTypeScriptsFromPaths(tsdkPathSetting);
return this.loadTypeScriptVersionsFromPath(tsdkPathSetting);
}
private get localNodeModulesVersions(): TypeScriptVersion[] {
return this.getTypeScriptsFromPaths(path.join('node_modules', 'typescript', 'lib'));
return this.loadTypeScriptVersionsFromPath(path.join('node_modules', 'typescript', 'lib'))
.filter(x => x.isValid);
}
private getTypeScriptsFromPaths(typeScriptPath: string): TypeScriptVersion[] {
if (path.isAbsolute(typeScriptPath)) {
const version = this.loadFromPath(path.join(typeScriptPath, 'tsserver.js'));
return version ? [version] : [];
}
private loadTypeScriptVersionsFromPath(relativePath: string): TypeScriptVersion[] {
if (!workspace.workspaceFolders) {
return [];
}
const versions: TypeScriptVersion[] = [];
for (const root of [workspace.workspaceFolders[0]]) {
const p = path.join(root.uri.fsPath, typeScriptPath, 'tsserver.js');
let label: string | undefined = undefined;
for (const root of workspace.workspaceFolders) {
let label: string = relativePath;
if (workspace.workspaceFolders && workspace.workspaceFolders.length > 1) {
label = path.join(root.name, typeScriptPath);
label = path.join(root.name, relativePath);
}
const version = this.loadFromPath(p, label);
if (version) {
versions.push(version);
}
versions.push(new TypeScriptVersion(path.join(root.uri.fsPath, relativePath), label));
}
return versions;
}
public loadFromPath(tsServerPath: string, label?: string): TypeScriptVersion | undefined {
if (!fs.existsSync(tsServerPath)) {
return undefined;
}
const version = this.getTypeScriptVersion(tsServerPath);
if (version) {
return { path: tsServerPath, version, label };
}
// Allow TS developers to provide custom version
const tsdkVersion = workspace.getConfiguration().get<string | undefined>('typescript.tsdk_version', undefined);
if (tsdkVersion) {
return { path: tsServerPath, version: new API(tsdkVersion), label };
}
return undefined;
}
private getTypeScriptVersion(serverPath: string): API | undefined {
if (!fs.existsSync(serverPath)) {
return undefined;
}
let p = serverPath.split(path.sep);
if (p.length <= 2) {
return undefined;
}
let p2 = p.slice(0, -2);
let modulePath = p2.join(path.sep);
let fileName = path.join(modulePath, 'package.json');
if (!fs.existsSync(fileName)) {
return undefined;
}
let contents = fs.readFileSync(fileName).toString();
let desc: any = null;
try {
desc = JSON.parse(contents);
} catch (err) {
return undefined;
}
if (!desc || !desc.version) {
return undefined;
}
return desc.version ? new API(desc.version) : undefined;
}
}

View file

@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Do not edit this file. It is machine generated.
{
"command.removeTag": "移除标签",
"command.updateTag": "更新标签",
"command.matchTag": "转至匹配对",
"command.prevEditPoint": "转到上一编辑点",
"command.nextEditPoint": "转到下一编辑点",
"command.mergeLines": "合并行",
"command.selectPrevItem": "选择上一项",
"command.selectNextItem": "选择下一项",
"command.splitJoinTag": "分离/联接标记",
"command.toggleComment": "切换注释",
"command.evaluateMathExpression": "求数学表达式的值",
"command.updateImageSize": "更新图像大小",
"command.reflectCSSValue": "映射 CSS 值",
"command.incrementNumberByOne": "增加 1",
"command.decrementNumberByOne": "减少 1",
"command.incrementNumberByOneTenth": "增加 0.1",
"command.decrementNumberByOneTenth": "减少 0.1",
"command.incrementNumberByTen": "增加 10",
"command.decrementNumberByTen": "减少 10"
}

View file

@ -7,6 +7,5 @@
"httpsRequired": "图像必须使用 HTTPS 协议。",
"svgsNotValid": "SVG 不是有效的图像源。",
"embeddedSvgsNotValid": "嵌入的 SVG 不是有效的图像源。",
"dataUrlsNotValid": "数据 URL 不是有效的图像源。",
"relativeUrlRequiresHttpsRepository": "相对图像 URL 需要存储库,其中 package.json 中包含 HTTPS 协议。"
"dataUrlsNotValid": "数据 URL 不是有效的图像源。"
}

View file

@ -6,6 +6,7 @@
{
"tag at": "{0} 处的 Tag",
"remote branch at": "{0} 处的远程分支",
"create branch": "$(plus) 创建新分支",
"repourl": "存储库 URL",
"parent": "父目录",
"cloning": "正在克隆 GIT 存储库...",

View file

@ -3,4 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Do not edit this file. It is machine generated.
{}
{
"preview.securityMessage.text": "已禁用此文档中的部分内容",
"preview.securityMessage.title": "已禁用此 Markdown 预览中的可能不安全的内容。更改 Markdown 预览安全设置以允许不安全内容或启用脚本。",
"preview.securityMessage.label": "已禁用内容安全警告"
}

View file

@ -4,5 +4,9 @@
*--------------------------------------------------------------------------------------------*/
// Do not edit this file. It is machine generated.
{
"preview.showPreviewSecuritySelector.currentSelection": "当前设置"
"preview.showPreviewSecuritySelector.strictTitle": "严格模式,仅载入安全内容。",
"preview.showPreviewSecuritySelector.currentSelection": "当前设置",
"preview.showPreviewSecuritySelector.insecureContentTitle": "允许通过 http 载入内容。",
"preview.showPreviewSecuritySelector.scriptsAndAllContent": "允许所有内容,执行所有脚本。不推荐。",
"preview.showPreviewSecuritySelector.title": "选择此工作区中 Markdown 预览的安全设置"
}

View file

@ -4,5 +4,6 @@
*--------------------------------------------------------------------------------------------*/
// Do not edit this file. It is machine generated.
{
"config.npm.autoDetect": "控制自动检测 npm 脚本是否打开。默认开启。"
"config.npm.autoDetect": "控制自动检测 npm 脚本是否打开。默认开启。",
"config.npm.runSilent": "使用 \"--silent\" 选项运行 npm 命令"
}

View file

@ -3,4 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Do not edit this file. It is machine generated.
{}
{
"buildAndWatchTscLabel": "监视 - {0}",
"buildTscLabel": "构建 - {0}"
}

View file

@ -5,7 +5,6 @@
// Do not edit this file. It is machine generated.
{
"useVSCodeVersionOption": "使用 VSCode 的版本",
"activeVersion": "当前处于活动状态",
"useWorkspaceVersionOption": "使用工作区版本",
"learnMore": "了解详细信息",
"selectTsVersion": "选择用于 JavaScript 和 TypeScript 语言功能的 TypeScript 版本"

View file

@ -10,7 +10,6 @@
"typescript.useCodeSnippetsOnMethodSuggest.dec": "完成函数的参数签名。",
"typescript.tsdk.desc": "指定包含要使用的 tsserver 和 lib*.d.ts 文件的文件夹路径。",
"typescript.disableAutomaticTypeAcquisition": "禁用自动获取类型。需要 TypeScript >= 2.0.6,并且更改后需要重启。",
"typescript.check.tscVersion": "检查全局安装的 TypeScript 编译器(例如 tsc )是否不同于使用的 TypeScript 语言服务。",
"typescript.tsserver.log": "将 TS 服务器的日志保存到一个文件。此日志可用于诊断 TS 服务器问题。日志可能包含你的项目中的文件路径、源代码和其他可能敏感的信息。",
"typescript.tsserver.trace": "对发送到 TS 服务器的消息启用跟踪。此跟踪信息可用于诊断 TS 服务器问题。 跟踪信息可能包含你的项目中的文件路径、源代码和其他可能敏感的信息。",
"typescript.validate.enable": "启用/禁用 TypeScript 验证。",

View file

@ -3,4 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Do not edit this file. It is machine generated.
{}
{
"authRequire": "需要验证代理",
"proxyauth": "{0} 代理需要验证。"
}

View file

@ -22,9 +22,11 @@
"miQuit": "退出 {0}",
"miNewFile": "新建文件(&&N)",
"miOpen": "打开(&&O)...",
"miOpenWorkspace": "打开工作区(&&O)...",
"miOpenFolder": "打开文件夹(&&F)...",
"miOpenFile": "打开文件(&&O)...",
"miOpenRecent": "打开最近的文件(&&R)",
"miSaveWorkspaceAs": "将工作区另存为(&&S)...",
"miAddFolderToWorkspace": "将文件夹添加到工作区(&&A)...",
"miSave": "保存(&&S)",
"miSaveAs": "另存为(&&A)...",
@ -32,6 +34,7 @@
"miAutoSave": "自动保存",
"miRevert": "还原文件(&&V)",
"miCloseWindow": "关闭窗口(&&W)",
"miCloseWorkspace": "关闭工作区(&&W)",
"miCloseFolder": "关闭文件夹(&&F)",
"miCloseEditor": "关闭编辑器(&&C)",
"miExit": "退出(&&X)",
@ -44,6 +47,7 @@
"miPreferences": "首选项(&&P)",
"miReopenClosedEditor": "重新打开已关闭的编辑器(&&R)",
"miMore": "更多(&&M)...",
"miClearRecentOpen": "清除最近打开记录",
"miUndo": "撤消(&&U)",
"miRedo": "恢复(&&R)",
"miCut": "剪切(&&T)",
@ -163,6 +167,7 @@
"miAbout": "关于(&&A)",
"miRunTask": "运行任务(&&R)...",
"miBuildTask": "运行生成任务(&&B)...",
"miRunningTask": "显示正在运行的任务(&&G)...",
"miRestartTask": "重启正在运行的任务(&&E)...",
"miTerminateTask": "终止任务(&&T)...",
"miConfigureTask": "配置任务(&&C)",
@ -174,5 +179,6 @@
"miDownloadingUpdate": "正在下载更新...",
"miInstallingUpdate": "正在安装更新...",
"miCheckForUpdates": "检查更新...",
"aboutDetail": "\n版本 {0}\n提交 {1}\n日期 {2}\nShell {3}\n渲染器 {4}\nNode {5}\n架构 {6}",
"okButton": "确定"
}

View file

@ -3,4 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Do not edit this file. It is machine generated.
{}
{
"diff.tooLarge": "文件过大,无法比较。"
}

View file

@ -50,6 +50,10 @@
"suggestOnTriggerCharacters": "控制键入触发器字符时是否应自动显示建议",
"acceptSuggestionOnEnter": "控制按“Enter”键是否像按“Tab”键一样接受建议。这能帮助避免“插入新行”和“接受建议”之间的歧义。值为“smart”时表示仅当文字改变时按“Enter”键才能接受建议",
"acceptSuggestionOnCommitCharacter": "控制是否应在遇到提交字符时接受建议。例如,在 JavaScript 中,分号(\";\")可以为提交字符,可接受建议并键入该字符。",
"snippetSuggestions.top": "在其他建议上方显示代码片段建议。",
"snippetSuggestions.bottom": "在其他建议下方显示代码片段建议。",
"snippetSuggestions.inline": "在其他建议中穿插显示代码片段建议。",
"snippetSuggestions.none": "不显示代码片段建议。",
"snippetSuggestions": "控制是否将代码段与其他建议一起显示以及它们的排序方式。",
"emptySelectionClipboard": "控制没有选择内容的复制是否复制当前行。",
"wordBasedSuggestions": "控制是否应根据文档中的字数计算完成。",

View file

@ -8,6 +8,7 @@
"lineHighlightBorderBox": "光标所在行四周边框的背景颜色。",
"rangeHighlight": "突出显示范围的背景颜色,例如 \"Quick Open\" 和“查找”功能。",
"caret": "编辑器光标颜色。",
"editorCursorBackground": "编辑器光标的背景色。可以自定义块型光标覆盖字符的颜色。",
"editorWhitespaces": "编辑器中空白字符颜色。",
"editorIndentGuides": "编辑器缩进参考线颜色。",
"editorLineNumbers": "编辑器行号颜色。",

View file

@ -15,5 +15,7 @@
"moveSelectionToNextFindMatch": "将上次选择移动到下一个查找匹配项",
"moveSelectionToPreviousFindMatch": "将上个选择内容移动到上一查找匹配项",
"selectAllOccurrencesOfFindMatch": "选择所有找到的查找匹配项",
"changeAll.label": "更改所有匹配项"
"changeAll.label": "更改所有匹配项",
"showNextFindTermAction": "显示下一个搜索结果",
"showPreviousFindTermAction": "显示上一个搜索结果"
}

View file

@ -6,7 +6,10 @@
{
"links.navigate.mac": "Cmd + 单击以跟踪链接",
"links.navigate": "Ctrl + 单击以跟踪链接",
"links.command.mac": "Cmd + 单击以执行命令",
"links.command": "Ctrl + 单击以执行命令",
"links.navigate.al": "Alt + 单击以访问链接",
"links.command.al": "Alt + 单击以执行命令",
"invalid.url": "抱歉,无法打开此链接,因为其格式不正确: {0}",
"missing.url": "抱歉,无法打开此链接,因为其目标丢失。",
"label": "打开链接"

View file

@ -6,5 +6,7 @@
{
"newWindow": "新建窗口",
"newWindowDesc": "打开一个新窗口",
"folderDesc": "{0} {1}"
"recentFolders": "最近使用的工作区",
"folderDesc": "{0} {1}",
"codeWorkspace": "代码工作区"
}

View file

@ -53,6 +53,9 @@
"badgeBackground": "Badge 背景色。Badge 是小型的信息标签,如表示搜索结果数量的标签。",
"badgeForeground": "Badge 前景色。Badge 是小型的信息标签,如表示搜索结果数量的标签。",
"scrollbarShadow": "表示视图被滚动的滚动条阴影。",
"scrollbarSliderBackground": "滚动条滑块背景色",
"scrollbarSliderHoverBackground": "滚动条滑块在悬停时的背景色",
"scrollbarSliderActiveBackground": "滚动条滑块被激活时的背景色",
"progressBarBackground": "表示长时间操作的进度条的背景色。",
"editorBackground": "编辑器背景颜色。",
"editorForeground": "编辑器默认前景色。",

View file

@ -6,9 +6,21 @@
{
"openFolder": "打开文件夹...",
"openFileFolder": "打开...",
"reload": "重新加载(&&R)",
"cancel": "取消",
"select": "选择(&&S)",
"selectWorkspace": "选择文件夹作为工作区",
"addSupported": "需要重载窗口以打开多个文件夹。",
"addFolderToWorkspace": "将文件夹添加到工作区...",
"add": "添加(&&A)",
"addFolderToWorkspaceTitle": "将文件夹添加到工作区",
"removeFolderFromWorkspace": "将文件夹从工作区删除",
"save": "保存(&&S)"
"saveWorkspaceAsAction": "将工作区另存为...",
"saveEmptyWorkspaceNotSupported": "请先打开一个工作区再保存。",
"saveNotSupported": "需要重载窗口以保存工作区。",
"save": "保存(&&S)",
"saveWorkspace": "保存工作区",
"openWorkspaceAction": "打开工作区...",
"newWorkspace": "新建工作区...",
"openWorkspaceConfigFile": "打开工作区配置文件"
}

View file

@ -36,6 +36,7 @@
"navigateNext": "前进",
"navigatePrevious": "后退",
"reopenClosedEditor": "重新打开已关闭的编辑器",
"clearRecentFiles": "清除最近打开",
"showEditorsInFirstGroup": "在第一组中显示编辑器",
"showEditorsInSecondGroup": "在第二组中显示编辑器",
"showEditorsInThirdGroup": "在第三组中显示编辑器",

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
// Do not edit this file. It is machine generated.
{
"compositePart.hideSideBarLabel": "隐藏侧边栏",
"focusSideBar": "聚焦信息侧边栏",
"viewCategory": "查看"
}

Some files were not shown because too many files have changed in this diff Show more