diff --git a/README.md b/README.md index 3a10b463101..df87a4eea0b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ a code editor with what developers need for their core edit-build-debug cycle. Code provides comprehensive editing and debugging support, an extensibility model, and lightweight integration with existing tools. -VS Code is updated monthly with new features and bug fixes. You can download it for Windows, Mac and Linux on [VS Code's website](https://code.visualstudio.com/Download). To get the latest releases everyday, you can install the [Insiders version of VS Code](https://code.visualstudio.com/insiders). This builds from the master branch and is updated at least daily. +VS Code is updated monthly with new features and bug fixes. You can download it for Windows, macOS, and Linux on [VS Code's website](https://code.visualstudio.com/Download). To get the latest releases everyday, you can install the [Insiders version of VS Code](https://code.visualstudio.com/insiders). This builds from the master branch and is updated at least daily.

VS Code in action diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 0cbd42b6e0b..68caf689ded 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -43,8 +43,8 @@ const nodeModules = ['electron', 'original-fs'] // Build const builtInExtensions = [ - { name: 'ms-vscode.node-debug', version: '1.16.2' }, - { name: 'ms-vscode.node-debug2', version: '1.16.0' } + { name: 'ms-vscode.node-debug', version: '1.16.5' }, + { name: 'ms-vscode.node-debug2', version: '1.16.3' } ]; const excludedExtensions = [ diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index 096f516afcf..9c2970d508b 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -5,16 +5,7 @@ declare module monaco { - interface Thenable { - /** - * Attaches callbacks for the resolution and/or rejection of the Promise. - * @param onfulfilled The callback to execute when the Promise is resolved. - * @param onrejected The callback to execute when the Promise is rejected. - * @returns A Promise for the completion of which ever callback is executed. - */ - then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Thenable; - then(onfulfilled?: (value: T) => TResult | Thenable, onrejected?: (reason: any) => void): Thenable; - } + type Thenable = PromiseLike; export interface IDisposable { dispose(): void; @@ -41,7 +32,7 @@ declare module monaco { Error = 3, } -#include(vs/base/common/winjs.base.d.ts): TValueCallback, ProgressCallback, TPromise +#include(vs/base/common/winjs.base.d.ts): TValueCallback, ProgressCallback, Promise #include(vs/base/common/cancellation): CancellationTokenSource, CancellationToken #include(vs/base/common/uri): URI #include(vs/editor/common/standalone/standaloneBase): KeyCode, KeyMod diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index 7b156212bec..95205aa1fe1 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -24,6 +24,7 @@ npmInstall('extensions'); // node modules shared by all extensions const extensions = [ 'vscode-api-tests', 'vscode-colorize-tests', + 'azure-account', 'json', 'configuration-editing', 'extension-editing', diff --git a/build/tfs/darwin/build.sh b/build/tfs/darwin/build.sh index 80676602f2e..acbb849d4b3 100755 --- a/build/tfs/darwin/build.sh +++ b/build/tfs/darwin/build.sh @@ -16,6 +16,9 @@ echo "machine monacotools.visualstudio.com password $VSO_PAT" > ~/.netrc step "Install dependencies" \ npm install +step "Hygiene" \ + npm run gulp -- hygiene + step "Mix in repository from vscode-distro" \ npm run gulp -- mixin @@ -23,7 +26,7 @@ step "Install distro dependencies" \ node build/tfs/common/installDistro.js step "Build minified & upload source maps" \ - npm run gulp -- --max_old_space_size=4096 vscode-darwin-min upload-vscode-sourcemaps + npm run gulp -- vscode-darwin-min upload-vscode-sourcemaps # step "Create loader snapshot" # node build/lib/snapshotLoader.js diff --git a/build/tfs/darwin/smoketest.sh b/build/tfs/darwin/smoketest.sh index e93de88e48a..ad1606c3aad 100755 --- a/build/tfs/darwin/smoketest.sh +++ b/build/tfs/darwin/smoketest.sh @@ -19,7 +19,7 @@ step "Install distro dependencies" \ node build/tfs/common/installDistro.js step "Build minified & upload source maps" \ - npm run gulp -- --max_old_space_size=4096 vscode-darwin-min + npm run gulp -- vscode-darwin-min step "Run smoke test" \ pushd test/smoke diff --git a/build/tfs/linux/build.sh b/build/tfs/linux/build.sh index 541130d2b50..b3d1825c2d9 100755 --- a/build/tfs/linux/build.sh +++ b/build/tfs/linux/build.sh @@ -18,6 +18,9 @@ echo "machine monacotools.visualstudio.com password $VSO_PAT" > ~/.netrc step "Install dependencies" \ npm install --arch=$ARCH --unsafe-perm +step "Hygiene" \ + npm run gulp -- hygiene + step "Mix in repository from vscode-distro" \ npm run gulp -- mixin @@ -28,7 +31,7 @@ step "Install distro dependencies" \ node build/tfs/common/installDistro.js --arch=$ARCH step "Build minified" \ - npm run gulp -- --max_old_space_size=4096 "vscode-linux-$ARCH-min" + npm run gulp -- "vscode-linux-$ARCH-min" # step "Create loader snapshot" # node build/lib/snapshotLoader.js --arch=$ARCH diff --git a/build/tfs/linux/release.sh b/build/tfs/linux/release.sh index 40d68aee73f..41f6d35e675 100755 --- a/build/tfs/linux/release.sh +++ b/build/tfs/linux/release.sh @@ -4,10 +4,10 @@ . ./build/tfs/common/common.sh step "Build Debian package" \ - npm run gulp -- --max_old_space_size=4096 "vscode-linux-$ARCH-build-deb" + npm run gulp -- "vscode-linux-$ARCH-build-deb" step "Build RPM package" \ - npm run gulp -- --max_old_space_size=4096 "vscode-linux-$ARCH-build-rpm" + npm run gulp -- "vscode-linux-$ARCH-build-rpm" (cd $BUILD_SOURCESDIRECTORY/build/tfs/common && \ step "Install build dependencies" \ diff --git a/build/tfs/linux/smoketest.sh b/build/tfs/linux/smoketest.sh index 579bac638b1..8d71fff1275 100644 --- a/build/tfs/linux/smoketest.sh +++ b/build/tfs/linux/smoketest.sh @@ -24,7 +24,7 @@ step "Install distro dependencies" \ node build/tfs/common/installDistro.js --arch=$ARCH step "Build minified" \ - npm run gulp -- --max_old_space_size=4096 "vscode-linux-$ARCH-min" + npm run gulp -- "vscode-linux-$ARCH-min" function configureEnvironment { id -u testuser &>/dev/null || (useradd -m testuser; chpasswd <<< testuser:testpassword) diff --git a/build/tfs/win32/1_build.ps1 b/build/tfs/win32/1_build.ps1 index c27b05caeaf..0090920d506 100644 --- a/build/tfs/win32/1_build.ps1 +++ b/build/tfs/win32/1_build.ps1 @@ -19,6 +19,10 @@ step "Install dependencies" { exec { & npm install } } +step "Hygiene" { + exec { & npm run gulp -- hygiene } +} + $env:VSCODE_MIXIN_PASSWORD = $mixinPassword step "Mix in repository from vscode-distro" { exec { & npm run gulp -- mixin } @@ -33,7 +37,7 @@ step "Install distro dependencies" { } step "Build minified" { - exec { & npm run gulp -- --max_old_space_size=4096 "vscode-win32-$global:arch-min" } + exec { & npm run gulp -- "vscode-win32-$global:arch-min" } } # step "Create loader snapshot" { diff --git a/build/tfs/win32/2_package.ps1 b/build/tfs/win32/2_package.ps1 index 4cb1978bbad..dcd90611374 100644 --- a/build/tfs/win32/2_package.ps1 +++ b/build/tfs/win32/2_package.ps1 @@ -6,7 +6,7 @@ Param( . .\build\tfs\win32\lib.ps1 step "Create archive and setup package" { - exec { & npm run gulp -- --max_old_space_size=4096 "vscode-win32-$global:arch-archive" "vscode-win32-$global:arch-setup" } + exec { & npm run gulp -- "vscode-win32-$global:arch-archive" "vscode-win32-$global:arch-setup" } } done \ No newline at end of file diff --git a/build/tfs/win32/smoketest.ps1 b/build/tfs/win32/smoketest.ps1 index 1817b806071..2a6519aaaf4 100644 --- a/build/tfs/win32/smoketest.ps1 +++ b/build/tfs/win32/smoketest.ps1 @@ -34,7 +34,7 @@ step "Install distro dependencies" { } step "Build minified" { - exec { & npm run gulp -- --max_old_space_size=4096 "vscode-win32-$global:arch-min" } + exec { & npm run gulp -- "vscode-win32-$global:arch-min" } } step "Run smoke test" { diff --git a/extensions/azure-account/.vscodeignore b/extensions/azure-account/.vscodeignore new file mode 100644 index 00000000000..24428a6f758 --- /dev/null +++ b/extensions/azure-account/.vscodeignore @@ -0,0 +1,4 @@ +test/** +src/** +tsconfig.json +npm-shrinkwrap.json \ No newline at end of file diff --git a/extensions/azure-account/npm-shrinkwrap.json b/extensions/azure-account/npm-shrinkwrap.json new file mode 100644 index 00000000000..18206b8124a --- /dev/null +++ b/extensions/azure-account/npm-shrinkwrap.json @@ -0,0 +1,521 @@ +{ + "name": "azure-account", + "version": "0.1.0", + "dependencies": { + "@types/copy-paste": { + "version": "1.1.30", + "from": "@types/copy-paste@>=1.1.30 <2.0.0", + "resolved": "https://registry.npmjs.org/@types/copy-paste/-/copy-paste-1.1.30.tgz", + "dev": true + }, + "@types/form-data": { + "version": "2.2.0", + "from": "@types/form-data@*", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.0.tgz", + "dependencies": { + "@types/node": { + "version": "8.0.24", + "from": "@types/node@*", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.24.tgz" + } + } + }, + "@types/node": { + "version": "6.0.87", + "from": "@types/node@>=6.0.40 <7.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.87.tgz", + "dev": true + }, + "@types/opn": { + "version": "3.0.28", + "from": "@types/opn@>=3.0.28 <4.0.0", + "resolved": "https://registry.npmjs.org/@types/opn/-/opn-3.0.28.tgz", + "dev": true + }, + "@types/request": { + "version": "0.0.45", + "from": "@types/request@>=0.0.45 <0.0.46", + "resolved": "https://registry.npmjs.org/@types/request/-/request-0.0.45.tgz", + "dependencies": { + "@types/node": { + "version": "8.0.24", + "from": "@types/node@*", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.24.tgz" + } + } + }, + "@types/uuid": { + "version": "2.0.30", + "from": "@types/uuid@>=2.0.29 <3.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-2.0.30.tgz", + "dependencies": { + "@types/node": { + "version": "8.0.24", + "from": "@types/node@*", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.24.tgz" + } + } + }, + "adal-node": { + "version": "0.1.22", + "from": "adal-node@>=0.1.22 <0.2.0", + "resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.1.22.tgz" + }, + "ajv": { + "version": "4.11.8", + "from": "ajv@>=4.9.1 <5.0.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz" + }, + "asn1": { + "version": "0.2.3", + "from": "asn1@>=0.2.3 <0.3.0", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" + }, + "assert-plus": { + "version": "0.2.0", + "from": "assert-plus@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz" + }, + "async": { + "version": "2.5.0", + "from": "async@>=0.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz" + }, + "asynckit": { + "version": "0.4.0", + "from": "asynckit@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + }, + "aws-sign2": { + "version": "0.6.0", + "from": "aws-sign2@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz" + }, + "aws4": { + "version": "1.6.0", + "from": "aws4@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz" + }, + "azure-arm-resource": { + "version": "2.0.0-preview", + "from": "azure-arm-resource@>=2.0.0-preview <3.0.0", + "resolved": "https://registry.npmjs.org/azure-arm-resource/-/azure-arm-resource-2.0.0-preview.tgz" + }, + "base64url": { + "version": "2.0.0", + "from": "base64url@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz" + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "from": "bcrypt-pbkdf@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "optional": true + }, + "boom": { + "version": "2.10.1", + "from": "boom@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "from": "buffer-equal-constant-time@1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz" + }, + "caseless": { + "version": "0.12.0", + "from": "caseless@>=0.12.0 <0.13.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" + }, + "co": { + "version": "4.6.0", + "from": "co@>=4.6.0 <5.0.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz" + }, + "combined-stream": { + "version": "1.0.5", + "from": "combined-stream@>=1.0.5 <1.1.0", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz" + }, + "copy-paste": { + "version": "1.3.0", + "from": "copy-paste@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/copy-paste/-/copy-paste-1.3.0.tgz" + }, + "core-util-is": { + "version": "1.0.2", + "from": "core-util-is@1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" + }, + "cryptiles": { + "version": "2.0.5", + "from": "cryptiles@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" + }, + "dashdash": { + "version": "1.14.1", + "from": "dashdash@>=1.12.0 <2.0.0", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, + "date-utils": { + "version": "1.2.21", + "from": "date-utils@*", + "resolved": "https://registry.npmjs.org/date-utils/-/date-utils-1.2.21.tgz" + }, + "delayed-stream": { + "version": "1.0.0", + "from": "delayed-stream@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + }, + "duplexer": { + "version": "0.1.1", + "from": "duplexer@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz" + }, + "ecc-jsbn": { + "version": "0.1.1", + "from": "ecc-jsbn@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "optional": true + }, + "ecdsa-sig-formatter": { + "version": "1.0.9", + "from": "ecdsa-sig-formatter@1.0.9", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz" + }, + "extend": { + "version": "3.0.1", + "from": "extend@>=3.0.0 <3.1.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz" + }, + "extsprintf": { + "version": "1.3.0", + "from": "extsprintf@1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" + }, + "forever-agent": { + "version": "0.6.1", + "from": "forever-agent@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" + }, + "form-data": { + "version": "2.1.4", + "from": "form-data@>=2.1.1 <2.2.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz" + }, + "getpass": { + "version": "0.1.7", + "from": "getpass@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, + "har-schema": { + "version": "1.0.5", + "from": "har-schema@>=1.0.5 <2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz" + }, + "har-validator": { + "version": "4.2.1", + "from": "har-validator@>=4.2.1 <4.3.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz" + }, + "hawk": { + "version": "3.1.3", + "from": "hawk@>=3.1.3 <3.2.0", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz" + }, + "hoek": { + "version": "2.16.3", + "from": "hoek@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + }, + "http-signature": { + "version": "1.1.1", + "from": "http-signature@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz" + }, + "iconv-lite": { + "version": "0.4.18", + "from": "iconv-lite@>=0.4.8 <0.5.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz" + }, + "is-buffer": { + "version": "1.1.5", + "from": "is-buffer@>=1.1.5 <2.0.0", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz" + }, + "is-stream": { + "version": "1.1.0", + "from": "is-stream@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" + }, + "is-typedarray": { + "version": "1.0.0", + "from": "is-typedarray@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" + }, + "is-wsl": { + "version": "1.1.0", + "from": "is-wsl@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz" + }, + "isstream": { + "version": "0.1.2", + "from": "isstream@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + }, + "jsbn": { + "version": "0.1.1", + "from": "jsbn@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "from": "json-schema@0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz" + }, + "json-stable-stringify": { + "version": "1.0.1", + "from": "json-stable-stringify@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz" + }, + "json-stringify-safe": { + "version": "5.0.1", + "from": "json-stringify-safe@>=5.0.1 <5.1.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" + }, + "jsonify": { + "version": "0.0.0", + "from": "jsonify@>=0.0.0 <0.1.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" + }, + "jsprim": { + "version": "1.4.1", + "from": "jsprim@>=1.2.2 <2.0.0", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, + "jwa": { + "version": "1.1.5", + "from": "jwa@>=1.1.4 <2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz" + }, + "jws": { + "version": "3.1.4", + "from": "jws@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz" + }, + "lodash": { + "version": "4.17.4", + "from": "lodash@>=4.14.0 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + }, + "mime-db": { + "version": "1.29.0", + "from": "mime-db@>=1.29.0 <1.30.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz" + }, + "mime-types": { + "version": "2.1.16", + "from": "mime-types@>=2.1.7 <2.2.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.16.tgz" + }, + "moment": { + "version": "2.18.1", + "from": "moment@>=2.14.1 <3.0.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz" + }, + "ms-rest": { + "version": "2.2.1", + "from": "ms-rest@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/ms-rest/-/ms-rest-2.2.1.tgz", + "dependencies": { + "@types/node": { + "version": "7.0.42", + "from": "@types/node@>=7.0.10 <8.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.42.tgz" + }, + "uuid": { + "version": "3.1.0", + "from": "uuid@^3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz" + } + } + }, + "ms-rest-azure": { + "version": "2.2.3", + "from": "ms-rest-azure@>=2.2.3 <3.0.0", + "resolved": "https://registry.npmjs.org/ms-rest-azure/-/ms-rest-azure-2.2.3.tgz", + "dependencies": { + "@types/node": { + "version": "7.0.42", + "from": "@types/node@^7.0.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.42.tgz" + }, + "async": { + "version": "0.2.7", + "from": "async@0.2.7", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.7.tgz" + }, + "uuid": { + "version": "3.1.0", + "from": "uuid@^3.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz" + } + } + }, + "node-uuid": { + "version": "1.4.7", + "from": "node-uuid@1.4.7", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz" + }, + "oauth-sign": { + "version": "0.8.2", + "from": "oauth-sign@>=0.8.1 <0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz" + }, + "opn": { + "version": "5.1.0", + "from": "opn@>=5.1.0 <6.0.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.1.0.tgz" + }, + "performance-now": { + "version": "0.2.0", + "from": "performance-now@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz" + }, + "punycode": { + "version": "1.4.1", + "from": "punycode@>=1.4.1 <2.0.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz" + }, + "qs": { + "version": "6.4.0", + "from": "qs@>=6.4.0 <6.5.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz" + }, + "request": { + "version": "2.81.0", + "from": "request@>=2.52.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", + "dependencies": { + "uuid": { + "version": "3.1.0", + "from": "uuid@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz" + } + } + }, + "safe-buffer": { + "version": "5.1.1", + "from": "safe-buffer@>=5.0.1 <6.0.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" + }, + "sntp": { + "version": "1.0.9", + "from": "sntp@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" + }, + "sshpk": { + "version": "1.13.1", + "from": "sshpk@>=1.7.0 <2.0.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, + "stringstream": { + "version": "0.0.5", + "from": "stringstream@>=0.0.4 <0.1.0", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz" + }, + "sync-exec": { + "version": "0.6.2", + "from": "sync-exec@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/sync-exec/-/sync-exec-0.6.2.tgz", + "optional": true + }, + "through": { + "version": "2.3.8", + "from": "through@>=2.3.4 <2.4.0", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + }, + "tough-cookie": { + "version": "2.3.2", + "from": "tough-cookie@>=2.3.0 <2.4.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz" + }, + "tunnel": { + "version": "0.0.5", + "from": "tunnel@>=0.0.2 <0.1.0", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.5.tgz" + }, + "tunnel-agent": { + "version": "0.6.0", + "from": "tunnel-agent@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" + }, + "tweetnacl": { + "version": "0.14.5", + "from": "tweetnacl@>=0.14.0 <0.15.0", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "optional": true + }, + "underscore": { + "version": "1.8.3", + "from": "underscore@>=1.3.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz" + }, + "verror": { + "version": "1.10.0", + "from": "verror@1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "from": "assert-plus@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" + } + } + }, + "vscode-nls": { + "version": "2.0.2", + "from": "vscode-nls@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-2.0.2.tgz" + }, + "xmldom": { + "version": "0.1.27", + "from": "xmldom@>=0.1.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz" + }, + "xpath.js": { + "version": "1.0.7", + "from": "xpath.js@>=1.0.5 <1.1.0", + "resolved": "https://registry.npmjs.org/xpath.js/-/xpath.js-1.0.7.tgz" + } + } +} diff --git a/extensions/azure-account/package.json b/extensions/azure-account/package.json new file mode 100644 index 00000000000..d98aa059e74 --- /dev/null +++ b/extensions/azure-account/package.json @@ -0,0 +1,70 @@ +{ + "name": "azure-account", + "version": "0.1.0", + "publisher": "vscode", + "engines": { + "vscode": "*" + }, + "enableProposedApi": true, + "activationEvents": [ + "*" + ], + "main": "./out/extension", + "contributes": { + "commands": [ + { + "command": "azure-account.login", + "title": "%azure-account.commands.login%", + "category": "%azure-account.commands.azure%" + }, + { + "command": "azure-account.logout", + "title": "%azure-account.commands.logout%", + "category": "%azure-account.commands.azure%" + }, + { + "command": "azure-account.addFilter", + "title": "%azure-account.commands.addResourceFilter%", + "category": "%azure-account.commands.azure%" + }, + { + "command": "azure-account.removeFilter", + "title": "%azure-account.commands.removeResourceFilter%", + "category": "%azure-account.commands.azure%" + }, + { + "command": "azure-account.createAccount", + "title": "%azure-account.commands.createAccount%", + "category": "%azure-account.commands.azure%" + } + ], + "configuration": { + "type": "object", + "title": "Azure configuration", + "properties": { + "azure.resourceFilter": { + "type": "array", + "default": null, + "description": "The resource filter, each element is either a subscription id or a subscription id and a resource group name separated by a slash." + } + } + } + }, + "scripts": { + "compile": "gulp compile-extension:azure-account", + "watch": "gulp watch-extension:azure-account" + }, + "devDependencies": { + "@types/copy-paste": "^1.1.30", + "@types/node": "^6.0.40", + "@types/opn": "^3.0.28" + }, + "dependencies": { + "adal-node": "^0.1.22", + "azure-arm-resource": "^2.0.0-preview", + "copy-paste": "^1.3.0", + "ms-rest-azure": "^2.2.3", + "vscode-nls": "^2.0.2", + "opn": "^5.1.0" + } +} \ No newline at end of file diff --git a/extensions/azure-account/package.nls.json b/extensions/azure-account/package.nls.json new file mode 100644 index 00000000000..4857a376665 --- /dev/null +++ b/extensions/azure-account/package.nls.json @@ -0,0 +1,8 @@ +{ + "azure-account.commands.azure": "Azure", + "azure-account.commands.login": "Login", + "azure-account.commands.logout": "Logout", + "azure-account.commands.addResourceFilter": "Add Resource Filter", + "azure-account.commands.removeResourceFilter": "Remove Resource Filter", + "azure-account.commands.createAccount": "Create an Account" +} \ No newline at end of file diff --git a/extensions/azure-account/src/azure-account.ts b/extensions/azure-account/src/azure-account.ts new file mode 100644 index 00000000000..d5b4b3d9ed1 --- /dev/null +++ b/extensions/azure-account/src/azure-account.ts @@ -0,0 +1,472 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const adal = require('adal-node'); +const MemoryCache = adal.MemoryCache; +const AuthenticationContext = adal.AuthenticationContext; +const CacheDriver = require('adal-node/lib/cache-driver'); +const createLogContext = require('adal-node/lib/log').createLogContext; + +import { DeviceTokenCredentials, AzureEnvironment } from 'ms-rest-azure'; +import { SubscriptionClient, ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource'; +import * as opn from 'opn'; +import * as copypaste from 'copy-paste'; +import * as nls from 'vscode-nls'; + +import { window, commands, credentials, EventEmitter, MessageItem, ExtensionContext, workspace, ConfigurationTarget } from 'vscode'; +import { AzureAccount, AzureSession, AzureLoginStatus, AzureResourceFilter } from './typings/azure-account.api'; + +const localize = nls.loadMessageBundle(); + +const defaultEnvironment = (AzureEnvironment).Azure; +const commonTenantId = 'common'; +const authorityHostUrl = defaultEnvironment.activeDirectoryEndpointUrl; +const clientId = '04b07795-8ddb-461a-bbee-02f9e1bf7b46'; +const authorityUrl = `${authorityHostUrl}${commonTenantId}`; +const resource = defaultEnvironment.activeDirectoryResourceId; + +const credentialsService = 'VSCode Public Azure'; +const credentialsAccount = 'Refresh Token'; + +interface DeviceLogin { + userCode: string; + deviceCode: string; + verificationUrl: string; + expiresIn: number; + interval: number; + message: string; +} + +interface TokenResponse { + tokenType: string; + expiresIn: number; + expiresOn: string; + resource: string; + accessToken: string; + refreshToken: string; + userId: string; + isUserIdDisplayable: boolean; + familyName: string; + givenName: string; + oid: string; + tenantId: string; + isMRRT: boolean; + _clientId: string; + _authority: string; +} + +interface AzureAccountWriteable extends AzureAccount { + status: AzureLoginStatus; +} + +class AzureLoginError extends Error { + constructor(message: string, public _reason: any) { + super(message); + } +} + +export class AzureLoginHelper { + + private onStatusChanged = new EventEmitter(); + private onSessionsChanged = new EventEmitter(); + private onFiltersChanged = new EventEmitter(); + private tokenCache = new MemoryCache(); + private oldResourceFilter: string; + + constructor(context: ExtensionContext) { + const subscriptions = context.subscriptions; + subscriptions.push(commands.registerCommand('azure-account.login', () => this.login().catch(console.error))); + subscriptions.push(commands.registerCommand('azure-account.logout', () => this.logout().catch(console.error))); + subscriptions.push(commands.registerCommand('azure-account.askForLogin', () => this.askForLogin().catch(console.error))); + subscriptions.push(commands.registerCommand('azure-account.addFilter', () => this.addFilter().catch(console.error))); + subscriptions.push(commands.registerCommand('azure-account.removeFilter', () => this.removeFilter().catch(console.error))); + subscriptions.push(this.api.onSessionsChanged(() => this.updateFilters().catch(console.error))); + subscriptions.push(workspace.onDidChangeConfiguration(() => this.updateFilters(true).catch(console.error))); + this.initialize() + .catch(console.error); + } + + api: AzureAccount = { + status: 'Initializing', + onStatusChanged: this.onStatusChanged.event, + sessions: [], + onSessionsChanged: this.onSessionsChanged.event, + filters: [], + onFiltersChanged: this.onFiltersChanged.event, + credentials + }; + + async login() { + try { + this.beginLoggingIn(); + const deviceLogin = await deviceLogin1(); + const copyAndOpen: MessageItem = { title: localize('azure-account.copyAndOpen', "Copy & Open") }; + const close: MessageItem = { title: localize('azure-account.close', "Close"), isCloseAffordance: true }; + const response = await window.showInformationMessage(deviceLogin.message, copyAndOpen, close); + if (response === copyAndOpen) { + copypaste.copy(deviceLogin.userCode); + opn(deviceLogin.verificationUrl); + } + const tokenResponse = await deviceLogin2(deviceLogin); + const refreshToken = tokenResponse.refreshToken; + const tokenResponses = await tokensFromToken(tokenResponse); + await credentials.writeSecret(credentialsService, credentialsAccount, refreshToken); + await this.updateSessions(tokenResponses); + } finally { + this.updateStatus(); + } + } + + async logout() { + await credentials.deleteSecret(credentialsService, credentialsAccount); + await this.updateSessions([]); + this.updateStatus(); + } + + private async initialize() { + try { + const refreshToken = await credentials.readSecret(credentialsService, credentialsAccount); + if (refreshToken) { + this.beginLoggingIn(); + const tokenResponse = await tokenFromRefreshToken(refreshToken); + const tokenResponses = await tokensFromToken(tokenResponse); + await this.updateSessions(tokenResponses); + } + } catch (err) { + if (!(err instanceof AzureLoginError)) { + throw err; + } + } finally { + this.updateStatus(); + } + } + + private beginLoggingIn() { + if (this.api.status !== 'LoggedIn') { + (this.api).status = 'LoggingIn'; + this.onStatusChanged.fire(this.api.status); + } + } + + private updateStatus() { + const status = this.api.sessions.length ? 'LoggedIn' : 'LoggedOut'; + if (this.api.status !== status) { + (this.api).status = status; + this.onStatusChanged.fire(this.api.status); + } + } + + private async updateSessions(tokenResponses: TokenResponse[]) { + await clearTokenCache(this.tokenCache); + for (const tokenResponse of tokenResponses) { + await addTokenToCache(this.tokenCache, tokenResponse); + } + const sessions = this.api.sessions; + sessions.splice(0, sessions.length, ...tokenResponses.map(tokenResponse => ({ + environment: defaultEnvironment, + userId: tokenResponse.userId, + tenantId: tokenResponse.tenantId, + credentials: new DeviceTokenCredentials({ username: tokenResponse.userId, clientId, tokenCache: this.tokenCache, domain: tokenResponse.tenantId }) + }))); + this.onSessionsChanged.fire(); + } + + private async askForLogin() { + if (this.api.status === 'LoggedIn') { + return; + } + const login = { title: localize('azure-account.login', "Login") }; + const cancel = { title: 'Cancel', isCloseAffordance: true }; + const result = await window.showInformationMessage(localize('azure-account.loginFirst', "Not logged in, log in first."), login, cancel); + return result === login && commands.executeCommand('azure-account.login'); + } + + private async addFilter() { + if (this.api.status !== 'LoggedIn') { + return commands.executeCommand('azure-account.askForLogin'); + } + + const azureConfig = workspace.getConfiguration('azure'); + const resourceFilter = azureConfig.get('resourceFilter') || []; + + const subscriptionItems: { session: AzureSession; subscription: SubscriptionModels.Subscription }[] = []; + for (const session of this.api.sessions) { + const credentials = session.credentials; + const client = new SubscriptionClient(credentials); + const subscriptions = await listAll(client.subscriptions, client.subscriptions.list()); + subscriptionItems.push(...subscriptions.filter(subscription => resourceFilter.indexOf(`${session.tenantId}/${subscription.subscriptionId}`) === -1) + .map(subscription => ({ + session, + subscription + }))); + } + subscriptionItems.sort((a, b) => a.subscription.displayName!.localeCompare(b.subscription.displayName!)); + const subscriptionResult = await window.showQuickPick(subscriptionItems.map(subscription => ({ + label: subscription.subscription.displayName!, + description: subscription.subscription.subscriptionId!, + subscription + }))); + if (!subscriptionResult) { + return; + } + + const { session, subscription } = subscriptionResult.subscription; + const client = new ResourceManagementClient(session.credentials, subscription.subscriptionId!); + const resourceGroups = await listAll(client.resourceGroups, client.resourceGroups.list()); + const resourceGroupFilters: AzureResourceFilter[] = [ + { + ...subscriptionResult.subscription, + allResourceGroups: true, + resourceGroups + } + ]; + resourceGroupFilters.push(...resourceGroups.filter(resourceGroup => resourceFilter.indexOf(`${session.tenantId}/${subscription.subscriptionId}/${resourceGroup.name}`) === -1) + .map(resourceGroup => ({ + session, + subscription, + allResourceGroups: false, + resourceGroups: [resourceGroup] + }))); + resourceGroupFilters.sort((a, b) => (!a.allResourceGroups ? a.resourceGroups[0].name! : '').localeCompare(!b.allResourceGroups ? b.resourceGroups[0].name! : '')); + const resourceGroupResult = await window.showQuickPick(resourceGroupFilters.map(resourceGroup => (!resourceGroup.allResourceGroups ? { + label: resourceGroup.resourceGroups[0].name!, + description: resourceGroup.resourceGroups[0].location, + resourceGroup + } : { + label: localize('azure-account.entireSubscription', "Entire Subscription"), + description: '', + resourceGroup + }))); + if (!resourceGroupResult) { + return; + } + + const resourceGroup = resourceGroupResult.resourceGroup; + if (!resourceGroup.allResourceGroups) { + resourceFilter.push(`${resourceGroup.session.tenantId}/${resourceGroup.subscription.subscriptionId}/${resourceGroup.resourceGroups[0].name}`); + } else { + resourceFilter.splice(0, resourceFilter.length, ...resourceFilter.filter(c => !c.startsWith(`${resourceGroup.session.tenantId}/${resourceGroup.subscription.subscriptionId}/`))); + resourceFilter.push(`${resourceGroup.session.tenantId}/${resourceGroup.subscription.subscriptionId}`); + } + + const resourceFilterConfig = azureConfig.inspect('resourceFilter'); + let target = ConfigurationTarget.Global; + if (resourceFilterConfig) { + if (resourceFilterConfig.workspaceFolderValue) { + target = ConfigurationTarget.WorkspaceFolder; + } else if (resourceFilterConfig.workspaceValue) { + target = ConfigurationTarget.Workspace; + } else if (resourceFilterConfig.globalValue) { + target = ConfigurationTarget.Global; + } + } + await azureConfig.update('resourceFilter', resourceFilter, target); + } + + private async removeFilter() { + if (this.api.status !== 'LoggedIn') { + return commands.executeCommand('azure-account.askForLogin'); + } + + const azureConfig = workspace.getConfiguration('azure'); + let resourceFilter = azureConfig.get('resourceFilter') || []; + + const filters = resourceFilter.length ? this.api.filters.reduce((list, filter) => { + if (filter.allResourceGroups) { + list.push(filter); + } else { + list.push(...filter.resourceGroups.map(resourceGroup => ({ + ...filter, + resourceGroups: [resourceGroup] + }))); + } + return list; + }, []) : []; + filters.sort((a, b) => (!a.allResourceGroups ? a.resourceGroups[0].name! : `/${a.subscription.displayName}`).localeCompare(!b.allResourceGroups ? b.resourceGroups[0].name! : `/${b.subscription.displayName}`)); + const filterResult = await window.showQuickPick(filters.map(filter => (!filter.allResourceGroups ? { + label: filter.resourceGroups[0].name!, + description: filter.subscription.displayName!, + filter + } : { + label: filter.subscription.displayName!, + description: filter.subscription.subscriptionId!, + filter + }))); + if (!filterResult) { + return; + } + + const filter = filterResult.filter; + const remove = !filter.allResourceGroups ? + `${filter.session.tenantId}/${filter.subscription.subscriptionId}/${filter.resourceGroups[0].name}` : + `${filter.session.tenantId}/${filter.subscription.subscriptionId}`; + resourceFilter = resourceFilter.filter(e => e !== remove); + + const resourceFilterConfig = azureConfig.inspect('resourceFilter'); + let target = ConfigurationTarget.Global; + if (resourceFilterConfig) { + if (resourceFilterConfig.workspaceFolderValue) { + target = ConfigurationTarget.WorkspaceFolder; + } else if (resourceFilterConfig.workspaceValue) { + target = ConfigurationTarget.Workspace; + } else if (resourceFilterConfig.globalValue) { + target = ConfigurationTarget.Global; + } + } + await azureConfig.update('resourceFilter', resourceFilter.length ? resourceFilter : undefined, target); + } + + private async updateFilters(configChange = false) { + const azureConfig = workspace.getConfiguration('azure'); + let resourceFilter = azureConfig.get('resourceFilter'); + if (configChange && JSON.stringify(resourceFilter) === this.oldResourceFilter) { + return; + } + this.oldResourceFilter = JSON.stringify(resourceFilter); + if (resourceFilter && !Array.isArray(resourceFilter)) { + resourceFilter = []; + } + const filters = resourceFilter && resourceFilter.map(s => typeof s === 'string' ? s.split('/') : []) + .filter(s => s.length === 2 || s.length === 3) + .map(([tenantId, subscriptionId, resourceGroup]) => ({ tenantId, subscriptionId, resourceGroup })); + const tenantIds = filters && filters.reduce | boolean>>>((result, filter) => { + const tenant = result[filter.tenantId] || (result[filter.tenantId] = {}); + const resourceGroups = tenant[filter.subscriptionId] || (tenant[filter.subscriptionId] = (filter.resourceGroup ? {} : true)); + if (typeof resourceGroups === 'object' && filter.resourceGroup) { + resourceGroups[filter.resourceGroup] = true; + } + return result; + }, {}); + + const newFilters: AzureResourceFilter[] = []; + const sessions = tenantIds ? this.api.sessions.filter(session => tenantIds[session.tenantId]) : this.api.sessions; + for (const session of sessions) { + const client = new SubscriptionClient(session.credentials); + const subscriptionIds = tenantIds && tenantIds[session.tenantId]; + const subscriptions = await listAll(client.subscriptions, client.subscriptions.list()); + const filteredSubscriptions = subscriptionIds ? subscriptions.filter(subscription => subscriptionIds[subscription.subscriptionId!]) : subscriptions; + for (const subscription of filteredSubscriptions) { + const client = new ResourceManagementClient(session.credentials, subscription.subscriptionId!); + const resourceGroupNames = subscriptionIds && subscriptionIds[subscription.subscriptionId!]; + const allResourceGroups = !(resourceGroupNames && typeof resourceGroupNames === 'object'); + const unfilteredResourceGroups = await listAll(client.resourceGroups, client.resourceGroups.list()); + const resourceGroups = allResourceGroups ? unfilteredResourceGroups : unfilteredResourceGroups.filter(resourceGroup => (>resourceGroupNames!)[resourceGroup.name!]); + newFilters.push({ session, subscription, allResourceGroups, resourceGroups }); + } + } + this.api.filters.splice(0, this.api.filters.length, ...newFilters); + this.onFiltersChanged.fire(); + } +} + +async function deviceLogin1(): Promise { + return new Promise((resolve, reject) => { + const cache = new MemoryCache(); + const context = new AuthenticationContext(authorityUrl, null, cache); + context.acquireUserCode(resource, clientId, 'en-us', function (err: any, response: any) { + if (err) { + reject(new AzureLoginError(localize('azure-account.userCodeFailed', "Aquiring user code failed"), err)); + } else { + resolve(response); + } + }); + }); +} + +async function deviceLogin2(deviceLogin: DeviceLogin) { + return new Promise((resolve, reject) => { + const tokenCache = new MemoryCache(); + const context = new AuthenticationContext(authorityUrl, null, tokenCache); + context.acquireTokenWithDeviceCode(resource, clientId, deviceLogin, function (err: any, tokenResponse: TokenResponse) { + if (err) { + reject(new AzureLoginError(localize('azure-account.tokenFailed', "Aquiring token with device code"), err)); + } else { + resolve(tokenResponse); + } + }); + }); +} + +async function tokenFromRefreshToken(refreshToken: string, tenantId = commonTenantId) { + return new Promise((resolve, reject) => { + const tokenCache = new MemoryCache(); + const context = new AuthenticationContext(`${authorityHostUrl}${tenantId}`, null, tokenCache); + context.acquireTokenWithRefreshToken(refreshToken, clientId, null, function (err: any, tokenResponse: TokenResponse) { + if (err) { + reject(new AzureLoginError(localize('azure-account.tokenFromRefreshTokenFailed', "Aquiring token with refresh token"), err)); + } else { + resolve(tokenResponse); + } + }); + }); +} + +async function tokensFromToken(firstTokenResponse: TokenResponse) { + const tokenResponses = [firstTokenResponse]; + const tokenCache = new MemoryCache(); + await addTokenToCache(tokenCache, firstTokenResponse); + const credentials = new DeviceTokenCredentials({ username: firstTokenResponse.userId, clientId, tokenCache }); + const client = new SubscriptionClient(credentials); + const tenants = await listAll(client.tenants, client.tenants.list()); + for (const tenant of tenants) { + if (tenant.tenantId !== firstTokenResponse.tenantId) { + const tokenResponse = await tokenFromRefreshToken(firstTokenResponse.refreshToken, tenant.tenantId); + tokenResponses.push(tokenResponse); + } + } + return tokenResponses; +} + +async function addTokenToCache(tokenCache: any, tokenResponse: TokenResponse) { + return new Promise((resolve, reject) => { + const driver = new CacheDriver( + { _logContext: createLogContext('') }, + `${authorityHostUrl}${tokenResponse.tenantId}`, + tokenResponse.resource, + clientId, + tokenCache, + (entry: any, resource: any, callback: (err: any, response: any) => {}) => { + callback(null, entry); + } + ); + driver.add(tokenResponse, function (err: any) { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); +} + +async function clearTokenCache(tokenCache: any) { + await new Promise((resolve, reject) => { + tokenCache.find({}, (err: any, entries: any[]) => { + if (err) { + reject(err); + } else { + tokenCache.remove(entries, (err: any) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + } + }); + }); +} + +export interface PartialList extends Array { + nextLink?: string; +} + +export async function listAll(client: { listNext(nextPageLink: string): Promise>; }, first: Promise>): Promise { + const all: T[] = []; + for (let list = await first; list.length || list.nextLink; list = list.nextLink ? await client.listNext(list.nextLink) : []) { + all.push(...list); + } + return all; +} \ No newline at end of file diff --git a/extensions/azure-account/src/extension.ts b/extensions/azure-account/src/extension.ts new file mode 100644 index 00000000000..df46a761df1 --- /dev/null +++ b/extensions/azure-account/src/extension.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { window, ExtensionContext, commands, credentials } from 'vscode'; +import { AzureLoginHelper } from './azure-account'; +import { AzureAccount } from './typings/azure-account.api'; +import * as opn from 'opn'; +import * as nls from 'vscode-nls'; + +const localize = nls.loadMessageBundle(); + +export function activate(context: ExtensionContext) { + if (!credentials) { + return; // Proposed API not available. + } + const azureLogin = new AzureLoginHelper(context); + const subscriptions = context.subscriptions; + subscriptions.push(createStatusBarItem(azureLogin.api)); + subscriptions.push(commands.registerCommand('azure-account.createAccount', createAccount)); + return azureLogin.api; +} + +function createAccount() { + opn('https://azure.microsoft.com/en-us/free'); +} + +function createStatusBarItem(api: AzureAccount) { + const statusBarItem = window.createStatusBarItem(); + function updateStatusBar() { + switch (api.status) { + case 'LoggingIn': + statusBarItem.text = localize('azure-account.loggingIn', "Azure: Logging in..."); + statusBarItem.show(); + break; + case 'LoggedIn': + statusBarItem.text = localize('azure-account.loggedIn', "Azure: {0}", api.sessions[0].userId); + statusBarItem.show(); + break; + default: + statusBarItem.hide(); + break; + } + } + api.onStatusChanged(updateStatusBar); + api.onSessionsChanged(updateStatusBar); + updateStatusBar(); + return statusBarItem; +} + +export function deactivate() { +} \ No newline at end of file diff --git a/extensions/azure-account/src/typings/azure-account.api.d.ts b/extensions/azure-account/src/typings/azure-account.api.d.ts new file mode 100644 index 00000000000..089511dda45 --- /dev/null +++ b/extensions/azure-account/src/typings/azure-account.api.d.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vscode'; +import { ServiceClientCredentials } from 'ms-rest'; +import { AzureEnvironment } from 'ms-rest-azure'; +import { SubscriptionModels, ResourceModels } from 'azure-arm-resource'; + +export type AzureLoginStatus = 'Initializing' | 'LoggingIn' | 'LoggedIn' | 'LoggedOut'; + +export interface AzureAccount { + readonly status: AzureLoginStatus; + readonly onStatusChanged: Event; + readonly sessions: AzureSession[]; + readonly onSessionsChanged: Event; + readonly filters: AzureResourceFilter[]; + readonly onFiltersChanged: Event; + readonly credentials: Credentials; +} + +export interface AzureSession { + readonly environment: AzureEnvironment; + readonly userId: string; + readonly tenantId: string; + readonly credentials: ServiceClientCredentials; +} + +export interface AzureResourceFilter { + readonly session: AzureSession; + readonly subscription: SubscriptionModels.Subscription; + readonly allResourceGroups: boolean; + readonly resourceGroups: ResourceModels.ResourceGroup[]; +} + +export interface Credentials { + readSecret(service: string, account: string): Thenable; + writeSecret(service: string, account: string, secret: string): Thenable; + deleteSecret(service: string, account: string): Thenable; +} diff --git a/extensions/azure-account/src/typings/ref.d.ts b/extensions/azure-account/src/typings/ref.d.ts new file mode 100644 index 00000000000..216911a680e --- /dev/null +++ b/extensions/azure-account/src/typings/ref.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/// diff --git a/extensions/azure-account/src/typings/vscode.rejected.d.ts b/extensions/azure-account/src/typings/vscode.rejected.d.ts new file mode 100644 index 00000000000..7f6f64df560 --- /dev/null +++ b/extensions/azure-account/src/typings/vscode.rejected.d.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// This is the place for API experiments and proposal. + +declare module 'vscode' { + + /** + * Namespace for handling credentials. + */ + export namespace credentials { + + /** + * Read a previously stored secret from the credential store. + * + * @param service The service of the credential. + * @param account The account of the credential. + * @return A promise for the secret of the credential. + */ + export function readSecret(service: string, account: string): Thenable; + + /** + * Write a secret to the credential store. + * + * @param service The service of the credential. + * @param account The account of the credential. + * @param secret The secret of the credential to write to the credential store. + * @return A promise indicating completion of the operation. + */ + export function writeSecret(service: string, account: string, secret: string): Thenable; + + /** + * Delete a previously stored secret from the credential store. + * + * @param service The service of the credential. + * @param account The account of the credential. + * @return A promise resolving to true if there was a secret for that service and account. + */ + export function deleteSecret(service: string, account: string): Thenable; + } +} diff --git a/extensions/azure-account/tsconfig.json b/extensions/azure-account/tsconfig.json new file mode 100644 index 00000000000..61265d449a4 --- /dev/null +++ b/extensions/azure-account/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "strict": true, + "noUnusedLocals": true, + "outDir": "./out", + "lib": [ + "es6" + ], + "sourceMap": true + }, + "exclude": [ + "node_modules" + ], + "include": [ + "src/**/*" + ] +} \ No newline at end of file diff --git a/extensions/css/client/src/cssMain.ts b/extensions/css/client/src/cssMain.ts index 1080628cad4..a13dabac0b9 100644 --- a/extensions/css/client/src/cssMain.ts +++ b/extensions/css/client/src/cssMain.ts @@ -9,6 +9,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, ColorProvider } from './colorDecorators'; +import { ConfigurationFeature } from 'vscode-languageclient/lib/proposed'; import * as nls from 'vscode-nls'; let localize = nls.loadMessageBundle(); @@ -23,7 +24,7 @@ export function activate(context: ExtensionContext) { // The server is implemented in node let serverModule = context.asAbsolutePath(path.join('server', 'out', 'cssServerMain.js')); // The debug options for the server - let debugOptions = { execArgv: ['--nolazy', '--debug=6004'] }; + let debugOptions = { execArgv: ['--nolazy', '--inspect=6004'] }; // If the extension is launch in debug mode the debug server options are use // Otherwise the run options are used @@ -44,6 +45,7 @@ export function activate(context: ExtensionContext) { // Create the language client and start the client. let client = new LanguageClient('css', localize('cssserver.name', 'CSS Language Server'), serverOptions, clientOptions); + client.registerFeature(new ConfigurationFeature(client)); let disposable = client.start(); // Push the disposable to the context's subscriptions so that the diff --git a/extensions/css/npm-shrinkwrap.json b/extensions/css/npm-shrinkwrap.json index 89c99f09cf0..6a497e0b691 100644 --- a/extensions/css/npm-shrinkwrap.json +++ b/extensions/css/npm-shrinkwrap.json @@ -13,19 +13,19 @@ "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", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.2.0.tgz" + "version": "3.3.1", + "from": "vscode-jsonrpc@>=3.3.0 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.3.1.tgz" }, "vscode-languageclient": { - "version": "3.2.0", - "from": "vscode-languageclient@3.2.0", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.2.0.tgz" + "version": "3.4.0-next.10", + "from": "vscode-languageclient@next", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.4.0-next.10.tgz" }, "vscode-languageserver-types": { - "version": "3.2.0", - "from": "vscode-languageserver-types@>=3.2.0 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.2.0.tgz" + "version": "3.3.0", + "from": "vscode-languageserver-types@>=3.3.0 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.3.0.tgz" }, "vscode-nls": { "version": "2.0.2", diff --git a/extensions/css/package.json b/extensions/css/package.json index ff0bf9ac994..49b8f98ba2f 100644 --- a/extensions/css/package.json +++ b/extensions/css/package.json @@ -62,16 +62,19 @@ "properties": { "css.validate": { "type": "boolean", + "scope": "resource", "default": true, "description": "%css.validate.desc%" }, "css.colorDecorators.enable": { "type": "boolean", + "scope": "window", "default": true, "description": "%css.colorDecorators.enable.desc%" }, "css.lint.compatibleVendorPrefixes": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -82,6 +85,7 @@ }, "css.lint.vendorPrefix": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -92,6 +96,7 @@ }, "css.lint.duplicateProperties": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -102,6 +107,7 @@ }, "css.lint.emptyRules": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -112,6 +118,7 @@ }, "css.lint.importStatement": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -122,6 +129,7 @@ }, "css.lint.boxModel": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -132,6 +140,7 @@ }, "css.lint.universalSelector": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -142,6 +151,7 @@ }, "css.lint.zeroUnits": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -152,6 +162,7 @@ }, "css.lint.fontFaceProperties": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -162,6 +173,7 @@ }, "css.lint.hexColorLength": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -172,6 +184,7 @@ }, "css.lint.argumentsInColorFunction": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -182,6 +195,7 @@ }, "css.lint.unknownProperties": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -192,6 +206,7 @@ }, "css.lint.ieHack": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -202,6 +217,7 @@ }, "css.lint.unknownVendorSpecificProperties": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -212,6 +228,7 @@ }, "css.lint.propertyIgnoredDueToDisplay": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -222,6 +239,7 @@ }, "css.lint.important": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -232,6 +250,7 @@ }, "css.lint.float": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -242,6 +261,7 @@ }, "css.lint.idSelector": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -252,6 +272,7 @@ }, "css.trace.server": { "type": "string", + "scope": "window", "enum": [ "off", "messages", @@ -274,16 +295,19 @@ "properties": { "scss.validate": { "type": "boolean", + "scope": "resource", "default": true, "description": "%scss.validate.desc%" }, "scss.colorDecorators.enable": { "type": "boolean", + "scope": "window", "default": true, "description": "%scss.colorDecorators.enable.desc%" }, "scss.lint.compatibleVendorPrefixes": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -294,6 +318,7 @@ }, "scss.lint.vendorPrefix": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -304,6 +329,7 @@ }, "scss.lint.duplicateProperties": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -314,6 +340,7 @@ }, "scss.lint.emptyRules": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -324,6 +351,7 @@ }, "scss.lint.importStatement": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -334,6 +362,7 @@ }, "scss.lint.boxModel": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -344,6 +373,7 @@ }, "scss.lint.universalSelector": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -354,6 +384,7 @@ }, "scss.lint.zeroUnits": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -364,6 +395,7 @@ }, "scss.lint.fontFaceProperties": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -374,6 +406,7 @@ }, "scss.lint.hexColorLength": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -384,6 +417,7 @@ }, "scss.lint.argumentsInColorFunction": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -394,6 +428,7 @@ }, "scss.lint.unknownProperties": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -404,6 +439,7 @@ }, "scss.lint.ieHack": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -414,6 +450,7 @@ }, "scss.lint.unknownVendorSpecificProperties": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -424,6 +461,7 @@ }, "scss.lint.propertyIgnoredDueToDisplay": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -434,6 +472,7 @@ }, "scss.lint.important": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -444,6 +483,7 @@ }, "scss.lint.float": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -454,6 +494,7 @@ }, "scss.lint.idSelector": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -477,16 +518,19 @@ "properties": { "less.validate": { "type": "boolean", + "scope": "resource", "default": true, "description": "%less.validate.desc%" }, "less.colorDecorators.enable": { "type": "boolean", + "scope": "window", "default": true, "description": "%less.colorDecorators.enable.desc%" }, "less.lint.compatibleVendorPrefixes": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -497,6 +541,7 @@ }, "less.lint.vendorPrefix": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -507,6 +552,7 @@ }, "less.lint.duplicateProperties": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -517,6 +563,7 @@ }, "less.lint.emptyRules": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -527,6 +574,7 @@ }, "less.lint.importStatement": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -537,6 +585,7 @@ }, "less.lint.boxModel": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -547,6 +596,7 @@ }, "less.lint.universalSelector": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -557,6 +607,7 @@ }, "less.lint.zeroUnits": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -567,6 +618,7 @@ }, "less.lint.fontFaceProperties": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -577,6 +629,7 @@ }, "less.lint.hexColorLength": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -587,6 +640,7 @@ }, "less.lint.argumentsInColorFunction": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -597,6 +651,7 @@ }, "less.lint.unknownProperties": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -607,6 +662,7 @@ }, "less.lint.ieHack": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -617,6 +673,7 @@ }, "less.lint.unknownVendorSpecificProperties": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -627,6 +684,7 @@ }, "less.lint.propertyIgnoredDueToDisplay": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -637,6 +695,7 @@ }, "less.lint.important": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -647,6 +706,7 @@ }, "less.lint.float": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -657,6 +717,7 @@ }, "less.lint.idSelector": { "type": "string", + "scope": "resource", "enum": [ "ignore", "warning", @@ -674,10 +735,10 @@ }, "dependencies": { "parse-color": "^1.0.0", - "vscode-languageclient": "^3.2.0", + "vscode-languageclient": "3.4.0-next.10", "vscode-nls": "^2.0.2" }, "devDependencies": { "@types/node": "^6.0.51" } -} \ No newline at end of file +} diff --git a/extensions/css/server/npm-shrinkwrap.json b/extensions/css/server/npm-shrinkwrap.json index 21329db6203..63bed9e999b 100644 --- a/extensions/css/server/npm-shrinkwrap.json +++ b/extensions/css/server/npm-shrinkwrap.json @@ -3,29 +3,34 @@ "version": "1.0.0", "dependencies": { "vscode-css-languageservice": { - "version": "2.1.2", + "version": "2.1.3", "from": "vscode-css-languageservice@next", - "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-2.1.2.tgz" + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-2.1.3.tgz" }, "vscode-jsonrpc": { - "version": "3.2.0", - "from": "vscode-jsonrpc@>=3.2.0 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.2.0.tgz" + "version": "3.3.1", + "from": "vscode-jsonrpc@>=3.3.0 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.3.1.tgz" }, "vscode-languageserver": { - "version": "3.2.0", - "from": "vscode-languageserver@3.2.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.2.0.tgz" + "version": "3.4.0-next.4", + "from": "vscode-languageserver@next", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.4.0-next.4.tgz" }, "vscode-languageserver-types": { - "version": "3.2.0", - "from": "vscode-languageserver-types@>=3.2.0 <4.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.2.0.tgz" + "version": "3.3.0", + "from": "vscode-languageserver-types@>=3.3.0 <4.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.3.0.tgz" }, "vscode-nls": { "version": "2.0.2", "from": "vscode-nls@>=2.0.1 <3.0.0", "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-2.0.2.tgz" + }, + "vscode-uri": { + "version": "1.0.1", + "from": "vscode-uri@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.1.tgz" } } } diff --git a/extensions/css/server/package.json b/extensions/css/server/package.json index c7b93d1573f..d8112cc044a 100644 --- a/extensions/css/server/package.json +++ b/extensions/css/server/package.json @@ -8,8 +8,8 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^2.1.2", - "vscode-languageserver": "^3.2.0" + "vscode-css-languageservice": "^2.1.3", + "vscode-languageserver": "3.4.0-next.4" }, "devDependencies": { "@types/node": "^6.0.51" diff --git a/extensions/css/server/src/cssServerMain.ts b/extensions/css/server/src/cssServerMain.ts index 573b4b61c5f..4b5c4e4ad2c 100644 --- a/extensions/css/server/src/cssServerMain.ts +++ b/extensions/css/server/src/cssServerMain.ts @@ -8,6 +8,7 @@ import { createConnection, IConnection, Range, TextDocuments, TextDocument, InitializeParams, InitializeResult, RequestType } from 'vscode-languageserver'; +import { GetConfigurationRequest } from 'vscode-languageserver/lib/protocol.proposed'; import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet } from 'vscode-css-languageservice'; import { getLanguageModelCache } from './languageModelCache'; @@ -43,10 +44,20 @@ connection.onShutdown(() => { stylesheets.dispose(); }); +let scopedSettingsSupport = false; // After the server has started the client sends an initilize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { - let snippetSupport = params.capabilities && params.capabilities.textDocument && params.capabilities.textDocument.completion && params.capabilities.textDocument.completion.completionItem && params.capabilities.textDocument.completion.completionItem.snippetSupport; + function hasClientCapability(name: string) { + let keys = name.split('.'); + let c = params.capabilities; + for (let i = 0; c && i < keys.length; i++) { + c = c[keys[i]]; + } + return !!c; + } + let snippetSupport = hasClientCapability('textDocument.completion.completionItem.snippetSupport'); + scopedSettingsSupport = hasClientCapability('workspace.configuration'); return { capabilities: { // Tell the client that the server works in FULL text document sync mode @@ -78,6 +89,20 @@ function getLanguageService(document: TextDocument) { return service; } +let documentSettings: { [key: string]: Thenable } = {}; +function getDocumentSettings(textDocument: TextDocument): Thenable { + if (scopedSettingsSupport) { + let promise = documentSettings[textDocument.uri]; + if (!promise) { + let configRequestParam = { items: [{ scopeUri: textDocument.uri, section: textDocument.languageId }] }; + promise = connection.sendRequest(GetConfigurationRequest.type, configRequestParam).then(s => s[0]); + documentSettings[textDocument.uri] = promise; + } + return promise; + } + return void 0; +} + // The settings have changed. Is send on server activation as well. connection.onDidChangeConfiguration(change => { updateConfiguration(change.settings); @@ -87,6 +112,8 @@ function updateConfiguration(settings: Settings) { for (let languageId in languageServices) { languageServices[languageId].configure(settings[languageId]); } + // reset all document settings + documentSettings = {}; // Revalidate any open text documents documents.all().forEach(triggerValidation); } @@ -123,10 +150,13 @@ function triggerValidation(textDocument: TextDocument): void { } function validateTextDocument(textDocument: TextDocument): void { + let settingsPromise = getDocumentSettings(textDocument); let stylesheet = stylesheets.get(textDocument); - let diagnostics = getLanguageService(textDocument).doValidation(textDocument, stylesheet); - // Send the computed diagnostics to VSCode. - connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); + settingsPromise.then(settings => { + let diagnostics = getLanguageService(textDocument).doValidation(textDocument, stylesheet, settings); + // Send the computed diagnostics to VSCode. + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); + }); } connection.onCompletion(textDocumentPosition => { diff --git a/extensions/emmet/npm-shrinkwrap.json b/extensions/emmet/npm-shrinkwrap.json index 68b2886e6d4..d4dd5915a17 100644 --- a/extensions/emmet/npm-shrinkwrap.json +++ b/extensions/emmet/npm-shrinkwrap.json @@ -4,8 +4,8 @@ "dependencies": { "@emmetio/css-parser": { "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" + "from": "ramya-rao-a/css-parser#vscode", + "resolved": "git://github.com/ramya-rao-a/css-parser.git#370c480ac103bd17c7bcfb34bf5d577dc40d3660" }, "@emmetio/extract-abbreviation": { "version": "0.1.3", @@ -38,9 +38,9 @@ "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz" }, "vscode-emmet-helper": { - "version": "1.0.16", - "from": "vscode-emmet-helper@>=1.0.16 <2.0.0", - "resolved": "https://registry.npmjs.org/vscode-emmet-helper/-/vscode-emmet-helper-1.0.16.tgz" + "version": "1.0.17", + "from": "vscode-emmet-helper@>=1.0.17 <2.0.0", + "resolved": "https://registry.npmjs.org/vscode-emmet-helper/-/vscode-emmet-helper-1.0.17.tgz" }, "vscode-languageserver-types": { "version": "3.3.0", diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index f9266ef067a..7bf1a1213b9 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -210,9 +210,9 @@ "dependencies": { "@emmetio/html-matcher": "^0.3.1", - "@emmetio/css-parser": "^0.4.0", + "@emmetio/css-parser": "ramya-rao-a/css-parser#vscode", "@emmetio/math-expression": "^0.1.1", - "vscode-emmet-helper": "^1.0.16", + "vscode-emmet-helper": "^1.0.17", "vscode-languageserver-types": "^3.0.3", "image-size": "^0.5.2", "vscode-nls": "2.0.2" diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index 399d3807833..df92393e509 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -95,10 +95,7 @@ export function expandEmmetAbbreviation(args): Thenable { const editor = vscode.window.activeTextEditor; - let rootNode = parseDocument(editor.document); - if (!rootNode) { - return fallbackTab(); - } + let rootNode = parseDocument(editor.document, false); // When tabbed on a non empty selection, do not treat it as an emmet abbreviation, and fallback to tab instead if (vscode.workspace.getConfiguration('emmet')['triggerExpansionOnTab'] === true && editor.selections.find(x => !x.isEmpty)) { @@ -192,21 +189,31 @@ export function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: s } if (isStyleSheet(syntax)) { - if (currentNode.type !== 'rule') { + + // If current node is a rule or at-rule, then perform additional checks to ensure + // emmet suggestions are not provided in the rule selector + if (currentNode.type !== 'rule' && currentNode.type !== 'at-rule') { return true; } + const currentCssNode = currentNode; - // Workaround for https://github.com/Microsoft/vscode/30188 - if (currentCssNode.parent - && currentCssNode.parent.type === 'rule' - && currentCssNode.selectorToken - && currentCssNode.selectorToken.start.line !== currentCssNode.selectorToken.end.line) { + // Position is valid if it occurs after the `{` that marks beginning of rule contents + if (position.isAfter(currentCssNode.contentStartToken.end)) { return true; } - // Position is valid if it occurs after the `{` that marks beginning of rule contents - return currentCssNode.selectorToken && position.isAfter(currentCssNode.selectorToken.end); + // Workaround for https://github.com/Microsoft/vscode/30188 + // The line above the rule selector is considered as part of the selector by the css-parser + // But we should assume it is a valid location for css properties under the parent rule + if (currentCssNode.parent + && (currentCssNode.parent.type === 'rule' || currentCssNode.parent.type === 'at-rule') + && currentCssNode.selectorToken + && position.line !== currentCssNode.selectorToken.end.line) { + return true; + } + + return false; } const currentHtmlNode = currentNode; diff --git a/extensions/emmet/src/defaultCompletionProvider.ts b/extensions/emmet/src/defaultCompletionProvider.ts index 28adbadfb7a..cadfd8ef380 100644 --- a/extensions/emmet/src/defaultCompletionProvider.ts +++ b/extensions/emmet/src/defaultCompletionProvider.ts @@ -72,9 +72,13 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi const currentHtmlNode = currentNode; if (currentHtmlNode && currentHtmlNode.close - && currentHtmlNode.name === 'style' && getInnerRange(currentHtmlNode).contains(position)) { - return 'css'; + if (currentHtmlNode.name === 'style') { + return 'css'; + } + if (currentHtmlNode.name === 'script') { + return; + } } } diff --git a/extensions/emmet/src/test/abbreviationAction.test.ts b/extensions/emmet/src/test/abbreviationAction.test.ts index 9d179b8d661..f3e0aec1456 100644 --- a/extensions/emmet/src/test/abbreviationAction.test.ts +++ b/extensions/emmet/src/test/abbreviationAction.test.ts @@ -21,6 +21,25 @@ const cssContents = ` } `; +const scssContents = ` +.boo { + margin: 10px; + p10 + .hoo { + p20 + } +} +@include b(alert) { + + margin: 10px; + p30 + + @include b(alert) { + p40 + } +} +` + const bemFilterExample = 'ul.search-form._wide>li.-querystring+li.-btn_large|bem'; const expectedBemFilterOutput = `