diff --git a/.github/commands.yml b/.github/commands.yml index 70d41295408..f20079caab8 100644 --- a/.github/commands.yml +++ b/.github/commands.yml @@ -51,6 +51,12 @@ action: 'close', comment: "The described behavior is how it is expected to work. If you disagree, please explain what is expected and what is not in more detail. See also our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines.\n\nHappy Coding!" }, + { + type: 'label', + name: '*english-please', + action: 'close', + comment: "This issue is being closed because its description is not in English, that makes it hard for us to work on it. Please open a new issue with an English description. You might find [Bing Translator](https://www.bing.com/translator) useful." + }, { type: 'comment', name: 'duplicate', diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 2df05cca0d3..69d9050d4a4 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -26,7 +26,7 @@ This project incorporates components from the projects listed below. The origina 19. Ikuyadeu/vscode-R version 0.5.5 (https://github.com/Ikuyadeu/vscode-R) 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. jeff-hykin/cpp-textmate-grammar version 1.4.5 (https://github.com/jeff-hykin/cpp-textmate-grammar) +22. jeff-hykin/cpp-textmate-grammar version 1.8.8 (https://github.com/jeff-hykin/cpp-textmate-grammar) 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) @@ -35,14 +35,14 @@ This project incorporates components from the projects listed below. The origina 28. language-php version 0.44.1 (https://github.com/atom/language-php) 29. language-rust version 0.4.12 (https://github.com/zargony/atom-language-rust) 30. MagicStack/MagicPython version 1.1.1 (https://github.com/MagicStack/MagicPython) -31. marked version 0.5.0 (https://github.com/markedjs/marked) +31. marked version 0.6.2 (https://github.com/markedjs/marked) 32. mdn-data version 1.1.12 (https://github.com/mdn/data) 33. Microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/Microsoft/TypeScript-TmLanguage) 34. Microsoft/vscode-JSON.tmLanguage (https://github.com/Microsoft/vscode-JSON.tmLanguage) 35. Microsoft/vscode-mssql version 1.4.0 (https://github.com/Microsoft/vscode-mssql) 36. mmims/language-batchfile version 0.7.5 (https://github.com/mmims/language-batchfile) 37. octicons version 8.3.0 (https://github.com/primer/octicons) -38. octref/language-css (https://github.com/octref/language-css) +38. octref/language-css version 0.42.11 (https://github.com/octref/language-css) 39. PowerShell/EditorSyntax (https://github.com/powershell/editorsyntax) 40. promise-polyfill version 8.0.0 (https://github.com/taylorhakes/promise-polyfill) 41. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) @@ -64,7 +64,7 @@ This project incorporates components from the projects listed below. The origina 57. TypeScript-TmLanguage version 1.0.0 (https://github.com/Microsoft/TypeScript-TmLanguage) 58. Unicode version 12.0.0 (http://www.unicode.org/) 59. vscode-logfile-highlighter version 2.4.1 (https://github.com/emilast/vscode-logfile-highlighter) -60. vscode-octicons-font version 1.0.0 (https://github.com/Microsoft/vscode-octicons-font) +60. vscode-octicons-font version 1.1.0 (https://github.com/Microsoft/vscode-octicons-font) 61. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) 62. Web Background Synchronization (https://github.com/WICG/BackgroundSync) diff --git a/build/.nativeignore b/build/.nativeignore new file mode 100644 index 00000000000..cc88ae0d878 --- /dev/null +++ b/build/.nativeignore @@ -0,0 +1,111 @@ +# cleanup rules for native node modules, .gitignore style + +fsevents/binding.gyp +fsevents/fsevents.cc +fsevents/build/** +fsevents/src/** +fsevents/test/** +!fsevents/**/*.node + +vscode-sqlite3/binding.gyp +vscode-sqlite3/benchmark/** +vscode-sqlite3/cloudformation/** +vscode-sqlite3/deps/** +vscode-sqlite3/test/** +vscode-sqlite3/build/** +vscode-sqlite3/src/** +!vscode-sqlite3/build/Release/*.node + +oniguruma/binding.gyp +oniguruma/build/** +oniguruma/src/** +oniguruma/deps/** +!oniguruma/build/Release/*.node +!oniguruma/src/*.js + +windows-mutex/binding.gyp +windows-mutex/build/** +windows-mutex/src/** +!windows-mutex/**/*.node + +native-keymap/binding.gyp +native-keymap/build/** +native-keymap/src/** +native-keymap/deps/** +!native-keymap/build/Release/*.node + +native-is-elevated/binding.gyp +native-is-elevated/build/** +native-is-elevated/src/** +native-is-elevated/deps/** +!native-is-elevated/build/Release/*.node + +native-watchdog/binding.gyp +native-watchdog/build/** +native-watchdog/src/** +!native-watchdog/build/Release/*.node + +spdlog/binding.gyp +spdlog/build/** +spdlog/deps/** +spdlog/src/** +spdlog/test/** +!spdlog/build/Release/*.node + +jschardet/dist/** + +windows-foreground-love/binding.gyp +windows-foreground-love/build/** +windows-foreground-love/src/** +!windows-foreground-love/**/*.node + +windows-process-tree/binding.gyp +windows-process-tree/build/** +windows-process-tree/src/** +!windows-process-tree/**/*.node + +gc-signals/binding.gyp +gc-signals/build/** +gc-signals/src/** +gc-signals/deps/** + +!gc-signals/build/Release/*.node +!gc-signals/src/index.js + +keytar/binding.gyp +keytar/build/** +keytar/src/** +keytar/script/** +keytar/node_modules/** +!keytar/**/*.node + +node-pty/binding.gyp +node-pty/build/** +node-pty/src/** +node-pty/tools/** +!node-pty/build/Release/*.exe +!node-pty/build/Release/*.dll +!node-pty/build/Release/*.node + +vscode-nsfw/binding.gyp +vscode-nsfw/build/** +vscode-nsfw/src/** +vscode-nsfw/openpa/** +vscode-nsfw/includes/** +!vscode-nsfw/build/Release/*.node +!vscode-nsfw/**/*.a + +vsda/binding.gyp +vsda/README.md +vsda/build/** +vsda/*.bat +vsda/*.sh +vsda/*.cpp +vsda/*.h +!vsda/build/Release/vsda.node + +vscode-windows-ca-certs/**/* +!vscode-windows-ca-certs/package.json +!vscode-windows-ca-certs/**/*.node + +node-addon-api/**/* \ No newline at end of file diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index f5d5ef626b1..5d6ec8c2cff 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -7,15 +7,21 @@ steps: inputs: versionSpec: "1.10.1" +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'vscode-builds-subscription' + KeyVaultName: vscode + - script: | set -e cat << EOF > ~/.netrc machine monacotools.visualstudio.com - password $(VSO_PAT) + password $(devops-pat) machine github.com login vscode - password $(VSCODE_MIXIN_PASSWORD) + password $(github-distro-mixin-password) EOF git config user.email "vscode@microsoft.com" @@ -34,8 +40,8 @@ steps: - script: | set -e - VSCODE_MIXIN_PASSWORD="$(VSCODE_MIXIN_PASSWORD)" \ - AZURE_STORAGE_ACCESS_KEY="$(AZURE_STORAGE_ACCESS_KEY)" \ + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ ./build/azure-pipelines/darwin/build.sh displayName: Build @@ -77,11 +83,11 @@ steps: - script: | set -e - VSCODE_MIXIN_PASSWORD="$(VSCODE_MIXIN_PASSWORD)" \ - AZURE_DOCUMENTDB_MASTERKEY="$(AZURE_DOCUMENTDB_MASTERKEY)" \ - AZURE_STORAGE_ACCESS_KEY="$(AZURE_STORAGE_ACCESS_KEY)" \ - AZURE_STORAGE_ACCESS_KEY_2="$(AZURE_STORAGE_ACCESS_KEY_2)" \ - VSCODE_HOCKEYAPP_TOKEN="$(VSCODE_HOCKEYAPP_TOKEN)" \ + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_STORAGE_ACCESS_KEY="$(ticino-storage-key)" \ + AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ + VSCODE_HOCKEYAPP_TOKEN="$(vscode-hockeyapp-token)" \ ./build/azure-pipelines/darwin/publish.sh displayName: Publish diff --git a/build/azure-pipelines/distro-build.yml b/build/azure-pipelines/distro-build.yml index dc55bce808c..639456ad4ce 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-build.yml @@ -10,13 +10,19 @@ steps: inputs: versionSpec: "10.15.1" +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'vscode-builds-subscription' + KeyVaultName: vscode + - script: | set -e cat << EOF > ~/.netrc machine github.com login vscode - password $(VSCODE_MIXIN_PASSWORD) + password $(github-distro-mixin-password) EOF git config user.email "vscode@microsoft.com" diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 342d72e4969..f727e30d252 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -7,6 +7,12 @@ steps: inputs: versionSpec: "1.10.1" +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'vscode-builds-subscription' + KeyVaultName: vscode + - script: | set -e export npm_config_arch="$(VSCODE_ARCH)" @@ -16,10 +22,10 @@ steps: cat << EOF > ~/.netrc machine monacotools.visualstudio.com - password $(VSO_PAT) + password $(devops-pat) machine github.com login vscode - password $(VSCODE_MIXIN_PASSWORD) + password $(github-distro-mixin-password) EOF git config user.email "vscode@microsoft.com" @@ -38,7 +44,7 @@ steps: - script: | set -e - VSCODE_MIXIN_PASSWORD="$(VSCODE_MIXIN_PASSWORD)" \ + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ ./build/azure-pipelines/linux/build.sh displayName: Build @@ -55,10 +61,10 @@ steps: - script: | set -e - AZURE_DOCUMENTDB_MASTERKEY="$(AZURE_DOCUMENTDB_MASTERKEY)" \ - AZURE_STORAGE_ACCESS_KEY_2="$(AZURE_STORAGE_ACCESS_KEY_2)" \ - VSCODE_MIXIN_PASSWORD="$(VSCODE_MIXIN_PASSWORD)" \ - VSCODE_HOCKEYAPP_TOKEN="$(VSCODE_HOCKEYAPP_TOKEN)" \ + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ + VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ + VSCODE_HOCKEYAPP_TOKEN="$(vscode-hockeyapp-token)" \ ./build/azure-pipelines/linux/publish.sh displayName: Publish diff --git a/build/azure-pipelines/linux/snap-build-linux.yml b/build/azure-pipelines/linux/snap-build-linux.yml index 9588ebcb36d..9d98e9fa472 100644 --- a/build/azure-pipelines/linux/snap-build-linux.yml +++ b/build/azure-pipelines/linux/snap-build-linux.yml @@ -7,6 +7,12 @@ steps: inputs: versionSpec: "1.10.1" +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'vscode-builds-subscription' + KeyVaultName: vscode + - task: DownloadPipelineArtifact@0 displayName: 'Download Pipeline Artifact' inputs: @@ -44,6 +50,6 @@ steps: (cd $SNAP_ROOT/code-* && sudo snapcraft snap --output "$SNAP_PATH") # Publish snap package - AZURE_DOCUMENTDB_MASTERKEY="$(AZURE_DOCUMENTDB_MASTERKEY)" \ - AZURE_STORAGE_ACCESS_KEY_2="$(AZURE_STORAGE_ACCESS_KEY_2)" \ + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ node build/azure-pipelines/common/publish.js "$VSCODE_QUALITY" "linux-snap-$ARCH" package "$SNAP_FILENAME" "$VERSION" true "$SNAP_PATH" \ No newline at end of file diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 86868f6a2a6..c8bedfbffc0 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -1,9 +1,11 @@ resources: containers: - container: vscode-x64 - image: joaomoreno/vscode-linux-build-agent:x64 + endpoint: VSCodeHub + image: vscodehub.azurecr.io/vscode-linux-build-agent:x64 - container: vscode-ia32 - image: joaomoreno/vscode-linux-build-agent:ia32 + endpoint: VSCodeHub + image: vscodehub.azurecr.io/vscode-linux-build-agent:ia32 - container: snapcraft image: snapcore/snapcraft diff --git a/build/azure-pipelines/publish-types/check-version.ts b/build/azure-pipelines/publish-types/check-version.ts new file mode 100644 index 00000000000..2c730889112 --- /dev/null +++ b/build/azure-pipelines/publish-types/check-version.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. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as cp from 'child_process'; + +let tag = ''; +try { + tag = cp + .execSync('git describe --tags `git rev-list --tags --max-count=1`') + .toString() + .trim(); + + if (!isValidTag(tag)) { + throw Error(`Invalid tag ${tag}`); + } +} catch (err) { + console.error(err); + console.error('Failed to update types'); + process.exit(1); +} + +function isValidTag(t: string) { + if (t.split('.').length !== 3) { + return false; + } + + const [major, minor, bug] = t.split('.'); + + // Only release for tags like 1.34.0 + if (bug !== '0') { + return false; + } + + if (parseInt(major, 10) === NaN || parseInt(minor, 10) === NaN) { + return false; + } + + return true; +} \ No newline at end of file diff --git a/build/azure-pipelines/publish-types/publish-types.yml b/build/azure-pipelines/publish-types/publish-types.yml new file mode 100644 index 00000000000..ea93c0b2b25 --- /dev/null +++ b/build/azure-pipelines/publish-types/publish-types.yml @@ -0,0 +1,40 @@ +# Publish @types/vscode for each release + +trigger: + branches: + include: ['refs/tags/*'] + +steps: +- task: NodeTool@0 + inputs: + versionSpec: "10.15.1" + +- task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 + inputs: + versionSpec: "1.10.1" + +- bash: | + # Install build dependencies + (cd build && yarn) + node build/azure-pipelines/publish-types/check-version.js + displayName: Check version + +- bash: | + git config --global user.email "vscode@microsoft.com" + git config --global user.name "VSCode" + + git clone https://$(GITHUB_TOKEN)@github.com/DefinitelyTyped/DefinitelyTyped.git --depth=1 + node build/azure-pipelines/publish-types/update-types.js + + TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) + + cd DefinitelyTyped + + git diff --color | cat + git add -A + git status + git checkout -b "vscode-types-$TAG_VERSION" + git commit -m "VS Code $TAG_VERSION Extension API" + git push origin "vscode-types-$TAG_VERSION" + + displayName: Push update to DefinitelyTyped diff --git a/build/azure-pipelines/publish-types/update-types.ts b/build/azure-pipelines/publish-types/update-types.ts new file mode 100644 index 00000000000..a5ef449b77b --- /dev/null +++ b/build/azure-pipelines/publish-types/update-types.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as fs from 'fs'; +import * as cp from 'child_process'; +import * as path from 'path'; + +let tag = ''; +try { + tag = cp + .execSync('git describe --tags `git rev-list --tags --max-count=1`') + .toString() + .trim(); + + const dtsUri = `https://raw.githubusercontent.com/microsoft/vscode/${tag}/src/vs/vscode.d.ts`; + const outPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/vscode/index.d.ts'); + cp.execSync(`curl ${dtsUri} --output ${outPath}`); + + updateDTSFile(outPath, tag); + + console.log(`Done updating vscode.d.ts at ${outPath}`); +} catch (err) { + console.error(err); + console.error('Failed to update types'); + process.exit(1); +} + +function updateDTSFile(outPath: string, tag: string) { + const oldContent = fs.readFileSync(outPath, 'utf-8'); + const newContent = getNewFileContent(oldContent, tag); + + fs.writeFileSync(outPath, newContent); +} + +function getNewFileContent(content: string, tag: string) { + const oldheader = [ + `/*---------------------------------------------------------------------------------------------`, + ` * Copyright (c) Microsoft Corporation. All rights reserved.`, + ` * Licensed under the MIT License. See License.txt in the project root for license information.`, + ` *--------------------------------------------------------------------------------------------*/` + ].join('\n'); + + return getNewFileHeader(tag) + content.slice(oldheader.length); +} + +function getNewFileHeader(tag: string) { + const [major, minor] = tag.split('.'); + const shorttag = `${major}.${minor}`; + + const header = [ + `// Type definitions for Visual Studio Code ${shorttag}`, + `// Project: https://github.com/microsoft/vscode`, + `// Definitions by: Visual Studio Code Team, Microsoft `, + `// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped`, + ``, + `/*---------------------------------------------------------------------------------------------`, + ` * Copyright (c) Microsoft Corporation. All rights reserved.`, + ` * Licensed under the MIT License.`, + ` * See https://github.com/Microsoft/vscode/blob/master/LICENSE.txt for license information.`, + ` *--------------------------------------------------------------------------------------------*/`, + ``, + `/**`, + ` * Type Definition for Visual Studio Code ${shorttag} Extension API`, + ` * See https://code.visualstudio.com/api for more information`, + ` */` + ].join('\n'); + + return header; +} diff --git a/build/azure-pipelines/sync-mooncake.yml b/build/azure-pipelines/sync-mooncake.yml index c422839de1c..f3e8bc07856 100644 --- a/build/azure-pipelines/sync-mooncake.yml +++ b/build/azure-pipelines/sync-mooncake.yml @@ -7,12 +7,18 @@ steps: inputs: versionSpec: "1.10.1" +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'vscode-builds-subscription' + KeyVaultName: vscode + - script: | set -e (cd build ; yarn) - AZURE_DOCUMENTDB_MASTERKEY="$(AZURE_DOCUMENTDB_MASTERKEY)" \ - AZURE_STORAGE_ACCESS_KEY_2="$(AZURE_STORAGE_ACCESS_KEY_2)" \ - MOONCAKE_STORAGE_ACCESS_KEY="$(MOONCAKE_STORAGE_ACCESS_KEY)" \ + AZURE_DOCUMENTDB_MASTERKEY="$(builds-docdb-key-readwrite)" \ + AZURE_STORAGE_ACCESS_KEY_2="$(vscode-storage-key)" \ + MOONCAKE_STORAGE_ACCESS_KEY="$(vscode-mooncake-storage-key)" \ node build/azure-pipelines/common/sync-mooncake.js "$VSCODE_QUALITY" diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index a49349150d2..8a6a6abb658 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -12,10 +12,16 @@ steps: versionSpec: '2.x' addToPath: true +- task: AzureKeyVault@1 + displayName: 'Azure Key Vault: Get Secrets' + inputs: + azureSubscription: 'vscode-builds-subscription' + KeyVaultName: vscode + - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" - "machine monacotools.visualstudio.com`npassword $(VSO_PAT)`nmachine github.com`nlogin vscode`npassword $(VSCODE_MIXIN_PASSWORD)" | Out-File "$env:USERPROFILE\_netrc" -Encoding ASCII + "machine monacotools.visualstudio.com`npassword $(devops-pat)`nmachine github.com`nlogin vscode`npassword $(github-distro-mixin-password)" | Out-File "$env:USERPROFILE\_netrc" -Encoding ASCII $env:npm_config_arch="$(VSCODE_ARCH)" $env:CHILD_CONCURRENCY="1" @@ -36,7 +42,7 @@ steps: - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" - $env:VSCODE_MIXIN_PASSWORD="$(VSCODE_MIXIN_PASSWORD)" + $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" .\build\azure-pipelines\win32\build.ps1 displayName: Build @@ -126,15 +132,15 @@ steps: - powershell: | $ErrorActionPreference = "Stop" - .\build\azure-pipelines\win32\import-esrp-auth-cert.ps1 -AuthCertificateBase64 $(ESRP_AUTH_CERTIFICATE) -AuthCertificateKey $(ESRP_AUTH_CERTIFICATE_KEY) + .\build\azure-pipelines\win32\import-esrp-auth-cert.ps1 -AuthCertificateBase64 $(esrp-auth-certificate) -AuthCertificateKey $(esrp-auth-certificate-key) displayName: Import ESRP Auth Certificate - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" - $env:AZURE_STORAGE_ACCESS_KEY_2 = "$(AZURE_STORAGE_ACCESS_KEY_2)" - $env:AZURE_DOCUMENTDB_MASTERKEY = "$(AZURE_DOCUMENTDB_MASTERKEY)" - $env:VSCODE_HOCKEYAPP_TOKEN = "$(VSCODE_HOCKEYAPP_TOKEN)" + $env:AZURE_STORAGE_ACCESS_KEY_2 = "$(vscode-storage-key)" + $env:AZURE_DOCUMENTDB_MASTERKEY = "$(builds-docdb-key-readwrite)" + $env:VSCODE_HOCKEYAPP_TOKEN = "$(vscode-hockeyapp-token)" .\build\azure-pipelines\win32\publish.ps1 displayName: Publish diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index f678b7bec07..b460362095f 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -31,7 +31,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.26", + "version": "0.0.27", "repo": "https://github.com/Microsoft/vscode-reference-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 2285cd00027..57ca292ef73 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -78,8 +78,6 @@ const vscodeResources = [ 'out-build/vs/workbench/contrib/welcome/walkThrough/**/*.md', 'out-build/vs/workbench/services/files/**/*.exe', 'out-build/vs/workbench/services/files/**/*.md', - 'out-build/vs/workbench/services/files2/**/*.exe', - 'out-build/vs/workbench/services/files2/**/*.md', 'out-build/vs/code/electron-browser/workbench/**', 'out-build/vs/code/electron-browser/sharedProcess/sharedProcess.js', 'out-build/vs/code/electron-browser/issue/issueReporter.js', @@ -325,28 +323,11 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op 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('vscode-sqlite3', ['binding.gyp', 'benchmark/**', 'cloudformation/**', 'deps/**', 'test/**', 'build/**', 'src/**'], ['build/Release/*.node'])) - .pipe(util.cleanNodeModule('oniguruma', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['build/Release/*.node', 'src/*.js'])) - .pipe(util.cleanNodeModule('windows-mutex', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node'])) - .pipe(util.cleanNodeModule('native-keymap', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['build/Release/*.node'])) - .pipe(util.cleanNodeModule('native-is-elevated', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['build/Release/*.node'])) - .pipe(util.cleanNodeModule('native-watchdog', ['binding.gyp', 'build/**', 'src/**'], ['build/Release/*.node'])) - .pipe(util.cleanNodeModule('spdlog', ['binding.gyp', 'build/**', 'deps/**', 'src/**', 'test/**'], ['build/Release/*.node'])) - .pipe(util.cleanNodeModule('jschardet', ['dist/**'])) - .pipe(util.cleanNodeModule('windows-foreground-love', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node'])) - .pipe(util.cleanNodeModule('windows-process-tree', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node'])) - .pipe(util.cleanNodeModule('gc-signals', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['build/Release/*.node', 'src/index.js'])) - .pipe(util.cleanNodeModule('keytar', ['binding.gyp', 'build/**', 'src/**', 'script/**', 'node_modules/**'], ['**/*.node'])) - .pipe(util.cleanNodeModule('node-pty', ['binding.gyp', 'build/**', 'src/**', 'tools/**'], ['build/Release/*.exe', 'build/Release/*.dll', 'build/Release/*.node'])) - .pipe(util.cleanNodeModule('vscode-nsfw', ['binding.gyp', 'build/**', 'src/**', 'openpa/**', 'includes/**'], ['build/Release/*.node', '**/*.a'])) - .pipe(util.cleanNodeModule('vsda', ['binding.gyp', 'README.md', 'build/**', '*.bat', '*.sh', '*.cpp', '*.h'], ['build/Release/vsda.node'])) - .pipe(util.cleanNodeModule('vscode-windows-ca-certs', ['**/*'], ['package.json', '**/*.node'])) - .pipe(util.cleanNodeModule('node-addon-api', ['**/*'])) + .pipe(util.cleanNodeModules(path.join(__dirname, '.nativeignore'))) .pipe(createAsar(path.join(process.cwd(), 'node_modules'), ['**/*.node', '**/vscode-ripgrep/bin/*', '**/node-pty/build/Release/*'], 'app/node_modules.asar')); let all = es.merge( - packageJsonStream, + packageJsonStream, productJsonStream, license, api, diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 61a96c7d056..ac557db625e 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -222,10 +222,6 @@ "name": "vs/workbench/services/files", "project": "vscode-workbench" }, - { - "name": "vs/workbench/services/files2", - "project": "vscode-workbench" - }, { "name": "vs/workbench/services/integrity", "project": "vscode-workbench" diff --git a/build/lib/standalone.js b/build/lib/standalone.js index 63e16442d53..da5987963b6 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -40,6 +40,7 @@ function extractEditor(options) { compilerOptions.noUnusedLocals = false; compilerOptions.preserveConstEnums = false; compilerOptions.declaration = false; + compilerOptions.noImplicitAny = false; compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic; options.compilerOptions = compilerOptions; let result = tss.shake(options); @@ -90,6 +91,8 @@ function extractEditor(options) { } delete tsConfig.compilerOptions.moduleResolution; writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); + const tsConfigBase = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.base.json')).toString()); + writeOutputFile('tsconfig.base.json', JSON.stringify(tsConfigBase, null, '\t')); [ 'vs/css.build.js', 'vs/css.d.ts', diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index dfd3c99fe91..79cdeeb455c 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -44,6 +44,7 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str compilerOptions.noUnusedLocals = false; compilerOptions.preserveConstEnums = false; compilerOptions.declaration = false; + compilerOptions.noImplicitAny = false; compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic; @@ -99,6 +100,8 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str delete tsConfig.compilerOptions.moduleResolution; writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); + const tsConfigBase = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.base.json')).toString()); + writeOutputFile('tsconfig.base.json', JSON.stringify(tsConfigBase, null, '\t')); [ 'vs/css.build.js', diff --git a/build/lib/util.js b/build/lib/util.js index 34ee13695fd..6a210d3decc 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -8,7 +8,6 @@ const es = require("event-stream"); const debounce = require("debounce"); const _filter = require("gulp-filter"); const rename = require("gulp-rename"); -const _ = require("underscore"); const path = require("path"); const fs = require("fs"); const _rimraf = require("rimraf"); @@ -100,22 +99,18 @@ function skipDirectories() { }); } exports.skipDirectories = skipDirectories; -function cleanNodeModule(name, excludes, includes) { - const toGlob = (path) => '**/node_modules/' + name + (path ? '/' + path : ''); - const negate = (str) => '!' + str; - const allFilter = _filter(toGlob('**'), { restore: true }); - const globs = [toGlob('**')].concat(excludes.map(_.compose(negate, toGlob))); +function cleanNodeModules(rulePath) { + const rules = fs.readFileSync(rulePath, 'utf8') + .split(/\r?\n/g) + .map(line => line.trim()) + .filter(line => line && !/^#/.test(line)); + const excludes = rules.filter(line => !/^!/.test(line)).map(line => `!**/node_modules/${line}`); + const includes = rules.filter(line => /^!/.test(line)).map(line => `**/node_modules/${line.substr(1)}`); const input = es.through(); - const nodeModuleInput = input.pipe(allFilter); - let output = nodeModuleInput.pipe(_filter(globs)); - if (includes) { - const includeGlobs = includes.map(toGlob); - output = es.merge(output, nodeModuleInput.pipe(_filter(includeGlobs))); - } - output = output.pipe(allFilter.restore); + const output = es.merge(input.pipe(_filter(['**', ...excludes])), input.pipe(_filter(includes))); return es.duplex(input, output); } -exports.cleanNodeModule = cleanNodeModule; +exports.cleanNodeModules = cleanNodeModules; function loadSourcemaps() { const input = es.through(); const output = input diff --git a/build/lib/util.ts b/build/lib/util.ts index 44ac9d0dc74..e87c0650ec3 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -132,23 +132,21 @@ export function skipDirectories(): NodeJS.ReadWriteStream { }); } -export function cleanNodeModule(name: string, excludes: string[], includes?: string[]): NodeJS.ReadWriteStream { - const toGlob = (path: string) => '**/node_modules/' + name + (path ? '/' + path : ''); - const negate = (str: string) => '!' + str; +export function cleanNodeModules(rulePath: string): NodeJS.ReadWriteStream { + const rules = fs.readFileSync(rulePath, 'utf8') + .split(/\r?\n/g) + .map(line => line.trim()) + .filter(line => line && !/^#/.test(line)); - const allFilter = _filter(toGlob('**'), { restore: true }); - const globs = [toGlob('**')].concat(excludes.map(_.compose(negate, toGlob) as (x: string) => string)); + const excludes = rules.filter(line => !/^!/.test(line)).map(line => `!**/node_modules/${line}`); + const includes = rules.filter(line => /^!/.test(line)).map(line => `**/node_modules/${line.substr(1)}`); const input = es.through(); - const nodeModuleInput = input.pipe(allFilter); - let output: NodeJS.ReadWriteStream = nodeModuleInput.pipe(_filter(globs)); + const output = es.merge( + input.pipe(_filter(['**', ...excludes])), + input.pipe(_filter(includes)) + ); - if (includes) { - const includeGlobs = includes.map(toGlob); - output = es.merge(output, nodeModuleInput.pipe(_filter(includeGlobs))); - } - - output = output.pipe(allFilter.restore); return es.duplex(input, output); } diff --git a/build/package.json b/build/package.json index a99b2181cce..49495b1c1e5 100644 --- a/build/package.json +++ b/build/package.json @@ -39,7 +39,7 @@ "minimist": "^1.2.0", "request": "^2.85.0", "tslint": "^5.9.1", - "typescript": "3.4.1", + "typescript": "3.4.5", "vsce": "1.48.0", "xml2js": "^0.4.17" }, diff --git a/build/yarn.lock b/build/yarn.lock index 83ba3d2381c..3d6f612789b 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -1894,10 +1894,10 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" -typescript@3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.1.tgz#b6691be11a881ffa9a05765a205cb7383f3b63c6" - integrity sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q== +typescript@3.4.5: + version "3.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99" + integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5" diff --git a/extensions/clojure/cgmanifest.json b/extensions/clojure/cgmanifest.json index 1a9d69c5633..3a72fefb369 100644 --- a/extensions/clojure/cgmanifest.json +++ b/extensions/clojure/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "atom/language-clojure", "repositoryUrl": "https://github.com/atom/language-clojure", - "commitHash": "ecc790326bc8e14220e4d2d72a392a30876c3219" + "commitHash": "de877502aa4a77ccdc2c7f0c9180436aea3effff" } }, "license": "MIT", - "version": "0.22.6", + "version": "0.22.7", "description": "The file syntaxes/clojure.tmLanguage.json was derived from the Atom package https://github.com/atom/language-clojure which was originally converted from the TextMate bundle https://github.com/mmcgrana/textmate-clojure." } ], diff --git a/extensions/clojure/syntaxes/clojure.tmLanguage.json b/extensions/clojure/syntaxes/clojure.tmLanguage.json index 3d059513af0..29c25edfa24 100644 --- a/extensions/clojure/syntaxes/clojure.tmLanguage.json +++ b/extensions/clojure/syntaxes/clojure.tmLanguage.json @@ -4,7 +4,7 @@ "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-clojure/commit/ecc790326bc8e14220e4d2d72a392a30876c3219", + "version": "https://github.com/atom/language-clojure/commit/de877502aa4a77ccdc2c7f0c9180436aea3effff", "name": "Clojure", "scopeName": "source.clojure", "patterns": [ @@ -83,7 +83,7 @@ "name": "constant.numeric.ratio.clojure" }, { - "match": "(-?\\d+[rR][0-9a-zA-Z]+)", + "match": "(-?\\d+[rR]\\w+)", "name": "constant.numeric.arbitrary-radix.clojure" }, { @@ -116,17 +116,17 @@ ] }, "keyword": { - "match": "(?<=(\\s|\\(|\\[|\\{)):[a-zA-Z0-9\\#\\.\\-\\_\\:\\+\\=\\>\\<\\/\\!\\?\\*]+(?=(\\s|\\)|\\]|\\}|\\,))", + "match": "(?<=(\\s|\\(|\\[|\\{)):[\\w\\#\\.\\-\\_\\:\\+\\=\\>\\<\\/\\!\\?\\*]+(?=(\\s|\\)|\\]|\\}|\\,))", "name": "constant.keyword.clojure" }, "keyfn": { "patterns": [ { - "match": "(?<=(\\s|\\(|\\[|\\{))(if(-[-a-z\\?]*)?|when(-[-a-z]*)?|for(-[-a-z]*)?|cond|do|let(-[-a-z\\?]*)?|binding|loop|recur|fn|throw[a-z\\-]*|try|catch|finally|([a-z]*case))(?=(\\s|\\)|\\]|\\}))", + "match": "(?<=(\\s|\\(|\\[|\\{))(if(-[-\\p{Ll}\\?]*)?|when(-[-\\p{Ll}]*)?|for(-[-\\p{Ll}]*)?|cond|do|let(-[-\\p{Ll}\\?]*)?|binding|loop|recur|fn|throw[\\p{Ll}\\-]*|try|catch|finally|([\\p{Ll}]*case))(?=(\\s|\\)|\\]|\\}))", "name": "storage.control.clojure" }, { - "match": "(?<=(\\s|\\(|\\[|\\{))(declare-?|(in-)?ns|import|use|require|load|compile|(def[a-z\\-]*))(?=(\\s|\\)|\\]|\\}))", + "match": "(?<=(\\s|\\(|\\[|\\{))(declare-?|(in-)?ns|import|use|require|load|compile|(def[\\p{Ll}\\-]*))(?=(\\s|\\)|\\]|\\}))", "name": "keyword.control.clojure" } ] @@ -309,7 +309,7 @@ "include": "#dynamic-variables" }, { - "match": "([a-zA-Z\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)", + "match": "([\\p{L}\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)", "name": "entity.global.clojure" }, { @@ -387,7 +387,7 @@ "namespace-symbol": { "patterns": [ { - "match": "([a-zA-Z\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)/", + "match": "([\\p{L}\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)/", "captures": { "1": { "name": "meta.symbol.namespace.clojure" @@ -399,13 +399,13 @@ "symbol": { "patterns": [ { - "match": "([a-zA-Z\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)", + "match": "([\\p{L}\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)", "name": "meta.symbol.clojure" } ] }, "var": { - "match": "(?<=(\\s|\\(|\\[|\\{)\\#)'[a-zA-Z0-9\\.\\-\\_\\:\\+\\=\\>\\<\\/\\!\\?\\*]+(?=(\\s|\\)|\\]|\\}))", + "match": "(?<=(\\s|\\(|\\[|\\{)\\#)'[\\w\\.\\-\\_\\:\\+\\=\\>\\<\\/\\!\\?\\*]+(?=(\\s|\\)|\\]|\\}))", "name": "meta.var.clojure" }, "vector": { diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index bb9690a07d4..e9f9e255f33 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -96,6 +96,14 @@ { "fileMatch": "/.vscode/extensions.json", "url": "vscode://schemas/extensions" + }, + { + "fileMatch": "/.devcontainer/devcontainer.json", + "url": "./schemas/devContainer.schema.json" + }, + { + "fileMatch": "/.devcontainer.json", + "url": "./schemas/devContainer.schema.json" } ] }, diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json new file mode 100644 index 00000000000..d0ee0901351 --- /dev/null +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -0,0 +1,137 @@ +{ + "$schema": "http://json-schema.org/schema#", + "description": "Defines a dev container", + "allowComments": true, + "type": "object", + "definitions": { + "devContainerCommon": { + "properties": { + "name": { + "type": "string", + "description": "A name to show for the workspace folder." + }, + "extensions": { + "type": "array", + "description": "An array of extensions that should be installed into the container.", + "items": { + "type": "string" + } + }, + "postCreateCommand": { + "type": ["string", "array"], + "description": "A command to run after creating the container. If this is a single string, it will be run in a shell. If this is an array of strings, it will be run as a single command without shell.", + "items": { + "type": "string" + } + }, + "devPort": { + "type": "integer", + "description": "The port VS Code can use to connect to its backend." + } + } + }, + "nonComposeBase": { + "properties": { + "appPort": { + "type": ["integer", "string", "array"], + "description": "Application ports that are exposed by the container. This can be a single port or an array of ports. Each port can be a number or a string. A number is mapped to the same port on the host. A string is passed to Docker unchanged and can be used to map ports differently, e.g. \"8000:8010\".", + "items": { + "type": ["integer", "string"] + } + }, + "runArgs": { + "type": "array", + "description": "The arguments required when starting in the container.", + "items": { + "type": "string" + } + }, + "shutdownAction": { + "type": "string", + "enum": ["none", "stopContainer"], + "description": "Action to take when VS Code is shutting down. The default is to stop the container." + }, + "overrideCommand": { + "type": "boolean", + "description": "Whether to overwrite the command specified in the image. The default is true." + } + } + }, + "dockerFileContainer": { + "properties": { + "dockerFile": { + "type": "string", + "description": "The location of the Dockerfile that defines the contents of the container. The path is relative to the folder containing the `devcontainer.json` file." + }, + "context": { + "type": "string", + "description": "The location of the context folder for building the Docker image. The path is relative to the folder containing the `devcontainer.json` file." + } + }, + "required": ["dockerFile"] + }, + "imageContainer": { + "properties": { + "image": { + "type": "string", + "description": "The docker image that will be used to create the container." + } + }, + "required": ["image"] + }, + "composeContainer": { + "properties": { + "dockerComposeFile": { + "type": ["string", "array"], + "description": "The name of the docker-compose file(s) used to start the services.", + "items": { + "type": "string" + } + }, + "service": { + "type": "string", + "description": "The service you want to work on." + }, + "workspaceFolder": { + "type": "string", + "description": "The path of the workspace folder inside the container." + }, + "shutdownAction": { + "type": "string", + "enum": ["none", "stopCompose"], + "description": "Action to take when VS Code is shutting down. The default is to stop the containers." + } + }, + "required": ["dockerComposeFile", "service", "workspaceFolder"] + } + }, + "allOf": [ + { + "oneOf": [ + { + "allOf": [ + { + "oneOf": [ + { + "$ref": "#/definitions/dockerFileContainer" + }, + { + "$ref": "#/definitions/imageContainer" + } + ] + }, + { + "$ref": "#/definitions/nonComposeBase" + } + ] + }, + { + "$ref": "#/definitions/composeContainer" + } + ] + }, + { + "$ref": "#/definitions/devContainerCommon" + } + ] +} \ No newline at end of file diff --git a/extensions/cpp/cgmanifest.json b/extensions/cpp/cgmanifest.json index 3ab00d30368..dcbf1f65e2f 100644 --- a/extensions/cpp/cgmanifest.json +++ b/extensions/cpp/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "jeff-hykin/cpp-textmate-grammar", "repositoryUrl": "https://github.com/jeff-hykin/cpp-textmate-grammar", - "commitHash": "84a65f7cce43f15aceaf1854c5bcc779c8575fe7" + "commitHash": "e33a317ccd0babba4b07ffc042ab9796e6412ddc" } }, "license": "MIT", - "version": "1.7.6", + "version": "1.8.15", "description": "The files syntaxes/c.json and syntaxes/c++.json were derived from https://github.com/atom/language-c which was originally converted from the C TextMate bundle https://github.com/textmate/c.tmbundle." }, { diff --git a/extensions/cpp/language-configuration.json b/extensions/cpp/language-configuration.json index e50df32dd11..b0b1de59d45 100644 --- a/extensions/cpp/language-configuration.json +++ b/extensions/cpp/language-configuration.json @@ -13,7 +13,8 @@ { "open": "{", "close": "}" }, { "open": "(", "close": ")" }, { "open": "'", "close": "'", "notIn": ["string", "comment"] }, - { "open": "\"", "close": "\"", "notIn": ["string"] } + { "open": "\"", "close": "\"", "notIn": ["string"] }, + { "open": "/*", "close": " */", "notIn": ["string", "comment"] } ], "surroundingPairs": [ ["{", "}"], diff --git a/extensions/cpp/syntaxes/c.tmLanguage.json b/extensions/cpp/syntaxes/c.tmLanguage.json index b7213b949df..925c7bb92ac 100644 --- a/extensions/cpp/syntaxes/c.tmLanguage.json +++ b/extensions/cpp/syntaxes/c.tmLanguage.json @@ -4,7 +4,7 @@ "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/jeff-hykin/cpp-textmate-grammar/commit/d450ac8fb4bd1750389acfd88be341e1a91a02f3", + "version": "https://github.com/jeff-hykin/cpp-textmate-grammar/commit/e33a317ccd0babba4b07ffc042ab9796e6412ddc", "name": "C", "scopeName": "source.c", "patterns": [ @@ -21,7 +21,10 @@ "include": "#comments" }, { - "match": "\\b(break|case|continue|default|do|else|for|goto|if|_Pragma|return|switch|while)\\b", + "include": "#switch_statement" + }, + { + "match": "\\b(break|continue|do|else|for|goto|if|_Pragma|return|while)\\b", "name": "keyword.control.c" }, { @@ -367,105 +370,13 @@ ], "repository": { "probably_a_parameter": { - "include": "#probably_a_parameter" - }, - "member_access": { - "match": "((?:[a-zA-Z_]\\w*|(?<=\\]|\\)))\\s*)(?:((?:\\.\\*|\\.))|((?:->\\*|->)))((?:[a-zA-Z_]\\w*\\s*(?-mix:(?:(?:\\.\\*|\\.))|(?:(?:->\\*|->)))\\s*)*)\\s*(\\b(?!(?:void|char|short|int|signed|unsigned|long|float|double|bool|_Bool|_Complex|_Imaginary|u_char|u_short|u_int|u_long|ushort|uint|u_quad_t|quad_t|qaddr_t|caddr_t|daddr_t|div_t|dev_t|fixpt_t|blkcnt_t|blksize_t|gid_t|in_addr_t|in_port_t|ino_t|key_t|mode_t|nlink_t|id_t|pid_t|off_t|segsz_t|swblk_t|uid_t|id_t|clock_t|size_t|ssize_t|time_t|useconds_t|suseconds_t|pthread_attr_t|pthread_cond_t|pthread_condattr_t|pthread_mutex_t|pthread_mutexattr_t|pthread_once_t|pthread_rwlock_t|pthread_rwlockattr_t|pthread_t|pthread_key_t|int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|int_least8_t|int_least16_t|int_least32_t|int_least64_t|uint_least8_t|uint_least16_t|uint_least32_t|uint_least64_t|int_fast8_t|int_fast16_t|int_fast32_t|int_fast64_t|uint_fast8_t|uint_fast16_t|uint_fast32_t|uint_fast64_t|intptr_t|uintptr_t|intmax_t|intmax_t|uintmax_t|uintmax_t|memory_order|atomic_bool|atomic_char|atomic_schar|atomic_uchar|atomic_short|atomic_ushort|atomic_int|atomic_uint|atomic_long|atomic_ulong|atomic_llong|atomic_ullong|atomic_char16_t|atomic_char32_t|atomic_wchar_t|atomic_int_least8_t|atomic_uint_least8_t|atomic_int_least16_t|atomic_uint_least16_t|atomic_int_least32_t|atomic_uint_least32_t|atomic_int_least64_t|atomic_uint_least64_t|atomic_int_fast8_t|atomic_uint_fast8_t|atomic_int_fast16_t|atomic_uint_fast16_t|atomic_int_fast32_t|atomic_uint_fast32_t|atomic_int_fast64_t|atomic_uint_fast64_t|atomic_intptr_t|atomic_uintptr_t|atomic_size_t|atomic_ptrdiff_t|atomic_intmax_t|atomic_uintmax_t))[a-zA-Z_]\\w*\\b(?!\\())", + "match": "(?<=(?:[a-zA-Z_0-9] |[&*>\\]\\)]))\\s*([a-zA-Z_]\\w*)\\s*(?=(?:\\[\\]\\s*)?(?:,|\\)))", "captures": { "1": { - "name": "variable.other.object.access.c" - }, - "2": { - "name": "punctuation.separator.dot-access.c" - }, - "3": { - "name": "punctuation.separator.pointer-access.c" - }, - "4": { - "patterns": [ - { - "include": "#member_access" - }, - { - "include": "#method_access" - }, - { - "match": "((?:[a-zA-Z_]\\w*|(?<=\\]|\\)))\\s*)(?:((?:\\.\\*|\\.))|((?:->\\*|->)))", - "captures": { - "1": { - "name": "variable.other.object.access.c" - }, - "2": { - "name": "punctuation.separator.dot-access.c" - }, - "3": { - "name": "punctuation.separator.pointer-access.c" - } - } - } - ] - }, - "5": { - "name": "variable.other.member.c" + "name": "variable.parameter.probably.c" } } }, - "method_access": { - "contentName": "meta.function-call.member", - "begin": "((?:[a-zA-Z_]\\w*|(?<=\\]|\\)))\\s*)(?:((?:\\.\\*|\\.))|((?:->\\*|->)))((?:[a-zA-Z_]\\w*\\s*(?-mix:(?:(?:\\.\\*|\\.))|(?:(?:->\\*|->)))\\s*)*)\\s*([a-zA-Z_]\\w*)(\\()", - "beginCaptures": { - "1": { - "name": "variable.other.object.access.c" - }, - "2": { - "name": "punctuation.separator.dot-access.c" - }, - "3": { - "name": "punctuation.separator.pointer-access.c" - }, - "4": { - "patterns": [ - { - "include": "#member_access" - }, - { - "include": "#method_access" - }, - { - "match": "((?:[a-zA-Z_]\\w*|(?<=\\]|\\)))\\s*)(?:((?:\\.\\*|\\.))|((?:->\\*|->)))", - "captures": { - "1": { - "name": "variable.other.object.access.c" - }, - "2": { - "name": "punctuation.separator.dot-access.c" - }, - "3": { - "name": "punctuation.separator.pointer-access.c" - } - } - } - ] - }, - "5": { - "name": "entity.name.function.member.c" - }, - "6": { - "name": "punctuation.section.arguments.begin.bracket.round.function.member.c" - } - }, - "end": "(\\))", - "endCaptures": { - "1": { - "name": "punctuation.section.arguments.end.bracket.round.function.member.c" - } - }, - "patterns": [ - { - "include": "#function-call-innards" - } - ] - }, "access-method": { "name": "meta.function-call.member.c", "begin": "([a-zA-Z_][a-zA-Z_0-9]*|(?<=[\\]\\)]))\\s*(?:(\\.)|(->))((?:(?:[a-zA-Z_][a-zA-Z_0-9]*)\\s*(?:(?:\\.)|(?:->)))*)\\s*([a-zA-Z_][a-zA-Z_0-9]*)(\\()", @@ -659,13 +570,13 @@ } }, "match": "^// =(\\s*.*?)\\s*=\\s*$\\n?", - "name": "comment.line.banner.cpp.c" + "name": "comment.line.banner.c" }, { "begin": "(^[ \\t]+)?(?=//)", "beginCaptures": { "1": { - "name": "punctuation.whitespace.comment.leading.cpp.c" + "name": "punctuation.whitespace.comment.leading.c" } }, "end": "(?!\\G)", @@ -674,11 +585,11 @@ "begin": "//", "beginCaptures": { "0": { - "name": "punctuation.definition.comment.cpp.c" + "name": "punctuation.definition.comment.c" } }, "end": "(?=\\n)", - "name": "comment.line.double-slash.cpp.c", + "name": "comment.line.double-slash.c", "patterns": [ { "include": "#line_continuation_character" @@ -713,16 +624,8 @@ } ] }, - "numbers": { - "patterns": [ - { - "match": "\\b((0(x|X)[0-9a-fA-F]([0-9a-fA-F']*[0-9a-fA-F])?)|(0(b|B)[01]([01']*[01])?)|(([0-9]([0-9']*[0-9])?\\.?[0-9]*([0-9']*[0-9])?)|(\\.[0-9]([0-9']*[0-9])?))((e|E)(\\+|-)?[0-9]([0-9']*[0-9])?)?)(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?\\b", - "name": "constant.numeric.c" - } - ] - }, "parens": { - "name": "punctuation.section.parens.c", + "name": "meta.parens.c", "begin": "\\(", "beginCaptures": { "0": { @@ -742,7 +645,7 @@ ] }, "parens-block": { - "name": "punctuation.section.parens.block.c", + "name": "meta.parens.block.c", "begin": "\\(", "beginCaptures": { "0": { @@ -761,7 +664,7 @@ }, { "match": "(?-mix:(?\\[\\]=]))", + "patterns": [ + { + "name": "meta.head.switch.c", + "begin": "\\G ?", + "end": "((?:\\{|(?=;)))", + "endCaptures": { + "1": { + "name": "punctuation.section.block.begin.bracket.curly.switch.c" + } + }, + "patterns": [ + { + "include": "#switch_conditional_parentheses" + }, + { + "include": "$base" + } + ] + }, + { + "name": "meta.body.switch.c", + "begin": "(?<=\\{)", + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.section.block.end.bracket.curly.switch.c" + } + }, + "patterns": [ + { + "include": "#default_statement" + }, + { + "include": "#case_statement" + }, + { + "include": "$base" + }, + { + "include": "#block_innards" + } + ] + }, + { + "name": "meta.tail.switch.c", + "begin": "(?<=})[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", + "patterns": [ + { + "include": "$base" + } + ] + } + ] + }, + "switch_conditional_parentheses": { + "name": "meta.conditional.switch.c", + "begin": "(\\()", + "beginCaptures": { + "1": { + "name": "punctuation.section.parens.begin.bracket.round.conditional.switch.c" + } + }, + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.section.parens.end.bracket.round.conditional.switch.c" + } + }, + "patterns": [ + { + "include": "#conditional_context" + } + ] + }, + "static_assert": { + "begin": "(static_assert|_Static_assert)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "keyword.other.static_assert.c" + }, + "2": { + "name": "punctuation.section.arguments.begin.bracket.round.c" + } + }, + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.section.arguments.end.bracket.round.c" + } + }, + "patterns": [ + { + "name": "meta.static_assert.message.c", + "begin": "(,)\\s*(?=(?:L|u8|u|U\\s*\\\")?)", + "beginCaptures": { + "1": { + "name": "comma.c punctuation.separator.delimiter.c" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#string_context" + }, + { + "include": "#string_context_c" + } + ] + }, + { + "include": "#function_call_context_c" + } + ] + }, + "conditional_context": { + "patterns": [ + { + "include": "$base" + }, + { + "include": "#block_innards" + } + ] + }, + "member_access": { + "match": "((?:[a-zA-Z_]\\w*|(?<=\\]|\\)))\\s*)(?:((?:\\.\\*|\\.))|((?:->\\*|->)))((?:[a-zA-Z_]\\w*\\s*(?-mix:(?:(?:\\.\\*|\\.))|(?:(?:->\\*|->)))\\s*)*)\\s*(\\b(?!(?:void|char|short|int|signed|unsigned|long|float|double|bool|_Bool|_Complex|_Imaginary|u_char|u_short|u_int|u_long|ushort|uint|u_quad_t|quad_t|qaddr_t|caddr_t|daddr_t|div_t|dev_t|fixpt_t|blkcnt_t|blksize_t|gid_t|in_addr_t|in_port_t|ino_t|key_t|mode_t|nlink_t|id_t|pid_t|off_t|segsz_t|swblk_t|uid_t|id_t|clock_t|size_t|ssize_t|time_t|useconds_t|suseconds_t|pthread_attr_t|pthread_cond_t|pthread_condattr_t|pthread_mutex_t|pthread_mutexattr_t|pthread_once_t|pthread_rwlock_t|pthread_rwlockattr_t|pthread_t|pthread_key_t|int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|int_least8_t|int_least16_t|int_least32_t|int_least64_t|uint_least8_t|uint_least16_t|uint_least32_t|uint_least64_t|int_fast8_t|int_fast16_t|int_fast32_t|int_fast64_t|uint_fast8_t|uint_fast16_t|uint_fast32_t|uint_fast64_t|intptr_t|uintptr_t|intmax_t|intmax_t|uintmax_t|uintmax_t|memory_order|atomic_bool|atomic_char|atomic_schar|atomic_uchar|atomic_short|atomic_ushort|atomic_int|atomic_uint|atomic_long|atomic_ulong|atomic_llong|atomic_ullong|atomic_char16_t|atomic_char32_t|atomic_wchar_t|atomic_int_least8_t|atomic_uint_least8_t|atomic_int_least16_t|atomic_uint_least16_t|atomic_int_least32_t|atomic_uint_least32_t|atomic_int_least64_t|atomic_uint_least64_t|atomic_int_fast8_t|atomic_uint_fast8_t|atomic_int_fast16_t|atomic_uint_fast16_t|atomic_int_fast32_t|atomic_uint_fast32_t|atomic_int_fast64_t|atomic_uint_fast64_t|atomic_intptr_t|atomic_uintptr_t|atomic_size_t|atomic_ptrdiff_t|atomic_intmax_t|atomic_uintmax_t))[a-zA-Z_]\\w*\\b(?!\\())", + "captures": { + "1": { + "name": "variable.other.object.access.c" + }, + "2": { + "name": "punctuation.separator.dot-access.c" + }, + "3": { + "name": "punctuation.separator.pointer-access.c" + }, + "4": { + "patterns": [ + { + "include": "#member_access" + }, + { + "include": "#method_access" + }, + { + "match": "((?:[a-zA-Z_]\\w*|(?<=\\]|\\)))\\s*)(?:((?:\\.\\*|\\.))|((?:->\\*|->)))", + "captures": { + "1": { + "name": "variable.other.object.access.c" + }, + "2": { + "name": "punctuation.separator.dot-access.c" + }, + "3": { + "name": "punctuation.separator.pointer-access.c" + } + } + } + ] + }, + "5": { + "name": "variable.other.member.c" + } + } + }, + "method_access": { + "contentName": "meta.function-call.member", + "begin": "((?:[a-zA-Z_]\\w*|(?<=\\]|\\)))\\s*)(?:((?:\\.\\*|\\.))|((?:->\\*|->)))((?:[a-zA-Z_]\\w*\\s*(?-mix:(?:(?:\\.\\*|\\.))|(?:(?:->\\*|->)))\\s*)*)\\s*([a-zA-Z_]\\w*)(\\()", + "beginCaptures": { + "1": { + "name": "variable.other.object.access.c" + }, + "2": { + "name": "punctuation.separator.dot-access.c" + }, + "3": { + "name": "punctuation.separator.pointer-access.c" + }, + "4": { + "patterns": [ + { + "include": "#member_access" + }, + { + "include": "#method_access" + }, + { + "match": "((?:[a-zA-Z_]\\w*|(?<=\\]|\\)))\\s*)(?:((?:\\.\\*|\\.))|((?:->\\*|->)))", + "captures": { + "1": { + "name": "variable.other.object.access.c" + }, + "2": { + "name": "punctuation.separator.dot-access.c" + }, + "3": { + "name": "punctuation.separator.pointer-access.c" + } + } + } + ] + }, + "5": { + "name": "entity.name.function.member.c" + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.function.member.c" + } + }, + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.section.arguments.end.bracket.round.function.member.c" + } + }, + "patterns": [ + { + "include": "#function-call-innards" + } + ] + }, + "numbers": { + "begin": "(?(?-mix:[a-zA-Z_$][\\w$]*)))\t # macro name\n(?:\n (\\()\n\t(\n\t \\s* \\g \\s*\t\t # first argument\n\t ((,) \\s* \\g \\s*)* # additional arguments\n\t (?:\\.\\.\\.)?\t\t\t# varargs ellipsis?\n\t)\n (\\))\n)?", - "beginCaptures": { - "1": { - "name": "keyword.control.directive.define.cpp" - }, - "2": { - "name": "punctuation.definition.directive.cpp" - }, - "3": { - "name": "entity.name.function.preprocessor.cpp" - }, - "5": { - "name": "punctuation.definition.parameters.begin.cpp" - }, - "6": { - "name": "variable.parameter.preprocessor.cpp" - }, - "8": { - "name": "punctuation.separator.parameters.cpp" - }, - "9": { - "name": "punctuation.definition.parameters.end.cpp" - } - }, - "end": "(?=(?://|/\\*))|(?", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.cpp" - } - }, - "name": "string.quoted.other.lt-gt.include.cpp" - } - ] + "include": "#meta_preprocessor_include" }, { - "include": "#pragma-mark" + "include": "#pragma_mark" }, { - "name": "meta.preprocessor.cpp", - "begin": "^\\s*((#)\\s*line)\\b", - "beginCaptures": { - "1": { - "name": "keyword.control.directive.line.cpp" - }, - "2": { - "name": "punctuation.definition.directive.cpp" - } - }, - "end": "(?=(?://|/\\*))|(?\\[\\]=]))", + "patterns": [ + { + "name": "meta.head.switch.cpp", + "begin": "\\G ?", + "end": "((?:\\{|(?=;)))", + "endCaptures": { + "1": { + "name": "punctuation.section.block.begin.bracket.curly.switch.cpp" + } + }, + "patterns": [ + { + "include": "#switch_conditional_parentheses" + }, + { + "include": "$base" + } + ] + }, + { + "name": "meta.body.switch.cpp", + "begin": "(?<=\\{)", + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.section.block.end.bracket.curly.switch.cpp" + } + }, + "patterns": [ + { + "include": "#default_statement" + }, + { + "include": "#case_statement" + }, + { + "include": "$base" + }, + { + "include": "#block_innards" + } + ] + }, + { + "name": "meta.tail.switch.cpp", + "begin": "(?<=})[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", + "patterns": [ + { + "include": "$base" + } + ] + } + ] + }, + "cpp_attributes": { "name": "support.other.attribute.cpp", - "begin": "((?:\\[\\[|__attribute\\(\\(|__attribute__\\(\\(|__declspec\\())", + "begin": "(\\[\\[)", "beginCaptures": { "1": { "name": "punctuation.section.attribute.begin.cpp" } }, - "end": "((?:\\]\\]|\\)\\)|\\)))", + "end": "(\\]\\])", "endCaptures": { "1": { "name": "punctuation.section.attribute.end.cpp" @@ -717,28 +859,28 @@ }, "patterns": [ { - "include": "#attribute_cpp" + "include": "#attributes_context" }, { "begin": "\\(", "end": "\\)", "patterns": [ { - "include": "#attribute_cpp" + "include": "#attributes_context" }, { - "include": "#strings_c" + "include": "#string_context_c" } ] }, { - "match": "(using)\\s+((?,\\w])*>\\s*", + "match": "(?:,\\w])*>\\s*", "captures": { "0": { "name": "meta.template.call.cpp", "patterns": [ { - "include": "#storage_types" - }, - { - "include": "#constants" - }, - { - "include": "#scope_resolution" - }, - { - "match": "(?)", + "endCaptures": { + "1": { + "name": "punctuation.section.angle-brackets.end.template.call.cpp" + } + }, + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, "template_isolated_definition": { "match": "(?\\s*$)", "captures": { @@ -809,19 +1145,7 @@ "name": "meta.template.definition.cpp", "patterns": [ { - "include": "#scope_resolution" - }, - { - "include": "#template_definition_argument" - }, - { - "include": "#template_argument_defaulted" - }, - { - "include": "#template_call_innards" - }, - { - "include": "$base" + "include": "#template_definition_context" } ] }, @@ -863,52 +1187,17 @@ }, "patterns": [ { - "include": "#storage_types" - }, - { - "include": "#constants" - }, - { - "include": "#scope_resolution" - }, - { - "match": "(?|$))", + "match": "(?:(?:\\s*((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)|((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*\\s+)+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*))|((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)\\s*(\\.\\.\\.)\\s*((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*))\\s*(?:(,)|(?=>|$))", "captures": { "1": { "name": "storage.type.template.argument.$1.cpp" @@ -948,7 +1237,7 @@ } }, "scope_resolution": { - "match": "((?:[a-zA-Z_]\\w*\\s*(?:(?-mix:(?:<(?:[\\s<>,\\w])*>\\s*)))?::)*\\s*)([a-zA-Z_]\\w*)\\s*(?:(<(?:[\\s<>,\\w])*>\\s*))?(::)", + "match": "((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*\\s*(?:(?-mix:(?:(?:,\\w])*>\\s*)))?::)*\\s*)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)\\s*(?:((?:,\\w])*>\\s*))?(::)", "captures": { "1": { "patterns": [ @@ -958,36 +1247,13 @@ ] }, "2": { - "name": "entity.name.namespace.scope-resolution.cpp" + "name": "entity.name.type.namespace.scope-resolution.cpp" }, "3": { "name": "meta.template.call.cpp", "patterns": [ { - "include": "#storage_types" - }, - { - "include": "#constants" - }, - { - "include": "#scope_resolution" - }, - { - "match": "(?:,\\w])*>\\s*)))?::)*\\s*)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)\\s*(?:((?:,\\w])*>\\s*))?(::)))?\\s*(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*(?:(?-mix:(?:(?:,\\w])*>\\s*)))?(?![\\w<:.])", + "captures": { + "0": { + "name": "entity.name.type.cpp meta.qualified_type.cpp", + "patterns": [ + { + "match": "(?:class|struct|union|enum)", + "name": "storage.type.$0.cpp" + }, + { + "include": "#function_type" + }, + { + "include": "#storage_types" + }, + { + "include": "#number_literal" + }, + { + "include": "#string_context_c" + }, + { + "include": "#comma" + } + ] + }, "1": { - "name": "keyword.operator.functionlike.cpp keyword.operator.sizeof.cpp" + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] }, "2": { - "name": "punctuation.section.arguments.begin.bracket.round.operator.sizeof.cpp keyword.operator.functionlike.cpp keyword.operator.sizeof.cpp" + "name": "meta.scope-resolution.cpp" + }, + "3": { + "patterns": [ + { + "include": "#scope_resolution" + } + ] + }, + "4": { + "name": "entity.name.type.namespace.scope-resolution.cpp" + }, + "5": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + "6": { + "name": "punctuation.separator.namespace.access.cpp" } - }, - "end": "(\\))", - "endCaptures": { + } + }, + "type_alias": { + "match": "(using)\\s*(?!namespace)(\\s*(?:,\\w])*>\\s*)))?::)*\\s*)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)\\s*(?:((?:,\\w])*>\\s*))?(::)))?\\s*(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*(?:(?-mix:(?:(?:,\\w])*>\\s*)))?(?![\\w<:.]))\\s*(\\=)\\s*(typename)?\\s*((?:(?-mix:(?:(?:,\\w])*>\\s*)))?::)*\\s*)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)\\s*(?:((?:,\\w])*>\\s*))?(::)))?\\s*(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*(?:(?-mix:(?:(?:,\\w])*>\\s*)))?(?![\\w<:.]))|(.+(?:,\\w])*>\\s*)))?::)*\\s*)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)\\s*(?:((?:,\\w])*>\\s*))?(\\()", "beginCaptures": { "1": { - "name": "keyword.operator.functionlike.cpp keyword.operator.alignas.cpp" + "patterns": [ + { + "include": "#scope_resolution" + } + ] }, "2": { - "name": "punctuation.section.arguments.begin.bracket.round.operator.alignas.cpp keyword.operator.functionlike.cpp keyword.operator.alignas.cpp" + "name": "entity.name.function.call.cpp" + }, + "3": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + "4": { + "name": "punctuation.section.arguments.begin.bracket.round.cpp" } }, "end": "(\\))", "endCaptures": { "1": { - "name": "punctuation.section.arguments.end.bracket.round.operator.alignas.cpp keyword.operator.functionlike.cpp keyword.operator.alignas.cpp" + "name": "punctuation.section.arguments.end.bracket.round.cpp" } }, "patterns": [ { - "include": "$base" + "include": "#function_call_context_c" } ] }, - "typeid_operator": { - "contentName": "meta.arguments.operator.typeid", - "begin": "((?|\\+\\+|\\-\\-|\\+|\\-|!|not|~|compl|\\*|&|sizeof|sizeof\\.\\.\\.|new|new\\[\\]|delete|delete\\[\\]|\\.\\*|\\->\\*|\\*|\\/|%|\\+|\\-|<<|>>|<=>|<|<=|>|>=|==|!=|not_eq|&|bitand|\\^|xor|\\||bitor|&&|and|\\|\\||or|\\?:|throw|=|\\+=|\\-=|\\*=|\\/=|%=|<<=|>>=|&=|and_eq|\\^=|xor_eq|\\|=|or_eq|,|alignof|alignas|typeid|noexcept|static_cast|dynamic_cast|const_cast|reinterpret_cast)|(?:throw|while|for|do|if|else|goto|switch|try|catch|return|break|case|continue|default))\\s*\\()((?:(?-mix:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*|::)++|(?<=operator)(?:\\+\\+|\\-\\-|\\(\\)|\\[\\]|\\->|\\+\\+|\\-\\-|\\+|\\-|!|~|\\*|&|new|new\\[\\]|delete|delete\\[\\]|\\->\\*|\\*|\\/|%|\\+|\\-|<<|>>|<=>|<|<=|>|>=|==|!=|&|\\^|\\||&&|\\|\\||=|\\+=|\\-=|\\*=|\\/=|%=|<<=|>>=|&=|\\^=|\\|=|,)))\\s*(\\()", "beginCaptures": { "1": { - "name": "keyword.operator.functionlike.cpp keyword.operator.typeid.cpp" + "name": "entity.name.function.cpp" }, "2": { - "name": "punctuation.section.arguments.begin.bracket.round.operator.typeid.cpp keyword.operator.functionlike.cpp keyword.operator.typeid.cpp" + "name": "punctuation.section.parameters.begin.bracket.round.cpp" } }, - "end": "(\\))", + "end": "(\\)|:)", "endCaptures": { "1": { - "name": "punctuation.section.arguments.end.bracket.round.operator.typeid.cpp keyword.operator.functionlike.cpp keyword.operator.typeid.cpp" + "name": "punctuation.section.parameters.end.bracket.round.cpp" } }, "patterns": [ { - "include": "$base" - } - ] - }, - "decltype_specifier": { - "contentName": "meta.arguments.decltype", - "begin": "((?:,\\w])*>\\s*)))?::)*\\s*)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)\\s*(?:((?:,\\w])*>\\s*))?(::)))?\\s*(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*(?:(?-mix:(?:(?:,\\w])*>\\s*)))?(?![\\w<:.]))\\s*(((?:\\*\\s*)*)((?:\\&\\s*?){0,2})\\s*)(\\()(\\*)\\s*((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)?\\s*(?:(\\[)(\\w*)(\\])\\s*)*(\\))\\s*(\\()", + "beginCaptures": { + "1": { + "name": "entity.name.type.cpp meta.qualified_type.cpp", + "patterns": [ + { + "match": "(?:class|struct|union|enum)", + "name": "storage.type.$0.cpp" + }, + { + "include": "#function_type" + }, + { + "include": "#storage_types" + }, + { + "include": "#number_literal" + }, + { + "include": "#string_context_c" + }, + { + "include": "#comma" + } + ] + }, + "2": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "3": { + "name": "meta.scope-resolution.cpp" + }, + "4": { + "patterns": [ + { + "include": "#scope_resolution" + } + ] + }, + "5": { + "name": "entity.name.type.namespace.scope-resolution.cpp" + }, + "6": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_context" + } + ] + }, + "7": { + "name": "punctuation.separator.namespace.access.cpp" + }, + "9": { + "name": "storage.modifier.pointer.cpp" + }, + "10": { + "name": "storage.modifier.reference.cpp" + }, + "11": { + "name": "punctuation.section.parens.begin.bracket.round.function.pointer.cpp" + }, + "12": { + "name": "punctuation.definition.function.pointer.dereference.cpp" + }, + "13": { + "name": "variable.other.definition.pointer.function.cpp" + }, + "14": { + "name": "punctuation.definition.begin.bracket.square.cpp" + }, + "15": { + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "16": { + "name": "punctuation.definition.end.bracket.square.cpp" + }, + "17": { + "name": "punctuation.section.parens.end.bracket.round.function.pointer.cpp" + }, + "18": { + "name": "punctuation.section.parameters.begin.bracket.round.function.pointer.cpp" + } + }, + "end": "(\\))\\s*(?=[{=,);]|\\n)(?!\\()", + "endCaptures": { + "1": { + "name": "punctuation.section.parameters.end.bracket.round.function.pointer.cpp" + } + }, + "patterns": [ + { + "include": "#parameter_struct" + }, + { + "include": "#probably_a_parameter" + }, + { + "include": "#function_context_c" + } + ] + }, "probably_a_parameter": { - "match": "(?:([a-zA-Z_]\\w*\\s*(?==))|((?<=\\w |\\*\\/|[&*>\\]\\)]|\\.\\.\\.)\\s*[a-zA-Z_]\\w*\\s*(?=(?:\\[\\]\\s*)?(?:,|\\)))))", + "match": "(?:((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*\\s*(?==))|((?<=\\w |\\*\\/|[&*>\\]\\)]|\\.\\.\\.)\\s*(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*\\s*(?=(?:\\[\\]\\s*)?(?:,|\\)))))", "captures": { "1": { "name": "variable.parameter.defaulted.cpp" @@ -1230,7 +1881,7 @@ }, "operator_overload": { "name": "meta.function.definition.parameters.operator-overload.cpp", - "begin": "(operator)((?:\\s*(?:\\+\\+|\\-\\-|\\(\\)|\\[\\]|\\->|\\+\\+|\\-\\-|\\+|\\-|!|~|\\*|&|\\->\\*|\\*|\\/|%|\\+|\\-|<<|>>|<=>|<|<=|>|>=|==|!=|&|\\^|\\||&&|\\|\\||=|\\+=|\\-=|\\*=|\\/=|%=|<<=|>>=|&=|\\^=|\\|=|,)|\\s+(?:(?:new|new\\[\\]|delete|delete\\[\\])|(?:[a-zA-Z_]\\w*\\s*(?:(?-mix:(?:<(?:[\\s<>,\\w])*>\\s*)))?::)*[a-zA-Z_]\\w*\\s*(?:&)?)))\\s*(\\()", + "begin": "(operator)((?:\\s*(?:\\+\\+|\\-\\-|\\(\\)|\\[\\]|\\->|\\+\\+|\\-\\-|\\+|\\-|!|~|\\*|&|\\->\\*|\\*|\\/|%|\\+|\\-|<<|>>|<=>|<|<=|>|>=|==|!=|&|\\^|\\||&&|\\|\\||=|\\+=|\\-=|\\*=|\\/=|%=|<<=|>>=|&=|\\^=|\\|=|,)|\\s+(?:(?:new|new\\[\\]|delete|delete\\[\\])|(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*\\s*(?:(?-mix:(?:(?:,\\w])*>\\s*)))?::)*(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*\\s*(?:&)?)))\\s*(\\()", "beginCaptures": { "1": { "name": "keyword.other.operator.overload.cpp" @@ -1258,12 +1909,12 @@ "include": "#probably_a_parameter" }, { - "include": "#function-innards-c" + "include": "#function_context_c" } ] }, "member_access": { - "match": "(?:((?\\*|->)))((?:[a-zA-Z_]\\w*\\s*(?-mix:(?:(?:\\.\\*|\\.))|(?:(?:->\\*|->)))\\s*)*)\\s*(\\b(?!auto[^(?-mix:\\w)]|void[^(?-mix:\\w)]|char[^(?-mix:\\w)]|short[^(?-mix:\\w)]|int[^(?-mix:\\w)]|signed[^(?-mix:\\w)]|unsigned[^(?-mix:\\w)]|long[^(?-mix:\\w)]|float[^(?-mix:\\w)]|double[^(?-mix:\\w)]|bool[^(?-mix:\\w)]|wchar_t[^(?-mix:\\w)]|u_char[^(?-mix:\\w)]|u_short[^(?-mix:\\w)]|u_int[^(?-mix:\\w)]|u_long[^(?-mix:\\w)]|ushort[^(?-mix:\\w)]|uint[^(?-mix:\\w)]|u_quad_t[^(?-mix:\\w)]|quad_t[^(?-mix:\\w)]|qaddr_t[^(?-mix:\\w)]|caddr_t[^(?-mix:\\w)]|daddr_t[^(?-mix:\\w)]|div_t[^(?-mix:\\w)]|dev_t[^(?-mix:\\w)]|fixpt_t[^(?-mix:\\w)]|blkcnt_t[^(?-mix:\\w)]|blksize_t[^(?-mix:\\w)]|gid_t[^(?-mix:\\w)]|in_addr_t[^(?-mix:\\w)]|in_port_t[^(?-mix:\\w)]|ino_t[^(?-mix:\\w)]|key_t[^(?-mix:\\w)]|mode_t[^(?-mix:\\w)]|nlink_t[^(?-mix:\\w)]|id_t[^(?-mix:\\w)]|pid_t[^(?-mix:\\w)]|off_t[^(?-mix:\\w)]|segsz_t[^(?-mix:\\w)]|swblk_t[^(?-mix:\\w)]|uid_t[^(?-mix:\\w)]|id_t[^(?-mix:\\w)]|clock_t[^(?-mix:\\w)]|size_t[^(?-mix:\\w)]|ssize_t[^(?-mix:\\w)]|time_t[^(?-mix:\\w)]|useconds_t[^(?-mix:\\w)]|suseconds_t[^(?-mix:\\w)]|pthread_attr_t[^(?-mix:\\w)]|pthread_cond_t[^(?-mix:\\w)]|pthread_condattr_t[^(?-mix:\\w)]|pthread_mutex_t[^(?-mix:\\w)]|pthread_mutexattr_t[^(?-mix:\\w)]|pthread_once_t[^(?-mix:\\w)]|pthread_rwlock_t[^(?-mix:\\w)]|pthread_rwlockattr_t[^(?-mix:\\w)]|pthread_t[^(?-mix:\\w)]|pthread_key_t[^(?-mix:\\w)]|int8_t[^(?-mix:\\w)]|int16_t[^(?-mix:\\w)]|int32_t[^(?-mix:\\w)]|int64_t[^(?-mix:\\w)]|uint8_t[^(?-mix:\\w)]|uint16_t[^(?-mix:\\w)]|uint32_t[^(?-mix:\\w)]|uint64_t[^(?-mix:\\w)]|int_least8_t[^(?-mix:\\w)]|int_least16_t[^(?-mix:\\w)]|int_least32_t[^(?-mix:\\w)]|int_least64_t[^(?-mix:\\w)]|uint_least8_t[^(?-mix:\\w)]|uint_least16_t[^(?-mix:\\w)]|uint_least32_t[^(?-mix:\\w)]|uint_least64_t[^(?-mix:\\w)]|int_fast8_t[^(?-mix:\\w)]|int_fast16_t[^(?-mix:\\w)]|int_fast32_t[^(?-mix:\\w)]|int_fast64_t[^(?-mix:\\w)]|uint_fast8_t[^(?-mix:\\w)]|uint_fast16_t[^(?-mix:\\w)]|uint_fast32_t[^(?-mix:\\w)]|uint_fast64_t[^(?-mix:\\w)]|intptr_t[^(?-mix:\\w)]|uintptr_t[^(?-mix:\\w)]|intmax_t[^(?-mix:\\w)]|intmax_t[^(?-mix:\\w)]|uintmax_t[^(?-mix:\\w)]|uintmax_t[^(?-mix:\\w)])[a-zA-Z_]\\w*\\b(?!\\())", + "match": "(?:((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*\\s*(?-mix:(?:(?:\\.\\*|\\.))|(?:(?:->\\*|->)))\\s*)*)\\s*(\\b(?!auto[^(?-mix:\\w)]|void[^(?-mix:\\w)]|char[^(?-mix:\\w)]|short[^(?-mix:\\w)]|int[^(?-mix:\\w)]|signed[^(?-mix:\\w)]|unsigned[^(?-mix:\\w)]|long[^(?-mix:\\w)]|float[^(?-mix:\\w)]|double[^(?-mix:\\w)]|bool[^(?-mix:\\w)]|wchar_t[^(?-mix:\\w)]|u_char[^(?-mix:\\w)]|u_short[^(?-mix:\\w)]|u_int[^(?-mix:\\w)]|u_long[^(?-mix:\\w)]|ushort[^(?-mix:\\w)]|uint[^(?-mix:\\w)]|u_quad_t[^(?-mix:\\w)]|quad_t[^(?-mix:\\w)]|qaddr_t[^(?-mix:\\w)]|caddr_t[^(?-mix:\\w)]|daddr_t[^(?-mix:\\w)]|div_t[^(?-mix:\\w)]|dev_t[^(?-mix:\\w)]|fixpt_t[^(?-mix:\\w)]|blkcnt_t[^(?-mix:\\w)]|blksize_t[^(?-mix:\\w)]|gid_t[^(?-mix:\\w)]|in_addr_t[^(?-mix:\\w)]|in_port_t[^(?-mix:\\w)]|ino_t[^(?-mix:\\w)]|key_t[^(?-mix:\\w)]|mode_t[^(?-mix:\\w)]|nlink_t[^(?-mix:\\w)]|id_t[^(?-mix:\\w)]|pid_t[^(?-mix:\\w)]|off_t[^(?-mix:\\w)]|segsz_t[^(?-mix:\\w)]|swblk_t[^(?-mix:\\w)]|uid_t[^(?-mix:\\w)]|id_t[^(?-mix:\\w)]|clock_t[^(?-mix:\\w)]|size_t[^(?-mix:\\w)]|ssize_t[^(?-mix:\\w)]|time_t[^(?-mix:\\w)]|useconds_t[^(?-mix:\\w)]|suseconds_t[^(?-mix:\\w)]|int8_t[^(?-mix:\\w)]|int16_t[^(?-mix:\\w)]|int32_t[^(?-mix:\\w)]|int64_t[^(?-mix:\\w)]|uint8_t[^(?-mix:\\w)]|uint16_t[^(?-mix:\\w)]|uint32_t[^(?-mix:\\w)]|uint64_t[^(?-mix:\\w)]|int_least8_t[^(?-mix:\\w)]|int_least16_t[^(?-mix:\\w)]|int_least32_t[^(?-mix:\\w)]|int_least64_t[^(?-mix:\\w)]|uint_least8_t[^(?-mix:\\w)]|uint_least16_t[^(?-mix:\\w)]|uint_least32_t[^(?-mix:\\w)]|uint_least64_t[^(?-mix:\\w)]|int_fast8_t[^(?-mix:\\w)]|int_fast16_t[^(?-mix:\\w)]|int_fast32_t[^(?-mix:\\w)]|int_fast64_t[^(?-mix:\\w)]|uint_fast8_t[^(?-mix:\\w)]|uint_fast16_t[^(?-mix:\\w)]|uint_fast32_t[^(?-mix:\\w)]|uint_fast64_t[^(?-mix:\\w)]|intptr_t[^(?-mix:\\w)]|uintptr_t[^(?-mix:\\w)]|intmax_t[^(?-mix:\\w)]|intmax_t[^(?-mix:\\w)]|uintmax_t[^(?-mix:\\w)]|uintmax_t[^(?-mix:\\w)])(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*\\b(?!\\())", "captures": { "1": { "name": "variable.language.this.cpp" @@ -1280,11 +1931,11 @@ "5": { "patterns": [ { - "match": "(?<=(?:\\.\\*|\\.|->|->\\*))\\s*(?-mix:(?:(?:(?\\*|->))))", + "match": "(?<=(?:\\.\\*|\\.|->|->\\*))\\s*(?-mix:(?:(?:(?\\*|->))))", "name": "variable.other.object.property.cpp" }, { - "match": "(?:((?\\*|->)))", + "match": "(?:((?\\*|->)))", "captures": { "1": { "name": "variable.language.this.cpp" @@ -1315,7 +1966,7 @@ }, "method_access": { "contentName": "meta.function-call.member", - "begin": "(?:((?\\*|->)))((?:[a-zA-Z_]\\w*\\s*(?-mix:(?:(?:\\.\\*|\\.))|(?:(?:->\\*|->)))\\s*)*)\\s*([a-zA-Z_]\\w*)(\\()", + "begin": "(?:((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*\\s*(?-mix:(?:(?:\\.\\*|\\.))|(?:(?:->\\*|->)))\\s*)*)\\s*((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)(\\()", "beginCaptures": { "1": { "name": "variable.language.this.cpp" @@ -1332,11 +1983,11 @@ "5": { "patterns": [ { - "match": "(?<=(?:\\.\\*|\\.|->|->\\*))\\s*(?-mix:(?:(?:(?\\*|->))))", + "match": "(?<=(?:\\.\\*|\\.|->|->\\*))\\s*(?-mix:(?:(?:(?\\*|->))))", "name": "variable.other.object.property.cpp" }, { - "match": "(?:((?\\*|->)))", + "match": "(?:((?\\*|->)))", "captures": { "1": { "name": "variable.language.this.cpp" @@ -1375,12 +2026,124 @@ }, "patterns": [ { - "include": "#function-call-innards-c" + "include": "#function_call_context_c" } ] }, + "using_namespace": { + "name": "meta.using-namespace.cpp", + "begin": "(?:,\\w])*>\\s*)))?::)*\\s*))?((?:,\\w])*>\\s*)))?::)*\\s*)\\s*(?:(?:((?\\[\\]=]))", + "patterns": [ + { + "name": "meta.head.namespace.cpp", + "begin": "\\G ?", + "end": "((?:\\{|(?=;)))", + "endCaptures": { + "1": { + "name": "punctuation.section.block.begin.bracket.curly.namespace.cpp" + } + } + }, + { + "name": "meta.body.namespace.cpp", + "begin": "(?<=\\{)", + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.section.block.end.bracket.curly.namespace.cpp" + } + }, + "patterns": [ + { + "include": "$base" + } + ] + }, + { + "name": "meta.tail.namespace.cpp", + "begin": "(?<=})[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", + "patterns": [ + { + "include": "$base" + } + ] + } + ] + }, + "macro_argument": { + "match": "##(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*(?!\\w)", + "name": "variable.other.macro.argument.cpp" + }, "lambdas": { - "begin": "((?:(?<=[^\\s]|^)(?:,\\w])*>\\s*)))?::)*\\s*)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)\\s*(?:((?:,\\w])*>\\s*))?(::)))?\\s*((?\\[\\]=]))", "endCaptures": { - "0": { - "name": "punctuation.section.block.end.bracket.curly.cpp" + "1": { + "name": "punctuation.terminator.statement.cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cpp" } }, - "name": "meta.block.cpp", "patterns": [ { - "captures": { + "name": "meta.head.enum.cpp", + "begin": "\\G ?", + "end": "((?:\\{|(?=;)))", + "endCaptures": { "1": { - "name": "support.function.any-method.cpp" - }, - "2": { - "name": "punctuation.definition.parameters.cpp" + "name": "punctuation.section.block.begin.bracket.curly.enum.cpp" } }, - "match": "(?x)\n(\n (?!while|for|do|if|else|switch|catch|return)\n (?:\\b[A-Za-z_][A-Za-z0-9_]*+\\b|::)*+ # actual name\n)\n\\s*(\\() # opening bracket", - "name": "meta.function-call.cpp" + "patterns": [ + { + "include": "$base" + } + ] + }, + { + "name": "meta.body.enum.cpp", + "begin": "(?<=\\{)", + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.section.block.end.bracket.curly.enum.cpp" + } + }, + "patterns": [ + { + "include": "#enumerator_list" + }, + { + "include": "#comments_context" + }, + { + "include": "#comma" + }, + { + "include": "#semicolon" + } + ] + }, + { + "name": "meta.tail.enum.cpp", + "begin": "(?<=})[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", + "patterns": [ + { + "include": "$base" + } + ] + } + ] + }, + "inhertance_context": { + "patterns": [ + { + "match": ",", + "name": "comma.cpp punctuation.separator.delimiter.inhertance.cpp" + }, + { + "match": "(?:,\\w])*>\\s*)))?::)*\\s*)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)\\s*(?:(?:(?:,\\w])*>\\s*))?(?:::)))?\\s*(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*(?:(?-mix:(?:(?:,\\w])*>\\s*)))?(?![\\w<:.]))))", + "captures": { + "1": { + "name": "entity.name.type.inherited.cpp" + } + } + } + ] + }, + "class_block": { + "name": "meta.block.class.cpp", + "begin": "((((?:,\\w])*>\\s*)))?::)*\\s*)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)\\s*(?:(?:(?:,\\w])*>\\s*))?(?:::)))?\\s*(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*(?:(?-mix:(?:(?:,\\w])*>\\s*)))?(?![\\w<:.]))))+)*))?))", + "beginCaptures": { + "1": { + "name": "meta.head.class.cpp" + }, + "3": { + "name": "storage.type.$3.cpp" + }, + "4": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "5": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "6": { + "name": "entity.name.type.$3.cpp" + }, + "7": { + "name": "storage.type.modifier.final.cpp" + }, + "8": { + "name": "colon.cpp punctuation.separator.inhertance.cpp" + }, + "9": { + "patterns": [ + { + "include": "#inhertance_context" + } + ] + } + }, + "end": "(?:(?:(?<=})\\s*(;)|(;))|(?=[;>\\[\\]=]))", + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cpp" + } + }, + "patterns": [ + { + "name": "meta.head.class.cpp", + "begin": "\\G ?", + "end": "((?:\\{|(?=;)))", + "endCaptures": { + "1": { + "name": "punctuation.section.block.begin.bracket.curly.class.cpp" + } + }, + "patterns": [ + { + "include": "#preprocessor_context" + }, + { + "include": "#inhertance_context" + }, + { + "include": "#template_call_range" + }, + { + "include": "#comments_context" + } + ] + }, + { + "name": "meta.body.class.cpp", + "begin": "(?<=\\{)", + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.section.block.end.bracket.curly.class.cpp" + } + }, + "patterns": [ + { + "include": "#function_pointer" + }, + { + "include": "#constructor_context" + }, + { + "include": "$base" + } + ] + }, + { + "name": "meta.tail.class.cpp", + "begin": "(?<=})[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", + "patterns": [ + { + "include": "$base" + } + ] + } + ] + }, + "struct_block": { + "name": "meta.block.struct.cpp", + "begin": "((((?:,\\w])*>\\s*)))?::)*\\s*)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)\\s*(?:(?:(?:,\\w])*>\\s*))?(?:::)))?\\s*(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*(?:(?-mix:(?:(?:,\\w])*>\\s*)))?(?![\\w<:.]))))+)*))?))", + "beginCaptures": { + "1": { + "name": "meta.head.struct.cpp" + }, + "3": { + "name": "storage.type.$3.cpp" + }, + "4": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "5": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "6": { + "name": "entity.name.type.$3.cpp" + }, + "7": { + "name": "storage.type.modifier.final.cpp" + }, + "8": { + "name": "colon.cpp punctuation.separator.inhertance.cpp" + }, + "9": { + "patterns": [ + { + "include": "#inhertance_context" + } + ] + } + }, + "end": "(?:(?:(?<=})\\s*(;)|(;))|(?=[;>\\[\\]=]))", + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cpp" + } + }, + "patterns": [ + { + "name": "meta.head.struct.cpp", + "begin": "\\G ?", + "end": "((?:\\{|(?=;)))", + "endCaptures": { + "1": { + "name": "punctuation.section.block.begin.bracket.curly.struct.cpp" + } + }, + "patterns": [ + { + "include": "#preprocessor_context" + }, + { + "include": "#inhertance_context" + }, + { + "include": "#template_call_range" + }, + { + "include": "#comments_context" + } + ] + }, + { + "name": "meta.body.struct.cpp", + "begin": "(?<=\\{)", + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.section.block.end.bracket.curly.struct.cpp" + } + }, + "patterns": [ + { + "include": "#function_pointer" + }, + { + "include": "#constructor_context" + }, + { + "include": "$base" + } + ] + }, + { + "name": "meta.tail.struct.cpp", + "begin": "(?<=})[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", + "patterns": [ + { + "include": "$base" + } + ] + } + ] + }, + "union_block": { + "name": "meta.block.union.cpp", + "begin": "((((?:,\\w])*>\\s*)))?::)*\\s*)(?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*)\\s*(?:(?:(?:,\\w])*>\\s*))?(?:::)))?\\s*(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F]))(?:(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U000[0-9a-fA-F])))*(?:(?-mix:(?:(?:,\\w])*>\\s*)))?(?![\\w<:.]))))+)*))?))", + "beginCaptures": { + "1": { + "name": "meta.head.union.cpp" + }, + "3": { + "name": "storage.type.$3.cpp" + }, + "4": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "5": { + "patterns": [ + { + "include": "#attributes_context" + }, + { + "include": "#number_literal" + } + ] + }, + "6": { + "name": "entity.name.type.$3.cpp" + }, + "7": { + "name": "storage.type.modifier.final.cpp" + }, + "8": { + "name": "colon.cpp punctuation.separator.inhertance.cpp" + }, + "9": { + "patterns": [ + { + "include": "#inhertance_context" + } + ] + } + }, + "end": "(?:(?:(?<=})\\s*(;)|(;))|(?=[;>\\[\\]=]))", + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cpp" + } + }, + "patterns": [ + { + "name": "meta.head.union.cpp", + "begin": "\\G ?", + "end": "((?:\\{|(?=;)))", + "endCaptures": { + "1": { + "name": "punctuation.section.block.begin.bracket.curly.union.cpp" + } + }, + "patterns": [ + { + "include": "#preprocessor_context" + }, + { + "include": "#inhertance_context" + }, + { + "include": "#template_call_range" + }, + { + "include": "#comments_context" + } + ] + }, + { + "name": "meta.body.union.cpp", + "begin": "(?<=\\{)", + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.section.block.end.bracket.curly.union.cpp" + } + }, + "patterns": [ + { + "include": "#function_pointer" + }, + { + "include": "#constructor_context" + }, + { + "include": "$base" + } + ] + }, + { + "name": "meta.tail.union.cpp", + "begin": "(?<=})[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", + "patterns": [ + { + "include": "$base" + } + ] + } + ] + }, + "extern_block": { + "name": "meta.block.extern.cpp", + "begin": "((\\bextern)(?=\\s*\\\"))", + "beginCaptures": { + "1": { + "name": "meta.head.extern.cpp" + }, + "2": { + "name": "storage.type.extern.cpp" + } + }, + "end": "(?:(?:(?<=})\\s*(;)|(;))|(?=[;>\\[\\]=]))", + "endCaptures": { + "1": { + "name": "punctuation.terminator.statement.cpp" + }, + "2": { + "name": "punctuation.terminator.statement.cpp" + } + }, + "patterns": [ + { + "name": "meta.head.extern.cpp", + "begin": "\\G ?", + "end": "((?:\\{|(?=;)))", + "endCaptures": { + "1": { + "name": "punctuation.section.block.begin.bracket.curly.extern.cpp" + } + }, + "patterns": [ + { + "include": "$base" + } + ] + }, + { + "name": "meta.body.extern.cpp", + "begin": "(?<=\\{)", + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.section.block.end.bracket.curly.extern.cpp" + } + }, + "patterns": [ + { + "include": "$base" + } + ] + }, + { + "name": "meta.tail.extern.cpp", + "begin": "(?<=})[\\s\\n]*", + "end": "[\\s\\n]*(?=;)", + "patterns": [ + { + "include": "$base" + } + ] }, { "include": "$base" } ] }, - "constructor": { + "hacky_fix_for_stray_directive": { + "match": "(?(?-mix:[a-zA-Z_$][\\w$]*)))\t # macro name\n(?:\n (\\()\n\t(\n\t \\s* \\g \\s*\t\t # first argument\n\t ((,) \\s* \\g \\s*)* # additional arguments\n\t (?:\\.\\.\\.)?\t\t\t# varargs ellipsis?\n\t)\n (\\))\n)?", + "beginCaptures": { + "1": { + "name": "keyword.control.directive.define.cpp" + }, + "2": { + "name": "punctuation.definition.directive.cpp" + }, + "3": { + "name": "entity.name.function.preprocessor.cpp" + }, + "5": { + "name": "punctuation.definition.parameters.begin.cpp" + }, + "6": { + "name": "variable.parameter.preprocessor.cpp" + }, + "8": { + "name": "punctuation.separator.parameters.cpp" + }, + "9": { + "name": "punctuation.definition.parameters.end.cpp" + } + }, + "end": "(?=(?://|/\\*))|(?", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.cpp" + } + }, + "name": "string.quoted.other.lt-gt.include.cpp" + } + ] + }, + "meta_preprocessor_line": { + "name": "meta.preprocessor.cpp", + "begin": "^\\s*((#)\\s*line)\\b", + "beginCaptures": { + "1": { + "name": "keyword.control.directive.line.cpp" + }, + "2": { + "name": "punctuation.definition.directive.cpp" + } + }, + "end": "(?=(?://|/\\*))|(?,\\w])*>\\s*)))?::)*\\s*))?((?,\\w])*>\\s*)))?::)*\\s*)\\s*(?:((?\\[\\]=]))", - "patterns": [ - { - "name": "meta.head.namespace.cpp", - "begin": "\\G", - "end": "((?:\\{|(?=;)))", - "endCaptures": { - "1": { - "name": "punctuation.section.block.begin.bracket.curly.namespace.cpp" - } - } - }, - { - "name": "meta.body.namespace.cpp", - "begin": "(?<=\\{)", - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.section.block.end.bracket.curly.namespace.cpp" - } - }, - "patterns": [ - { - "include": "$base" - } - ] - }, - { - "name": "meta.tail.namespace.cpp", - "begin": "(?<=})[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", - "patterns": [ - { - "include": "$base" - } - ] - } - ] + "include": "#type_alias" }, { - "name": "meta.block.class.cpp", - "begin": "((((?\\[\\]=]))", - "endCaptures": { - "1": { - "name": "punctuation.terminator.statement.cpp" - }, - "2": { - "name": "punctuation.terminator.statement.cpp" - } - }, - "patterns": [ - { - "name": "meta.head.class.cpp", - "begin": "\\G", - "end": "((?:\\{|(?=;)))", - "endCaptures": { - "1": { - "name": "punctuation.section.block.begin.bracket.curly.class.cpp" - } - }, - "patterns": [ - { - "match": ",", - "name": "comma.cpp punctuation.separator.delimiter.inhertance.cpp" - }, - { - "match": "(?:private|protected|public)", - "name": "storage.type.modifier.access.$0.cpp" - }, - { - "match": "(?<=private|protected|public|,|:)\\s*(?!(?:private|protected|public))((?)", - "endCaptures": { - "1": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "patterns": [ - { - "include": "#storage_types" - }, - { - "include": "#constants" - }, - { - "include": "#scope_resolution" - }, - { - "match": "(?\\[\\]=]))", - "endCaptures": { - "1": { - "name": "punctuation.terminator.statement.cpp" - }, - "2": { - "name": "punctuation.terminator.statement.cpp" - } - }, - "patterns": [ - { - "name": "meta.head.struct.cpp", - "begin": "\\G", - "end": "((?:\\{|(?=;)))", - "endCaptures": { - "1": { - "name": "punctuation.section.block.begin.bracket.curly.struct.cpp" - } - }, - "patterns": [ - { - "match": ",", - "name": "comma.cpp punctuation.separator.delimiter.inhertance.cpp" - }, - { - "match": "(?:private|protected|public)", - "name": "storage.type.modifier.access.$0.cpp" - }, - { - "match": "(?<=private|protected|public|,|:)\\s*(?!(?:private|protected|public))((?)", - "endCaptures": { - "1": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "patterns": [ - { - "include": "#storage_types" - }, - { - "include": "#constants" - }, - { - "include": "#scope_resolution" - }, - { - "match": "(?\\[\\]=]))", - "endCaptures": { - "1": { - "name": "punctuation.terminator.statement.cpp" - }, - "2": { - "name": "punctuation.terminator.statement.cpp" - } - }, - "patterns": [ - { - "name": "meta.head.union.cpp", - "begin": "\\G", - "end": "((?:\\{|(?=;)))", - "endCaptures": { - "1": { - "name": "punctuation.section.block.begin.bracket.curly.union.cpp" - } - }, - "patterns": [ - { - "match": ",", - "name": "comma.cpp punctuation.separator.delimiter.inhertance.cpp" - }, - { - "match": "(?:private|protected|public)", - "name": "storage.type.modifier.access.$0.cpp" - }, - { - "match": "(?<=private|protected|public|,|:)\\s*(?!(?:private|protected|public))((?)", - "endCaptures": { - "1": { - "name": "punctuation.section.angle-brackets.end.template.call.cpp" - } - }, - "patterns": [ - { - "include": "#storage_types" - }, - { - "include": "#constants" - }, - { - "include": "#scope_resolution" - }, - { - "match": "(?,\\w])*>\\s*)))?::)*\\s*)([a-zA-Z_]\\w*)\\s*(?:(<(?:[\\s<>,\\w])*>\\s*))?(::)))?\\s*((?\\[\\]=]))", - "endCaptures": { - "1": { - "name": "punctuation.terminator.statement.cpp" - }, - "2": { - "name": "punctuation.terminator.statement.cpp" - } - }, - "patterns": [ - { - "name": "meta.head.enum.cpp", - "begin": "\\G", - "end": "((?:\\{|(?=;)))", - "endCaptures": { - "1": { - "name": "punctuation.section.block.begin.bracket.curly.enum.cpp" - } - } - }, - { - "name": "meta.body.enum.cpp", - "begin": "(?<=\\{)", - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.section.block.end.bracket.curly.enum.cpp" - } - }, - "patterns": [ - { - "include": "$base" - } - ] - }, - { - "name": "meta.tail.enum.cpp", - "begin": "(?<=})[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", - "patterns": [ - { - "include": "$base" - } - ] - } - ] + "include": "#union_block" }, { - "name": "meta.block.extern.cpp", - "begin": "((\\bextern)(?=\\s*\\\"))", - "beginCaptures": { - "1": { - "name": "meta.head.extern.cpp" - }, - "2": { - "name": "storage.type.extern.cpp" - } - }, - "end": "(?:(?:(?<=})\\s*(;)|(;))|(?=[;()>\\[\\]=]))", - "endCaptures": { - "1": { - "name": "punctuation.terminator.statement.cpp" - }, - "2": { - "name": "punctuation.terminator.statement.cpp" - } - }, - "patterns": [ - { - "name": "meta.head.extern.cpp", - "begin": "\\G", - "end": "((?:\\{|(?=;)))", - "endCaptures": { - "1": { - "name": "punctuation.section.block.begin.bracket.curly.extern.cpp" - } - }, - "patterns": [ - { - "include": "$base" - } - ] - }, - { - "name": "meta.body.extern.cpp", - "begin": "(?<=\\{)", - "end": "(\\})", - "endCaptures": { - "1": { - "name": "punctuation.section.block.end.bracket.curly.extern.cpp" - } - }, - "patterns": [ - { - "include": "$base" - } - ] - }, - { - "name": "meta.tail.extern.cpp", - "begin": "(?<=})[\\s\\n]*", - "end": "[\\s\\n]*(?=;)", - "patterns": [ - { - "include": "$base" - } - ] - }, - { - "include": "$base" - } - ] + "include": "#enum_block" + }, + { + "include": "#extern_block" } ] }, - "strings": { + "string_context": { "patterns": [ { "begin": "(u|u8|U|L)?\"", @@ -2379,7 +3247,7 @@ "name": "constant.character.escape.cpp" }, { - "include": "#string_placeholder-c" + "include": "#string_escapes_context_c" } ] }, @@ -2409,40 +3277,36 @@ } ] }, - "block-c": { + "block": { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.block.begin.bracket.curly.cpp" + } + }, + "end": "}|(?=\\s*#\\s*(?:elif|else|endif)\\b)", + "endCaptures": { + "0": { + "name": "punctuation.section.block.end.bracket.curly.cpp" + } + }, + "name": "meta.block.cpp", "patterns": [ { - "begin": "{", - "beginCaptures": { - "0": { - "name": "punctuation.section.block.begin.bracket.curly.cpp" - } - }, - "end": "}|(?=\\s*#\\s*(?:elif|else|endif)\\b)", - "endCaptures": { - "0": { - "name": "punctuation.section.block.end.bracket.curly.cpp" - } - }, - "name": "meta.block.cpp", - "patterns": [ - { - "include": "#block_innards-c" - } - ] + "include": "#block_context" } ] }, - "block_innards-c": { + "block_context": { "patterns": [ { - "include": "#preprocessor-rule-enabled-block" + "include": "#preprocessor_rule_enabled_block" }, { - "include": "#preprocessor-rule-disabled-block" + "include": "#preprocessor_rule_disabled_block" }, { - "include": "#preprocessor-rule-conditional-block" + "include": "#preprocessor_rule_conditional_block" }, { "include": "#method_access" @@ -2451,7 +3315,7 @@ "include": "#member_access" }, { - "include": "#c_function_call" + "include": "#function_call_c" }, { "name": "meta.initialization.cpp", @@ -2472,7 +3336,7 @@ }, "patterns": [ { - "include": "#function-call-innards-c" + "include": "#function_call_context_c" } ] }, @@ -2491,29 +3355,29 @@ }, "patterns": [ { - "include": "#block_innards-c" + "include": "#block_context" } ] }, { - "include": "#parens-block-c" + "include": "#parentheses_block" }, { "include": "$base" } ] }, - "c_function_call": { - "begin": "(?x)\n(?!(?:while|for|do|if|else|switch|catch|return|typeid|alignof|alignas|sizeof|and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|typeid|xor|xor_eq|alignof|alignas|constexpr|volatile|operator|(?:::)?new|(?:::)?delete)\\s*\\()\n(?=\n(?:[A-Za-z_][A-Za-z0-9_]*+|::)++\\s*(?-mix:(?:(?-mix:(?:<(?:[\\s<>,\\w])*>\\s*)))?)\\( # actual name\n|\n(?:(?<=operator)(?:[-*&<>=+!]+|\\(\\)|\\[\\]))\\s*\\(\n)", + "function_call_c": { + "begin": "(?x)\n(?!(?:while|for|do|if|else|switch|catch|return|typeid|alignof|alignas|sizeof|and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|typeid|xor|xor_eq|alignof|alignas|constexpr|volatile|operator|(?:::)?new|(?:::)?delete)\\s*\\()\n(?=\n(?:[A-Za-z_][A-Za-z0-9_]*+|::)++\\s*(?-mix:(?:(?-mix:(?:(?:,\\w])*>\\s*)))?)\\( # actual name\n|\n(?:(?<=operator)(?:[-*&<>=+!]+|\\(\\)|\\[\\]))\\s*\\(\n)", "end": "(?<=\\))(?!\\w)", "name": "meta.function-call.cpp", "patterns": [ { - "include": "#function-call-innards-c" + "include": "#function_call_context_c" } ] }, - "comments": { + "comments_context": { "patterns": [ { "captures": { @@ -2584,24 +3448,20 @@ "include": "#disabled" }, { - "include": "#pragma-mark" + "include": "#pragma_mark" } ] }, "line_continuation_character": { - "patterns": [ - { - "match": "(\\\\)\\n", - "captures": { - "1": { - "name": "constant.character.escape.line-continuation.cpp" - } - } + "match": "(\\\\)\\n", + "captures": { + "1": { + "name": "constant.character.escape.line-continuation.cpp" } - ] + } }, - "parens-c": { - "name": "punctuation.section.parens-c.cpp", + "parentheses": { + "name": "meta.parens.cpp", "begin": "\\(", "beginCaptures": { "0": { @@ -2620,8 +3480,8 @@ } ] }, - "parens-block-c": { - "name": "meta.block.parens.cpp", + "parentheses_block": { + "name": "meta.parens.block.cpp", "begin": "\\(", "beginCaptures": { "0": { @@ -2636,7 +3496,7 @@ }, "patterns": [ { - "include": "#block_innards-c" + "include": "#block_context" }, { "match": "(?-mix:(?=+!]+|\\(\\)|\\[\\]))\n)\n\\s*(\\()", - "beginCaptures": { - "1": { - "name": "entity.name.function.cpp" - }, - "2": { - "name": "punctuation.section.parameters.begin.bracket.round.cpp" - } - }, - "end": "(?-mix:\\)|:)", - "endCaptures": { - "0": { - "name": "punctuation.section.parameters.end.bracket.round.cpp" - } - }, - "patterns": [ - { - "include": "#probably_a_parameter" - }, - { - "include": "#function-innards-c" - } - ] + "include": "#legacy_function_definition" }, { "begin": "\\(", @@ -3873,7 +4676,7 @@ }, "patterns": [ { - "include": "#function-innards-c" + "include": "#function_context_c" } ] }, @@ -3882,13 +4685,13 @@ } ] }, - "function-call-innards-c": { + "function_call_context_c": { "patterns": [ { - "include": "#attribute_cpp" + "include": "#attributes_context" }, { - "include": "#comments" + "include": "#comments_context" }, { "include": "#storage_types" @@ -3903,7 +4706,7 @@ "include": "#operators" }, { - "begin": "(?x)\n(?!(?:while|for|do|if|else|switch|catch|return|typeid|alignof|alignas|sizeof|and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|typeid|xor|xor_eq|alignof|alignas)\\s*\\()\n(\n(?:new)\\s*((?-mix:(?:(?-mix:(?:<(?:[\\s<>,\\w])*>\\s*)))?)) # actual name\n|\n(?:(?<=operator)(?:[-*&<>=+!]+|\\(\\)|\\[\\]))\n)\n\\s*(\\()", + "begin": "(?x)\n(?!(?:while|for|do|if|else|switch|catch|return|typeid|alignof|alignas|sizeof|and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|typeid|xor|xor_eq|alignof|alignas)\\s*\\()\n(\n(?:new)\\s*((?-mix:(?:(?-mix:(?:(?:,\\w])*>\\s*)))?)) # actual name\n|\n(?:(?<=operator)(?:[-*&<>=+!]+|\\(\\)|\\[\\]))\n)\n\\s*(\\()", "beginCaptures": { "1": { "name": "keyword.operator.wordlike.cpp memory.cpp keyword.operator.new.cpp" @@ -3927,69 +4730,12 @@ }, "patterns": [ { - "include": "#function-call-innards-c" + "include": "#function_call_context_c" } ] }, { - "begin": "(?,\\w])*>\\s*)))?::)*\\s*)([a-zA-Z_]\\w*)\\s*(?:(<(?:[\\s<>,\\w])*>\\s*))?(\\()", - "beginCaptures": { - "1": { - "patterns": [ - { - "include": "#scope_resolution" - } - ] - }, - "2": { - "name": "entity.name.function.call.cpp" - }, - "3": { - "name": "meta.template.call.cpp", - "patterns": [ - { - "include": "#storage_types" - }, - { - "include": "#constants" - }, - { - "include": "#scope_resolution" - }, - { - "match": "(?", - "t": "source.c meta.block.c punctuation.section.parens.block.c keyword.operator.comparison.c", + "t": "source.c meta.block.c meta.parens.block.c keyword.operator.comparison.c", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -980,7 +980,7 @@ }, { "c": "0", - "t": "source.c meta.block.c punctuation.section.parens.block.c constant.numeric.c", + "t": "source.c meta.block.c meta.parens.block.c constant.numeric.decimal.c", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #09885A", @@ -991,7 +991,7 @@ }, { "c": ")", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.end.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.end.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1057,7 +1057,7 @@ }, { "c": "(", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.begin.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.begin.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1068,7 +1068,7 @@ }, { "c": "-", - "t": "source.c meta.block.c punctuation.section.parens.block.c keyword.operator.c", + "t": "source.c meta.block.c meta.parens.block.c keyword.operator.c", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1079,7 +1079,7 @@ }, { "c": "b", - "t": "source.c meta.block.c punctuation.section.parens.block.c", + "t": "source.c meta.block.c meta.parens.block.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1090,7 +1090,7 @@ }, { "c": "+", - "t": "source.c meta.block.c punctuation.section.parens.block.c keyword.operator.c", + "t": "source.c meta.block.c meta.parens.block.c keyword.operator.c", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1101,7 +1101,7 @@ }, { "c": "sqrt", - "t": "source.c meta.block.c punctuation.section.parens.block.c meta.function-call.c entity.name.function.c", + "t": "source.c meta.block.c meta.parens.block.c meta.function-call.c entity.name.function.c", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", @@ -1112,7 +1112,7 @@ }, { "c": "(", - "t": "source.c meta.block.c punctuation.section.parens.block.c meta.function-call.c punctuation.section.arguments.begin.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c meta.function-call.c punctuation.section.arguments.begin.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1123,7 +1123,7 @@ }, { "c": "determinant", - "t": "source.c meta.block.c punctuation.section.parens.block.c meta.function-call.c", + "t": "source.c meta.block.c meta.parens.block.c meta.function-call.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1134,7 +1134,7 @@ }, { "c": ")", - "t": "source.c meta.block.c punctuation.section.parens.block.c meta.function-call.c punctuation.section.arguments.end.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c meta.function-call.c punctuation.section.arguments.end.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1145,7 +1145,7 @@ }, { "c": ")", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.end.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.end.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1167,7 +1167,7 @@ }, { "c": "(", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.begin.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.begin.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1178,7 +1178,7 @@ }, { "c": "2", - "t": "source.c meta.block.c punctuation.section.parens.block.c constant.numeric.c", + "t": "source.c meta.block.c meta.parens.block.c constant.numeric.decimal.c", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #09885A", @@ -1189,7 +1189,7 @@ }, { "c": "*", - "t": "source.c meta.block.c punctuation.section.parens.block.c keyword.operator.c", + "t": "source.c meta.block.c meta.parens.block.c keyword.operator.c", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1200,7 +1200,7 @@ }, { "c": "a", - "t": "source.c meta.block.c punctuation.section.parens.block.c", + "t": "source.c meta.block.c meta.parens.block.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1211,7 +1211,7 @@ }, { "c": ")", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.end.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.end.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1266,7 +1266,7 @@ }, { "c": "(", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.begin.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.begin.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1277,7 +1277,7 @@ }, { "c": "-", - "t": "source.c meta.block.c punctuation.section.parens.block.c keyword.operator.c", + "t": "source.c meta.block.c meta.parens.block.c keyword.operator.c", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1288,7 +1288,7 @@ }, { "c": "b", - "t": "source.c meta.block.c punctuation.section.parens.block.c", + "t": "source.c meta.block.c meta.parens.block.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1299,7 +1299,7 @@ }, { "c": "-", - "t": "source.c meta.block.c punctuation.section.parens.block.c keyword.operator.c", + "t": "source.c meta.block.c meta.parens.block.c keyword.operator.c", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1310,7 +1310,7 @@ }, { "c": "sqrt", - "t": "source.c meta.block.c punctuation.section.parens.block.c meta.function-call.c entity.name.function.c", + "t": "source.c meta.block.c meta.parens.block.c meta.function-call.c entity.name.function.c", "r": { "dark_plus": "entity.name.function: #DCDCAA", "light_plus": "entity.name.function: #795E26", @@ -1321,7 +1321,7 @@ }, { "c": "(", - "t": "source.c meta.block.c punctuation.section.parens.block.c meta.function-call.c punctuation.section.arguments.begin.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c meta.function-call.c punctuation.section.arguments.begin.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1332,7 +1332,7 @@ }, { "c": "determinant", - "t": "source.c meta.block.c punctuation.section.parens.block.c meta.function-call.c", + "t": "source.c meta.block.c meta.parens.block.c meta.function-call.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1343,7 +1343,7 @@ }, { "c": ")", - "t": "source.c meta.block.c punctuation.section.parens.block.c meta.function-call.c punctuation.section.arguments.end.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c meta.function-call.c punctuation.section.arguments.end.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1354,7 +1354,7 @@ }, { "c": ")", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.end.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.end.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1376,7 +1376,7 @@ }, { "c": "(", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.begin.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.begin.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1387,7 +1387,7 @@ }, { "c": "2", - "t": "source.c meta.block.c punctuation.section.parens.block.c constant.numeric.c", + "t": "source.c meta.block.c meta.parens.block.c constant.numeric.decimal.c", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #09885A", @@ -1398,7 +1398,7 @@ }, { "c": "*", - "t": "source.c meta.block.c punctuation.section.parens.block.c keyword.operator.c", + "t": "source.c meta.block.c meta.parens.block.c keyword.operator.c", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1409,7 +1409,7 @@ }, { "c": "a", - "t": "source.c meta.block.c punctuation.section.parens.block.c", + "t": "source.c meta.block.c meta.parens.block.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1420,7 +1420,7 @@ }, { "c": ")", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.end.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.end.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1684,7 +1684,7 @@ }, { "c": "(", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.begin.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.begin.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1695,7 +1695,7 @@ }, { "c": "determinant", - "t": "source.c meta.block.c punctuation.section.parens.block.c", + "t": "source.c meta.block.c meta.parens.block.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1706,7 +1706,7 @@ }, { "c": "==", - "t": "source.c meta.block.c punctuation.section.parens.block.c keyword.operator.comparison.c", + "t": "source.c meta.block.c meta.parens.block.c keyword.operator.comparison.c", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1717,7 +1717,7 @@ }, { "c": "0", - "t": "source.c meta.block.c punctuation.section.parens.block.c constant.numeric.c", + "t": "source.c meta.block.c meta.parens.block.c constant.numeric.decimal.c", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #09885A", @@ -1728,7 +1728,7 @@ }, { "c": ")", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.end.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.end.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1849,7 +1849,7 @@ }, { "c": "(", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.begin.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.begin.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1860,7 +1860,7 @@ }, { "c": "2", - "t": "source.c meta.block.c punctuation.section.parens.block.c constant.numeric.c", + "t": "source.c meta.block.c meta.parens.block.c constant.numeric.decimal.c", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #09885A", @@ -1871,7 +1871,7 @@ }, { "c": "*", - "t": "source.c meta.block.c punctuation.section.parens.block.c keyword.operator.c", + "t": "source.c meta.block.c meta.parens.block.c keyword.operator.c", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -1882,7 +1882,7 @@ }, { "c": "a", - "t": "source.c meta.block.c punctuation.section.parens.block.c", + "t": "source.c meta.block.c meta.parens.block.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1893,7 +1893,7 @@ }, { "c": ")", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.end.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.end.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2212,7 +2212,7 @@ }, { "c": "(", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.begin.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.begin.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2223,7 +2223,7 @@ }, { "c": "2", - "t": "source.c meta.block.c punctuation.section.parens.block.c constant.numeric.c", + "t": "source.c meta.block.c meta.parens.block.c constant.numeric.decimal.c", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #09885A", @@ -2234,7 +2234,7 @@ }, { "c": "*", - "t": "source.c meta.block.c punctuation.section.parens.block.c keyword.operator.c", + "t": "source.c meta.block.c meta.parens.block.c keyword.operator.c", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -2245,7 +2245,7 @@ }, { "c": "a", - "t": "source.c meta.block.c punctuation.section.parens.block.c", + "t": "source.c meta.block.c meta.parens.block.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2256,7 +2256,7 @@ }, { "c": ")", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.end.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.end.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2377,7 +2377,7 @@ }, { "c": "(", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.begin.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.begin.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2388,7 +2388,7 @@ }, { "c": "2", - "t": "source.c meta.block.c punctuation.section.parens.block.c constant.numeric.c", + "t": "source.c meta.block.c meta.parens.block.c constant.numeric.decimal.c", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #09885A", @@ -2399,7 +2399,7 @@ }, { "c": "*", - "t": "source.c meta.block.c punctuation.section.parens.block.c keyword.operator.c", + "t": "source.c meta.block.c meta.parens.block.c keyword.operator.c", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -2410,7 +2410,7 @@ }, { "c": "a", - "t": "source.c meta.block.c punctuation.section.parens.block.c", + "t": "source.c meta.block.c meta.parens.block.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2421,7 +2421,7 @@ }, { "c": ")", - "t": "source.c meta.block.c punctuation.section.parens.block.c punctuation.section.parens.end.bracket.round.c", + "t": "source.c meta.block.c meta.parens.block.c punctuation.section.parens.end.bracket.round.c", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -2762,7 +2762,7 @@ }, { "c": "0", - "t": "source.c meta.block.c constant.numeric.c", + "t": "source.c meta.block.c constant.numeric.decimal.c", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #09885A", diff --git a/extensions/cpp/test/colorize-results/test_cc.json b/extensions/cpp/test/colorize-results/test_cc.json index a83541f6115..e53e3ede6e4 100644 --- a/extensions/cpp/test/colorize-results/test_cc.json +++ b/extensions/cpp/test/colorize-results/test_cc.json @@ -221,7 +221,7 @@ }, { "c": "(", - "t": "source.cpp punctuation.section.parens-c.cpp punctuation.section.parens.begin.bracket.round.cpp", + "t": "source.cpp meta.parens.cpp punctuation.section.parens.begin.bracket.round.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -232,7 +232,7 @@ }, { "c": "int", - "t": "source.cpp punctuation.section.parens-c.cpp storage.type.primitive.cpp", + "t": "source.cpp meta.parens.cpp storage.type.primitive.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -243,7 +243,7 @@ }, { "c": " i", - "t": "source.cpp punctuation.section.parens-c.cpp", + "t": "source.cpp meta.parens.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -254,7 +254,7 @@ }, { "c": "=", - "t": "source.cpp punctuation.section.parens-c.cpp keyword.operator.assignment.cpp", + "t": "source.cpp meta.parens.cpp keyword.operator.assignment.cpp", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -265,7 +265,7 @@ }, { "c": "0", - "t": "source.cpp punctuation.section.parens-c.cpp constant.numeric.decimal.cpp", + "t": "source.cpp meta.parens.cpp constant.numeric.decimal.cpp", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #09885A", @@ -276,7 +276,7 @@ }, { "c": ";", - "t": "source.cpp punctuation.section.parens-c.cpp punctuation.terminator.statement.cpp", + "t": "source.cpp meta.parens.cpp punctuation.terminator.statement.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -287,7 +287,7 @@ }, { "c": "i", - "t": "source.cpp punctuation.section.parens-c.cpp", + "t": "source.cpp meta.parens.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -298,7 +298,7 @@ }, { "c": "<", - "t": "source.cpp punctuation.section.parens-c.cpp keyword.operator.comparison.cpp", + "t": "source.cpp meta.parens.cpp keyword.operator.comparison.cpp", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -309,7 +309,7 @@ }, { "c": "num_candidate", - "t": "source.cpp punctuation.section.parens-c.cpp", + "t": "source.cpp meta.parens.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -320,7 +320,7 @@ }, { "c": ";", - "t": "source.cpp punctuation.section.parens-c.cpp punctuation.terminator.statement.cpp", + "t": "source.cpp meta.parens.cpp punctuation.terminator.statement.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -331,7 +331,7 @@ }, { "c": "++", - "t": "source.cpp punctuation.section.parens-c.cpp keyword.operator.increment.cpp", + "t": "source.cpp meta.parens.cpp keyword.operator.increment.cpp", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -342,7 +342,7 @@ }, { "c": "i", - "t": "source.cpp punctuation.section.parens-c.cpp", + "t": "source.cpp meta.parens.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -353,7 +353,7 @@ }, { "c": ")", - "t": "source.cpp punctuation.section.parens-c.cpp punctuation.section.parens.end.bracket.round.cpp", + "t": "source.cpp meta.parens.cpp punctuation.section.parens.end.bracket.round.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1189,13 +1189,13 @@ }, { "c": "std", - "t": "source.cpp meta.block.cpp meta.scope-resolution.cpp entity.name.namespace.scope-resolution.cpp", + "t": "source.cpp meta.block.cpp meta.scope-resolution.cpp entity.name.type.namespace.scope-resolution.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" + "hc_black": "entity.name.type: #4EC9B0" } }, { @@ -1771,118 +1771,8 @@ } }, { - "c": "asm", - "t": "source.cpp meta.block.cpp meta.function-call.cpp storage.type.asm.cpp", - "r": { - "dark_plus": "storage.type: #569CD6", - "light_plus": "storage.type: #0000FF", - "dark_vs": "storage.type: #569CD6", - "light_vs": "storage.type: #0000FF", - "hc_black": "storage.type: #569CD6" - } - }, - { - "c": "(", - "t": "source.cpp meta.block.cpp meta.function-call.cpp punctuation.section.parens.begin.bracket.round.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.cpp meta.block.cpp meta.function-call.cpp string.quoted.double.cpp punctuation.definition.string.begin.cpp", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "movw $0x38, ", - "t": "source.cpp meta.block.cpp meta.function-call.cpp string.quoted.double.cpp", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "%a", - "t": "source.cpp meta.block.cpp meta.function-call.cpp string.quoted.double.cpp constant.other.placeholder.cpp", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "x; ltr ", - "t": "source.cpp meta.block.cpp meta.function-call.cpp string.quoted.double.cpp", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "%a", - "t": "source.cpp meta.block.cpp meta.function-call.cpp string.quoted.double.cpp constant.other.placeholder.cpp", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "x", - "t": "source.cpp meta.block.cpp meta.function-call.cpp string.quoted.double.cpp", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.cpp meta.block.cpp meta.function-call.cpp string.quoted.double.cpp punctuation.definition.string.end.cpp", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ")", - "t": "source.cpp meta.block.cpp meta.function-call.cpp punctuation.section.parens.end.bracket.round.cpp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ";", - "t": "source.cpp meta.block.cpp punctuation.terminator.statement.cpp", + "c": "asm(\"movw $0x38, %ax; ltr %ax\");", + "t": "source.cpp meta.block.cpp meta.function-call.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1893,7 +1783,7 @@ }, { "c": " ", - "t": "source.cpp meta.block.cpp", + "t": "source.cpp meta.block.cpp meta.function-call.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", diff --git a/extensions/cpp/test/colorize-results/test_cpp.json b/extensions/cpp/test/colorize-results/test_cpp.json index 8733d46d7c0..9702dc50b0b 100644 --- a/extensions/cpp/test/colorize-results/test_cpp.json +++ b/extensions/cpp/test/colorize-results/test_cpp.json @@ -133,13 +133,13 @@ }, { "c": "std", - "t": "source.cpp meta.using-namespace.cpp entity.name.namespace.cpp", + "t": "source.cpp meta.using-namespace.cpp entity.name.type.namespace.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" + "hc_black": "entity.name.type: #4EC9B0" } }, { @@ -286,8 +286,19 @@ } }, { - "c": "public:", - "t": "source.cpp meta.block.class.cpp meta.body.class.cpp storage.type.modifier.access.control.public:.cpp", + "c": "public", + "t": "source.cpp meta.block.class.cpp meta.body.class.cpp storage.type.modifier.access.control.public.cpp", + "r": { + "dark_plus": "storage.type: #569CD6", + "light_plus": "storage.type: #0000FF", + "dark_vs": "storage.type: #569CD6", + "light_vs": "storage.type: #0000FF", + "hc_black": "storage.type: #569CD6" + } + }, + { + "c": ":", + "t": "source.cpp meta.block.class.cpp meta.body.class.cpp storage.type.modifier.access.control.public.cpp colon.cpp", "r": { "dark_plus": "storage.type: #569CD6", "light_plus": "storage.type: #0000FF", @@ -617,13 +628,13 @@ }, { "c": "Rectangle", - "t": "source.cpp meta.scope-resolution.cpp entity.name.namespace.scope-resolution.cpp", + "t": "source.cpp meta.scope-resolution.cpp entity.name.type.namespace.scope-resolution.cpp", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "entity.name.type: #4EC9B0", + "light_plus": "entity.name.type: #267F99", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" + "hc_black": "entity.name.type: #4EC9B0" } }, { @@ -1057,7 +1068,7 @@ }, { "c": "(", - "t": "source.cpp meta.block.cpp meta.block.parens.cpp punctuation.section.parens.begin.bracket.round.cpp", + "t": "source.cpp meta.block.cpp meta.parens.block.cpp punctuation.section.parens.begin.bracket.round.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1068,7 +1079,7 @@ }, { "c": "3", - "t": "source.cpp meta.block.cpp meta.block.parens.cpp constant.numeric.decimal.cpp", + "t": "source.cpp meta.block.cpp meta.parens.block.cpp constant.numeric.decimal.cpp", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #09885A", @@ -1079,7 +1090,7 @@ }, { "c": ",", - "t": "source.cpp meta.block.cpp meta.block.parens.cpp comma.cpp punctuation.separator.delimiter.cpp", + "t": "source.cpp meta.block.cpp meta.parens.block.cpp comma.cpp punctuation.separator.delimiter.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1090,7 +1101,7 @@ }, { "c": "4", - "t": "source.cpp meta.block.cpp meta.block.parens.cpp constant.numeric.decimal.cpp", + "t": "source.cpp meta.block.cpp meta.parens.block.cpp constant.numeric.decimal.cpp", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #09885A", @@ -1101,7 +1112,7 @@ }, { "c": ")", - "t": "source.cpp meta.block.cpp meta.block.parens.cpp punctuation.section.parens.end.bracket.round.cpp", + "t": "source.cpp meta.block.cpp meta.parens.block.cpp punctuation.section.parens.end.bracket.round.cpp", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", diff --git a/extensions/fsharp/cgmanifest.json b/extensions/fsharp/cgmanifest.json index 851e97ca639..d24f6a3d010 100644 --- a/extensions/fsharp/cgmanifest.json +++ b/extensions/fsharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "ionide/ionide-fsgrammar", "repositoryUrl": "https://github.com/ionide/ionide-fsgrammar", - "commitHash": "be0bdfd1e272b6633f5edf1429052fe9fa4df7c1" + "commitHash": "b2100c95d7857c5421d111a860fcdd20954a0263" } }, "license": "MIT", diff --git a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json index 9a9b1b1f2fd..82e6d33548f 100644 --- a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json +++ b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json @@ -4,7 +4,7 @@ "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/ionide/ionide-fsgrammar/commit/be0bdfd1e272b6633f5edf1429052fe9fa4df7c1", + "version": "https://github.com/ionide/ionide-fsgrammar/commit/b2100c95d7857c5421d111a860fcdd20954a0263", "name": "fsharp", "scopeName": "source.fsharp", "patterns": [ @@ -285,6 +285,33 @@ } ] }, + { + "begin": "(\\()", + "end": "(\\))", + "beginCaptures": { + "1": { + "name": "keyword.symbol.fsharp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.symbol.fsharp" + } + }, + "patterns": [ + { + "match": "(([?[:alpha:]0-9'`^._ ]+))+", + "captures": { + "1": { + "name": "entity.name.type.fsharp" + } + } + }, + { + "include": "#tuple_signature" + } + ] + }, { "match": "(?!when|and|or\\b)\\b([\\w0-9'`^._]+)", "comments": "Here we need the \\w modifier in order to check that the words isn't blacklisted", @@ -571,7 +598,7 @@ "include": "#common_declaration" }, { - "match": "(\\?{0,1})([[:alpha:]0-9'`^._ ]+)\\s*(:)(\\s*([[:alpha:]0-9'`^._ ]+)){0,1}", + "match": "(\\?{0,1})([[:alpha:]0-9'`^._ ]+)\\s*(:)((?!with\\b)\\b([\\w0-9'`^._ ]+)){0,1}", "captures": { "1": { "name": "keyword.symbol.fsharp" @@ -772,9 +799,9 @@ } }, { - "begin": "(<(?![[:space:]]*\\)))", + "begin": "(<+(?![[:space:]]*\\)))", "beginComment": "The group (?![[:space:]]*\\) is for protection against overload operator. static member (<)", - "end": "((?)", + "end": "((?|\\))", "endComment": "The group (? when using SRTP synthax", "beginCaptures": { "1": { @@ -814,6 +841,14 @@ { "include": "#definition" }, + { + "match": "(?<=>)\\s*(``([[:alpha:]0-9'^._ ]+)``|[[:alpha:]0-9'`^._]+)", + "captures": { + "1": { + "name": "entity.name.type.fsharp" + } + } + }, { "include": "#variables" }, @@ -826,7 +861,7 @@ "patterns": [ { "name": "binding.fsharp", - "begin": "\\b(let mutable|static let mutable|let inline|let|member val|static member inline|static member|default|member|override|let!)(\\s+rec|mutable)?(\\s+\\[\\<.*\\>\\])?\\s*(private|internal|public)?\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9,\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9,\\._`\\s]+|(?<=,)\\s)*)?", + "begin": "\\b(let mutable|static let mutable|let inline|let|member val|static member inline|static member|default|member|override|let!)(\\s+rec|mutable)?(\\s+\\[\\<.*\\>\\])?\\s*(private|internal|public)?\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?", "end": "\\s*(with\\b|=|\\n+=|(?<=\\=))", "beginCaptures": { "1": { @@ -856,6 +891,26 @@ } ] }, + { + "name": "binding.fsharp", + "begin": "\\b((get|set)\\s*(?=\\())(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?", + "end": "\\s*(=|\\n+=|(?<=\\=))", + "beginCaptures": { + "3": { + "name": "variable.fsharp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.fsharp" + } + }, + "patterns": [ + { + "include": "#common_binding_definition" + } + ] + }, { "name": "binding.fsharp", "begin": "\\b(static val mutable|val mutable|val)(\\s+rec|mutable)?(\\s+\\[\\<.*\\>\\])?\\s*(private|internal|public)?\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9,\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9,\\._`\\s]+|(?<=,)\\s)*)?", @@ -920,7 +975,7 @@ } }, { - "match": "([[:alpha:]0-9'`^._]+)|``([[:alpha:]0-9'^._ ]+)``", + "match": "(``([[:alpha:]0-9'^._ ]+)``|[[:alpha:]0-9'`^._]+)", "captures": { "1": { "name": "entity.name.type.fsharp" @@ -1142,7 +1197,7 @@ "match": "\\(\\)" }, { - "match": "(\\?{0,1})(``[[:alpha:]0-9'`^:,._ ]+``|[[:alpha:]0-9'`<>^._ ]\\w*)", + "match": "(\\?{0,1})(``[[:alpha:]0-9'`^:,._ ]+``|(?!private\\b)\\b[\\w[:alpha:]0-9'`<>^._ ]+)", "captures": { "1": { "name": "keyword.symbol.fsharp" diff --git a/extensions/fsharp/test/colorize-results/test_fs.json b/extensions/fsharp/test/colorize-results/test_fs.json index 37c0b61c14a..e5736fc10aa 100644 --- a/extensions/fsharp/test/colorize-results/test_fs.json +++ b/extensions/fsharp/test/colorize-results/test_fs.json @@ -604,28 +604,6 @@ "hc_black": "keyword: #569CD6" } }, - { - "c": " get", - "t": "source.fsharp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "()", - "t": "source.fsharp constant.language.unit.fsharp", - "r": { - "dark_plus": "constant.language: #569CD6", - "light_plus": "constant.language: #0000FF", - "dark_vs": "constant.language: #569CD6", - "light_vs": "constant.language: #0000FF", - "hc_black": "constant.language: #569CD6" - } - }, { "c": " ", "t": "source.fsharp", @@ -637,9 +615,42 @@ "hc_black": "default: #FFFFFF" } }, + { + "c": "get", + "t": "source.fsharp binding.fsharp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "()", + "t": "source.fsharp binding.fsharp constant.language.unit.fsharp", + "r": { + "dark_plus": "constant.language: #569CD6", + "light_plus": "constant.language: #0000FF", + "dark_vs": "constant.language: #569CD6", + "light_vs": "constant.language: #0000FF", + "hc_black": "constant.language: #569CD6" + } + }, + { + "c": " ", + "t": "source.fsharp binding.fsharp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, { "c": "=", - "t": "source.fsharp keyword.symbol.fsharp", + "t": "source.fsharp binding.fsharp keyword.fsharp", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -681,50 +692,6 @@ "hc_black": "keyword: #569CD6" } }, - { - "c": " set", - "t": "source.fsharp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "(", - "t": "source.fsharp keyword.symbol.fsharp", - "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" - } - }, - { - "c": "value", - "t": "source.fsharp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ")", - "t": "source.fsharp keyword.symbol.fsharp", - "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6" - } - }, { "c": " ", "t": "source.fsharp", @@ -736,9 +703,64 @@ "hc_black": "default: #FFFFFF" } }, + { + "c": "set", + "t": "source.fsharp binding.fsharp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "(", + "t": "source.fsharp binding.fsharp keyword.symbol.fsharp", + "r": { + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6" + } + }, + { + "c": "value", + "t": "source.fsharp binding.fsharp variable.parameter.fsharp", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.fsharp binding.fsharp keyword.symbol.fsharp", + "r": { + "dark_plus": "keyword: #569CD6", + "light_plus": "keyword: #0000FF", + "dark_vs": "keyword: #569CD6", + "light_vs": "keyword: #0000FF", + "hc_black": "keyword: #569CD6" + } + }, + { + "c": " ", + "t": "source.fsharp binding.fsharp", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, { "c": "=", - "t": "source.fsharp keyword.symbol.fsharp", + "t": "source.fsharp binding.fsharp keyword.fsharp", "r": { "dark_plus": "keyword: #569CD6", "light_plus": "keyword: #0000FF", @@ -979,7 +1001,7 @@ } }, { - "c": " targetAge", + "c": " targetAge ", "t": "source.fsharp binding.fsharp variable.parameter.fsharp", "r": { "dark_plus": "variable: #9CDCFE", @@ -989,17 +1011,6 @@ "hc_black": "variable: #9CDCFE" } }, - { - "c": " ", - "t": "source.fsharp binding.fsharp", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, { "c": "=", "t": "source.fsharp binding.fsharp keyword.fsharp", diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 3132b8cdf86..42952e0ca05 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -306,6 +306,8 @@ function getGitErrorCode(stderr: string): string | undefined { return GitErrorCodes.BranchAlreadyExists; } else if (/'.+' is not a valid branch name/.test(stderr)) { return GitErrorCodes.InvalidBranchName; + } else if (/Please,? commit your changes or stash them/.test(stderr)) { + return GitErrorCodes.DirtyWorkTree; } return undefined; diff --git a/extensions/handlebars/cgmanifest.json b/extensions/handlebars/cgmanifest.json index 4d30387e915..39f8efc676d 100644 --- a/extensions/handlebars/cgmanifest.json +++ b/extensions/handlebars/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "daaain/Handlebars", "repositoryUrl": "https://github.com/daaain/Handlebars", - "commitHash": "790f2b0222098a3a236bd9e91bb9a039eeca4d8e" + "commitHash": "85a153a6f759df4e8da7533e1b3651f007867c51" } }, "licenseDetail": [ @@ -29,7 +29,7 @@ "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." ], "license": "MIT", - "version": "1.7.1" + "version": "1.8.0" } ], "version": 1 diff --git a/extensions/handlebars/syntaxes/Handlebars.tmLanguage.json b/extensions/handlebars/syntaxes/Handlebars.tmLanguage.json index 957f16ae035..be8b06fa085 100644 --- a/extensions/handlebars/syntaxes/Handlebars.tmLanguage.json +++ b/extensions/handlebars/syntaxes/Handlebars.tmLanguage.json @@ -4,7 +4,7 @@ "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/daaain/Handlebars/commit/790f2b0222098a3a236bd9e91bb9a039eeca4d8e", + "version": "https://github.com/daaain/Handlebars/commit/85a153a6f759df4e8da7533e1b3651f007867c51", "name": "Handlebars", "scopeName": "text.html.handlebars", "patterns": [ @@ -653,7 +653,7 @@ ] }, "else_token": { - "begin": "(\\{\\{)(~?else)(@?\\s(if)\\s([-a-zA-Z0-9_\\./]+))?", + "begin": "(\\{\\{)(~?else)(@?\\s(if)\\s([-a-zA-Z0-9_\\.\\(\\s\\)/]+))?", "end": "(~?\\}\\}\\}*)", "name": "meta.function.inline.else.handlebars", "beginCaptures": { diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 74aa5e694a1..7c90bfb52c9 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -10,7 +10,7 @@ "main": "./out/htmlServerMain", "dependencies": { "vscode-css-languageservice": "^4.0.2-next.3", - "vscode-html-languageservice": "^3.0.0-next.6", + "vscode-html-languageservice": "^3.0.0-next.7", "vscode-languageserver": "^5.3.0-next.2", "vscode-languageserver-types": "^3.14.0", "vscode-nls": "^4.0.0", diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index 76766c7948b..6bdbac4e1a2 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -237,10 +237,10 @@ vscode-css-languageservice@^4.0.2-next.3: vscode-languageserver-types "^3.14.0" vscode-nls "^4.0.0" -vscode-html-languageservice@^3.0.0-next.6: - version "3.0.0-next.6" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.0.0-next.6.tgz#4a33567abfc3ccacc4258f4f2c1743eb064b490b" - integrity sha512-h5JDiJtqPvRlWlN59Ue/clLCL6/vtAVYHQql9uLLZ+zwuX6kbF/7sCCM6hAswuYushx5kniNzwq47A23V1nlUA== +vscode-html-languageservice@^3.0.0-next.7: + version "3.0.0-next.7" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-3.0.0-next.7.tgz#6590cb0a6fa5bb257afca03669bbf6ad8adb58c6" + integrity sha512-s9dVSMVKGlrAymj6WByoEheZMen82yHqiXh7F8cz9KCqDHJ3LRpcJMKrIdOFQJ6qTcFW9I7VqO77VSB9d1RZyQ== dependencies: vscode-languageserver-types "^3.14.0" vscode-nls "^4.0.0" diff --git a/extensions/java/cgmanifest.json b/extensions/java/cgmanifest.json index fb1791f0ab4..8616c4fcc53 100644 --- a/extensions/java/cgmanifest.json +++ b/extensions/java/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "atom/language-java", "repositoryUrl": "https://github.com/atom/language-java", - "commitHash": "a91b17906a2142bc61c06c6f214f135a2e3cdc96" + "commitHash": "9fc8f699e55284c0a8ddf03d929504064eb4f757" } }, "license": "MIT", - "version": "0.31.1" + "version": "0.31.2" } ], "version": 1 diff --git a/extensions/java/syntaxes/java.tmLanguage.json b/extensions/java/syntaxes/java.tmLanguage.json index f73c132ff03..736861a6043 100644 --- a/extensions/java/syntaxes/java.tmLanguage.json +++ b/extensions/java/syntaxes/java.tmLanguage.json @@ -4,7 +4,7 @@ "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-java/commit/a91b17906a2142bc61c06c6f214f135a2e3cdc96", + "version": "https://github.com/atom/language-java/commit/9fc8f699e55284c0a8ddf03d929504064eb4f757", "name": "Java", "scopeName": "source.java", "patterns": [ @@ -1576,7 +1576,7 @@ ] }, "variables": { - "begin": "(?x)\n(?=\n (\n \\b(void|boolean|byte|char|short|int|float|long|double)\\b\n |\n (?>(\\w+\\.)*[A-Z]+\\w*) # e.g. `javax.ws.rs.Response`, or `String`\n )\n (\n <[\\w<>,\\.?\\s\\[\\]]*> # e.g. `HashMap`, or `List`\n )?\n (\n (\\[\\])* # int[][]\n )?\n \\s+\n [A-Za-z_$][\\w$]* # At least one identifier after space\n ([\\w\\[\\],$][\\w\\[\\],\\s]*)? # possibly primitive array or additional identifiers\n \\s*(=|:|;)\n)", + "begin": "(?x)\n(?=\n (\n \\b(void|boolean|byte|char|short|int|float|long|double)\\b\n |\n (?>(\\w+\\.)*[A-Z]+\\w*) # e.g. `javax.ws.rs.Response`, or `String`\n )\n \\s*\n (\n <[\\w<>,\\.?\\s\\[\\]]*> # e.g. `HashMap`, or `List`\n )?\n \\s*\n (\n (\\[\\])* # int[][]\n )?\n \\s+\n [A-Za-z_$][\\w$]* # At least one identifier after space\n ([\\w\\[\\],$][\\w\\[\\],\\s]*)? # possibly primitive array or additional identifiers\n \\s*(=|:|;)\n)", "end": "(?=\\=|:|;)", "name": "meta.definition.variable.java", "patterns": [ diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index 1ca17ec6059..9ea6c3cec1b 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json @@ -4,7 +4,7 @@ "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/Microsoft/TypeScript-TmLanguage/commit/d8c9720c4ebb9e0ec4b0d691525f33570a4387ea", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/3508c88a4ac6112934e0c34de7942c67682b2321", "name": "JavaScript (with React support)", "scopeName": "source.js", "patterns": [ @@ -2416,7 +2416,7 @@ "patterns": [ { "begin": "(?{1,5})}", + "name": "constant.character.escape.powershell" + }, + { + "match": "`u(?:\\{[0-9a-fA-F]{,6}.)?", + "name": "invalid.character.escape.powershell" + } + ] + }, "function": { "begin": "^(?:\\s*+)(?i)(function|filter|configuration|workflow)\\s+(?:(global|local|script|private):)?((?:\\p{L}|\\d|_|-|\\.)+)", "beginCaptures": { @@ -677,7 +645,7 @@ { "captures": { "0": { - "name": "support.constant.automatic.powershell" + "name": "support.variable.automatic.powershell" }, "1": { "name": "punctuation.definition.variable.powershell" @@ -687,7 +655,7 @@ } }, "comment": "Automatic variables are not constants, but they are read-only. In monokai (default) color schema support.variable doesn't have color, so we use constant.", - "match": "(\\$)(?i:(\\$|\\^|\\?|_|Args|ConsoleFileName|Event|EventArgs|EventSubscriber|ForEach|Input|LastExitCode|Matches|MyInvocation|NestedPromptLevel|Profile|PSBoundParameters|PsCmdlet|PsCulture|PSDebugContext|PSItem|PSCommandPath|PSScriptRoot|PsUICulture|Pwd|Sender|SourceArgs|SourceEventArgs|StackTrace|Switch|This))((?:\\.(?:\\p{L}|\\d|_)+)*\\b)?\\b" + "match": "(\\$)((?:[$^?])|(?i:_|Args|ConsoleFileName|Event|EventArgs|EventSubscriber|ForEach|Input|LastExitCode|Matches|MyInvocation|NestedPromptLevel|Profile|PSBoundParameters|PsCmdlet|PsCulture|PSDebugContext|PSItem|PSCommandPath|PSScriptRoot|PsUICulture|Pwd|Sender|SourceArgs|SourceEventArgs|StackTrace|Switch|This)\\b)((?:\\.(?:\\p{L}|\\d|_)+)*\\b)?" }, { "captures": { @@ -865,7 +833,7 @@ } }, "comment": "Automatic variables are not constants, but they are read-only...", - "match": "(\\$)(?i:(\\$|\\^|\\?|_|Args|ConsoleFileName|Event|EventArgs|EventSubscriber|ForEach|Input|LastExitCode|Matches|MyInvocation|NestedPromptLevel|Profile|PSBoundParameters|PsCmdlet|PsCulture|PSDebugContext|PSItem|PSCommandPath|PSScriptRoot|PsUICulture|Pwd|Sender|SourceArgs|SourceEventArgs|StackTrace|Switch|This))\\b" + "match": "(\\$)((?:[$^?])|(?i:_|Args|ConsoleFileName|Event|EventArgs|EventSubscriber|ForEach|Input|LastExitCode|Matches|MyInvocation|NestedPromptLevel|Profile|PSBoundParameters|PsCmdlet|PsCulture|PSDebugContext|PSItem|PSCommandPath|PSScriptRoot|PsUICulture|Pwd|Sender|SourceArgs|SourceEventArgs|StackTrace|Switch|This)\\b)" }, { "captures": { @@ -897,7 +865,7 @@ "name": "variable.other.member.powershell" } }, - "match": "(?i:(\\$|@)(global|local|private|script|using|workflow):((?:\\p{L}|\\d|_)+))" + "match": "(?i:(\\$)(global|local|private|script|using|workflow):((?:\\p{L}|\\d|_)+))" }, { "captures": { @@ -1023,9 +991,6 @@ { "include": "#variableNoProperty" }, - { - "include": "#variable" - }, { "include": "#doubleQuotedStringEscapes" }, diff --git a/extensions/powershell/test/colorize-results/test_ps1.json b/extensions/powershell/test/colorize-results/test_ps1.json index fd82cda536b..e41d1306499 100644 --- a/extensions/powershell/test/colorize-results/test_ps1.json +++ b/extensions/powershell/test/colorize-results/test_ps1.json @@ -716,24 +716,24 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell support.constant.automatic.powershell punctuation.definition.variable.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" + "hc_black": "support.variable: #9CDCFE" } }, { "c": "_", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell support.constant.automatic.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell support.variable.automatic.powershell", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" + "hc_black": "support.variable: #9CDCFE" } }, { @@ -1090,24 +1090,24 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.constant.automatic.powershell punctuation.definition.variable.powershell", + "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" + "hc_black": "support.variable: #9CDCFE" } }, { "c": "_", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.constant.automatic.powershell", + "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" + "hc_black": "support.variable: #9CDCFE" } }, { @@ -1299,24 +1299,24 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.constant.automatic.powershell punctuation.definition.variable.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" + "hc_black": "support.variable: #9CDCFE" } }, { "c": "_", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.constant.automatic.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" + "hc_black": "support.variable: #9CDCFE" } }, { @@ -1486,24 +1486,24 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.constant.automatic.powershell punctuation.definition.variable.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" + "hc_black": "support.variable: #9CDCFE" } }, { "c": "matches", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.constant.automatic.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" + "hc_black": "support.variable: #9CDCFE" } }, { @@ -1563,24 +1563,24 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.constant.automatic.powershell punctuation.definition.variable.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" + "hc_black": "support.variable: #9CDCFE" } }, { "c": "matches", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.constant.automatic.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell", "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" + "hc_black": "support.variable: #9CDCFE" } }, { diff --git a/extensions/shellscript/package.json b/extensions/shellscript/package.json index c343b4fd90a..67a05c028ea 100644 --- a/extensions/shellscript/package.json +++ b/extensions/shellscript/package.json @@ -14,7 +14,7 @@ "aliases": ["Shell Script", "shellscript", "bash", "sh", "zsh", "ksh"], "extensions": [".sh", ".bash", ".bashrc", ".bash_aliases", ".bash_profile", ".bash_login", ".ebuild", ".install", ".profile", ".bash_logout", ".zsh", ".zshrc", ".zprofile", ".zlogin", ".zlogout", ".zshenv", ".zsh-theme", ".ksh"], "filenames": ["PKGBUILD"], - "firstLine": "^#!.*\\b(bash|zsh|sh|tcsh|ksh|ash).*|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", + "firstLine": "^#!.*\\b(bash|zsh|sh|tcsh|ksh|ash|qsh).*|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", "configuration": "./language-configuration.json", "mimetypes": ["text/x-shellscript"] }], diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json index d74d3d57ed5..1f7d48782d2 100644 --- a/extensions/theme-abyss/themes/abyss-color-theme.json +++ b/extensions/theme-abyss/themes/abyss-color-theme.json @@ -400,6 +400,7 @@ "statusBar.noFolderBackground": "#10192c", "statusBar.debuggingBackground": "#10192c", // "statusBar.foreground": "", + "statusBarItem.remoteBackground": "#0063a5", "statusBarItem.prominentBackground": "#0063a5", "statusBarItem.prominentHoverBackground": "#0063a5dd", // "statusBarItem.activeBackground": "", diff --git a/extensions/theme-defaults/themes/dark_defaults.json b/extensions/theme-defaults/themes/dark_defaults.json index 276a71d0fdb..00c2ac8c36b 100644 --- a/extensions/theme-defaults/themes/dark_defaults.json +++ b/extensions/theme-defaults/themes/dark_defaults.json @@ -15,6 +15,8 @@ "settings.textInputBackground": "#292929", "settings.numberInputBackground": "#292929", "menu.background": "#252526", - "menu.foreground": "#CCCCCC" + "menu.foreground": "#CCCCCC", + "statusBarItem.remoteForeground": "#FFF", + "statusBarItem.remoteBackground": "#16825D" } } \ No newline at end of file diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index c66e666e2dc..e73cc3a09af 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -71,8 +71,9 @@ "scope": [ "keyword.control", "keyword.operator.new.cpp", - "keyword.operator.delete.cpp", - "keyword.other.using" + "keyword.operator.delete", + "keyword.other.using", + "keyword.other.operator" ], "settings": { "foreground": "#C586C0" diff --git a/extensions/theme-defaults/themes/hc_black.json b/extensions/theme-defaults/themes/hc_black.json index 74bf098818a..870681495dc 100644 --- a/extensions/theme-defaults/themes/hc_black.json +++ b/extensions/theme-defaults/themes/hc_black.json @@ -4,7 +4,8 @@ "include": "./hc_black_defaults.json", "colors": { "selection.background": "#008000", - "editor.selectionBackground": "#FFFFFF" + "editor.selectionBackground": "#FFFFFF", + "statusBarItem.remoteBackground": "#00000000" }, "tokenColors": [ { @@ -69,7 +70,8 @@ "keyword.control", "keyword.operator.new.cpp", "keyword.operator.delete.cpp", - "keyword.other.using" + "keyword.other.using", + "keyword.other.operator" ], "settings": { "foreground": "#C586C0" diff --git a/extensions/theme-defaults/themes/hc_black_defaults.json b/extensions/theme-defaults/themes/hc_black_defaults.json index 932ebabbbc8..9d11138a99b 100644 --- a/extensions/theme-defaults/themes/hc_black_defaults.json +++ b/extensions/theme-defaults/themes/hc_black_defaults.json @@ -6,6 +6,7 @@ "editor.foreground": "#FFFFFF", "editorIndentGuide.background": "#FFFFFF", "editorIndentGuide.activeBackground": "#FFFFFF", + "statusBarItem.remoteBackground": "#00000000", "sideBarTitle.foreground": "#FFFFFF" }, "settings": [ diff --git a/extensions/theme-defaults/themes/light_defaults.json b/extensions/theme-defaults/themes/light_defaults.json index 91c5fb1d93a..e28c9b8ed0b 100644 --- a/extensions/theme-defaults/themes/light_defaults.json +++ b/extensions/theme-defaults/themes/light_defaults.json @@ -14,6 +14,8 @@ "list.hoverBackground": "#E8E8E8", "input.placeholderForeground": "#767676", "settings.textInputBorder": "#CECECE", - "settings.numberInputBorder": "#CECECE" + "settings.numberInputBorder": "#CECECE", + "statusBarItem.remoteForeground": "#FFF", + "statusBarItem.remoteBackground": "#16825D" } } \ No newline at end of file diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index c58e8c6f903..ce23ed901d6 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -72,7 +72,8 @@ "keyword.control", "keyword.operator.new.cpp", "keyword.operator.delete.cpp", - "keyword.other.using" + "keyword.other.using", + "keyword.other.operator" ], "settings": { "foreground": "#AF00DB" diff --git a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json index 8b2110255f2..fac06fd00d4 100644 --- a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json +++ b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json @@ -27,6 +27,7 @@ "statusBar.background": "#423523", "statusBar.debuggingBackground": "#423523", "statusBar.noFolderBackground": "#423523", + "statusBarItem.remoteBackground": "#6e583b", "activityBar.background": "#221a0f", "activityBar.foreground": "#d3af86", "sideBar.background": "#362712", @@ -116,7 +117,8 @@ "keyword.control", "keyword.operator.new.cpp", "keyword.operator.delete.cpp", - "keyword.other.using" + "keyword.other.using", + "keyword.other.operator" ], "settings": { "foreground": "#98676a" diff --git a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json index e5088a0811e..ed325071b64 100644 --- a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json +++ b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json @@ -31,6 +31,7 @@ "statusBar.debuggingBackground": "#505050", "statusBar.noFolderBackground": "#505050", "titleBar.activeBackground": "#505050", + "statusBarItem.remoteBackground": "#3655b5", "activityBar.background": "#353535", "activityBar.foreground": "#ffffff", "activityBarBadge.background": "#3655b5", @@ -259,7 +260,8 @@ "keyword.control", "keyword.operator.new.cpp", "keyword.operator.delete.cpp", - "keyword.other.using" + "keyword.other.using", + "keyword.other.operator" ], "settings": { "fontStyle": "", diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json index ec445f89fd3..14d616f6153 100644 --- a/extensions/theme-monokai/themes/monokai-color-theme.json +++ b/extensions/theme-monokai/themes/monokai-color-theme.json @@ -50,6 +50,7 @@ "statusBar.background": "#414339", "statusBar.noFolderBackground": "#414339", "statusBar.debuggingBackground": "#75715E", + "statusBarItem.remoteBackground": "#AC6218", "activityBar.background": "#272822", "activityBar.foreground": "#f8f8f2", "activityBar.dropBackground": "#414339", diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index 2cef3aa273f..67f7caa4da5 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -516,6 +516,7 @@ "statusBar.background": "#705697", "statusBar.noFolderBackground": "#705697", "statusBar.debuggingBackground": "#705697", + "statusBarItem.remoteBackground": "#4e3c69", "activityBar.background": "#EDEDF5", "activityBar.foreground": "#705697", "activityBarBadge.background": "#705697", diff --git a/extensions/theme-red/themes/Red-color-theme.json b/extensions/theme-red/themes/Red-color-theme.json index 3de9575b0db..18e5786a8f3 100644 --- a/extensions/theme-red/themes/Red-color-theme.json +++ b/extensions/theme-red/themes/Red-color-theme.json @@ -9,6 +9,7 @@ "sideBar.background": "#330000", "statusBar.background": "#700000", "statusBar.noFolderBackground": "#700000", + "statusBarItem.remoteBackground": "#c33", "editorGroupHeader.tabsBackground": "#330000", "titleBar.activeBackground": "#770000", "titleBar.inactiveBackground": "#772222", diff --git a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json index 30cbe306f4d..5605aba721d 100644 --- a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json +++ b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json @@ -448,6 +448,7 @@ "statusBar.background": "#00212B", "statusBar.debuggingBackground": "#00212B", "statusBar.noFolderBackground": "#00212B", + "statusBarItem.remoteBackground": "#2AA19899", "statusBarItem.prominentBackground": "#003847", "statusBarItem.prominentHoverBackground": "#003847", // "statusBarItem.activeBackground": "", diff --git a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json index 79caab20000..b94aeb71de8 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -447,6 +447,7 @@ "statusBar.debuggingBackground": "#EEE8D5", "statusBar.noFolderBackground": "#EEE8D5", // "statusBar.foreground": "", + "statusBarItem.remoteBackground": "#AC9D57", "statusBarItem.prominentBackground": "#DDD6C1", "statusBarItem.prominentHoverBackground": "#DDD6C199", // "statusBarItem.activeBackground": "", diff --git a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json index 8d01980349f..2ca39bf565d 100644 --- a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json +++ b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json @@ -30,6 +30,7 @@ "debugToolBar.background": "#001c40", "titleBar.activeBackground": "#001126", "statusBar.background": "#001126", + "statusBarItem.remoteBackground": "#0e639c", "statusBar.noFolderBackground": "#001126", "statusBar.debuggingBackground": "#001126", "activityBar.background": "#001733", diff --git a/extensions/typescript-basics/cgmanifest.json b/extensions/typescript-basics/cgmanifest.json index 7d5ebfc9707..ffd4150e68a 100644 --- a/extensions/typescript-basics/cgmanifest.json +++ b/extensions/typescript-basics/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "TypeScript-TmLanguage", "repositoryUrl": "https://github.com/Microsoft/TypeScript-TmLanguage", - "commitHash": "d8c9720c4ebb9e0ec4b0d691525f33570a4387ea" + "commitHash": "3508c88a4ac6112934e0c34de7942c67682b2321" } }, "license": "MIT", diff --git a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json index cdfe6fd83bf..a3d9b24fe96 100644 --- a/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json +++ b/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json @@ -4,7 +4,7 @@ "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/Microsoft/TypeScript-TmLanguage/commit/d8c9720c4ebb9e0ec4b0d691525f33570a4387ea", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/3508c88a4ac6112934e0c34de7942c67682b2321", "name": "TypeScript", "scopeName": "source.ts", "patterns": [ @@ -2413,7 +2413,7 @@ "patterns": [ { "begin": "(?(); + private readonly _languageSettings = new Map(); constructor() { - for (const language of allDiagnosticLangauges) { + for (const language of allDiagnosticLanguages) { this._languageSettings.set(language, DiagnosticSettings.defaultSettings); } } @@ -112,11 +112,11 @@ class DiagnosticSettings { })); } - private get(language: DiagnosticLanguage): LangaugeDiagnosticSettings { + private get(language: DiagnosticLanguage): LanguageDiagnosticSettings { return this._languageSettings.get(language) || DiagnosticSettings.defaultSettings; } - private update(language: DiagnosticLanguage, f: (x: LangaugeDiagnosticSettings) => LangaugeDiagnosticSettings): boolean { + private update(language: DiagnosticLanguage, f: (x: LanguageDiagnosticSettings) => LanguageDiagnosticSettings): boolean { const currentSettings = this.get(language); const newSettings = f(currentSettings); this._languageSettings.set(language, newSettings); diff --git a/extensions/typescript-language-features/src/features/organizeImports.ts b/extensions/typescript-language-features/src/features/organizeImports.ts index cbae287db1e..bb7880800a6 100644 --- a/extensions/typescript-language-features/src/features/organizeImports.ts +++ b/extensions/typescript-language-features/src/features/organizeImports.ts @@ -46,7 +46,7 @@ class OrganizeImportsCommand implements Command { } } }; - const response = await this.client.execute('organizeImports', args, nulToken); + const response = await this.client.interruptGetErr(() => this.client.execute('organizeImports', args, nulToken)); if (response.type !== 'response' || !response.body) { return false; } diff --git a/extensions/typescript-language-features/src/features/smartSelect.ts b/extensions/typescript-language-features/src/features/smartSelect.ts index a9a0370bee9..3d05d1309a2 100644 --- a/extensions/typescript-language-features/src/features/smartSelect.ts +++ b/extensions/typescript-language-features/src/features/smartSelect.ts @@ -27,7 +27,7 @@ class SmartSelection implements vscode.SelectionRangeProvider { return undefined; } - const args: Proto.FileRequestArgs & { locations: Proto.Location[] } = { + const args: Proto.SelectionRangeRequestArgs = { file, locations: positions.map(typeConverters.Position.toLocation) }; diff --git a/extensions/typescript-language-features/src/features/task.ts b/extensions/typescript-language-features/src/features/task.ts index 1b1d4d3bded..8c393ade672 100644 --- a/extensions/typescript-language-features/src/features/task.ts +++ b/extensions/typescript-language-features/src/features/task.ts @@ -96,6 +96,7 @@ class TscTaskProvider implements vscode.TaskProvider { const uri = editor.document.uri; return [{ path: uri.fsPath, + posixPath: uri.path, workspaceFolder: vscode.workspace.getWorkspaceFolder(uri) }]; } @@ -121,6 +122,7 @@ class TscTaskProvider implements vscode.TaskProvider { const folder = vscode.workspace.getWorkspaceFolder(uri); return [{ path: normalizedConfigPath, + posixPath: uri.path, workspaceFolder: folder }]; } @@ -232,9 +234,9 @@ class TscTaskProvider implements vscode.TaskProvider { private getLabelForTasks(project: TSConfig): string { if (project.workspaceFolder) { - return path.relative(project.workspaceFolder.uri.fsPath, project.path); + return path.posix.relative(project.workspaceFolder.uri.path, project.posixPath); } - return project.path; + return project.posixPath; } private onConfigurationChanged(): void { diff --git a/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts b/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts index 653890616e9..e053719f3d4 100644 --- a/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts +++ b/extensions/typescript-language-features/src/test/functionCallSnippet.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; -import { snippetForFunctionCall } from "../utils/snippetForFunctionCall"; +import { snippetForFunctionCall } from '../utils/snippetForFunctionCall'; suite('typescript function call snippets', () => { test('Should use label as function name', async () => { diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 18438c1d03a..1bfb290fa0a 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -209,6 +209,10 @@ export class TypeScriptServerSpawner { args.push('--noGetErrOnBackgroundUpdate'); } + if (apiVersion.gte(API.v345)) { + args.push('--validateDefaultNpmLocation'); + } + return { args, cancellationPipeName, tsServerLogFile }; } @@ -250,7 +254,7 @@ export class TypeScriptServer extends Disposable { private readonly _tracer: Tracer, ) { super(); - this._reader = this._register(new Reader(this._childProcess.stdout)); + this._reader = this._register(new Reader(this._childProcess.stdout!)); this._reader.onData(msg => this.dispatchMessage(msg)); this._childProcess.on('exit', code => this.handleExit(code)); this._childProcess.on('error', error => this.handleError(error)); @@ -270,7 +274,7 @@ export class TypeScriptServer extends Disposable { public get tsServerLogFile() { return this._tsServerLogFile; } public write(serverRequest: Proto.Request) { - this._childProcess.stdin.write(JSON.stringify(serverRequest) + '\r\n', 'utf8'); + this._childProcess.stdin!.write(JSON.stringify(serverRequest) + '\r\n', 'utf8'); } public dispose() { diff --git a/extensions/typescript-language-features/src/typescriptService.ts b/extensions/typescript-language-features/src/typescriptService.ts index 32ed3a60060..1156b79d268 100644 --- a/extensions/typescript-language-features/src/typescriptService.ts +++ b/extensions/typescript-language-features/src/typescriptService.ts @@ -11,17 +11,6 @@ import { TypeScriptServiceConfiguration } from './utils/configuration'; import Logger from './utils/logger'; import { PluginManager } from './utils/plugins'; -declare module './protocol' { - interface SelectionRange { - textSpan: Proto.TextSpan; - parent?: SelectionRange; - } - - interface SelectionRangeResponse extends Proto.Response { - body?: ReadonlyArray; - } -} - export namespace ServerResponse { export class Cancelled { @@ -65,7 +54,7 @@ export interface TypeScriptRequestTypes { 'quickinfo': [Proto.FileLocationRequestArgs, Proto.QuickInfoResponse]; 'references': [Proto.FileLocationRequestArgs, Proto.ReferencesResponse]; 'rename': [Proto.RenameRequestArgs, Proto.RenameResponse]; - 'selectionRange': [Proto.FileRequestArgs & { locations: Proto.Location[] }, Proto.SelectionRangeResponse]; + 'selectionRange': [Proto.SelectionRangeRequestArgs, Proto.SelectionRangeResponse]; 'signatureHelp': [Proto.SignatureHelpRequestArgs, Proto.SignatureHelpResponse]; 'typeDefinition': [Proto.FileLocationRequestArgs, Proto.TypeDefinitionResponse]; } diff --git a/extensions/typescript-language-features/src/typings/ref.d.ts b/extensions/typescript-language-features/src/typings/ref.d.ts index 954bab971e3..c9849d48e08 100644 --- a/extensions/typescript-language-features/src/typings/ref.d.ts +++ b/extensions/typescript-language-features/src/typings/ref.d.ts @@ -5,4 +5,3 @@ /// /// -/// diff --git a/extensions/typescript-language-features/src/utils/api.ts b/extensions/typescript-language-features/src/utils/api.ts index 9dcd05013db..01f3ac6e6a5 100644 --- a/extensions/typescript-language-features/src/utils/api.ts +++ b/extensions/typescript-language-features/src/utils/api.ts @@ -36,6 +36,7 @@ export default class API { public static readonly v330 = API.fromSimpleString('3.3.0'); public static readonly v333 = API.fromSimpleString('3.3.3'); public static readonly v340 = API.fromSimpleString('3.4.0'); + public static readonly v345 = API.fromSimpleString('3.4.5'); public static readonly v350 = API.fromSimpleString('3.5.0'); diff --git a/extensions/typescript-language-features/src/utils/electron.ts b/extensions/typescript-language-features/src/utils/electron.ts index ff69c8c8cc9..3a1ece8f726 100644 --- a/extensions/typescript-language-features/src/utils/electron.ts +++ b/extensions/typescript-language-features/src/utils/electron.ts @@ -22,8 +22,21 @@ const getRootTempDir = (() => { }; })(); +export const getInstanceDir = (() => { + let dir: string | undefined; + return () => { + if (!dir) { + dir = path.join(getRootTempDir(), temp.makeRandomHexString(20)); + } + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } + return dir; + }; +})(); + export function getTempFile(prefix: string): string { - return path.join(getRootTempDir(), `${prefix}-${temp.makeRandomHexString(20)}.tmp`); + return path.join(getInstanceDir(), `${prefix}-${temp.makeRandomHexString(20)}.tmp`); } function generatePatchedEnv(env: any, modulePath: string): any { diff --git a/extensions/typescript-language-features/src/utils/languageDescription.ts b/extensions/typescript-language-features/src/utils/languageDescription.ts index b9f279dc509..79f9ce9f47b 100644 --- a/extensions/typescript-language-features/src/utils/languageDescription.ts +++ b/extensions/typescript-language-features/src/utils/languageDescription.ts @@ -9,7 +9,7 @@ export const enum DiagnosticLanguage { TypeScript } -export const allDiagnosticLangauges = [DiagnosticLanguage.JavaScript, DiagnosticLanguage.TypeScript]; +export const allDiagnosticLanguages = [DiagnosticLanguage.JavaScript, DiagnosticLanguage.TypeScript]; export interface LanguageDescription { readonly id: string; diff --git a/extensions/typescript-language-features/src/utils/previewer.ts b/extensions/typescript-language-features/src/utils/previewer.ts index aa698b2c163..f4a17274960 100644 --- a/extensions/typescript-language-features/src/utils/previewer.ts +++ b/extensions/typescript-language-features/src/utils/previewer.ts @@ -14,7 +14,7 @@ function getTagBodyText(tag: Proto.JSDocTagInfo): string | undefined { switch (tag.name) { case 'example': case 'default': - // Convert to markdown code block if it not already one + // Convert to markdown code block if it is not already one if (tag.text.match(/^\s*[~`]{3}/g)) { return tag.text; } diff --git a/extensions/typescript-language-features/src/utils/surveyor.ts b/extensions/typescript-language-features/src/utils/surveyor.ts index bf3ba4ad601..2af183be12e 100644 --- a/extensions/typescript-language-features/src/utils/surveyor.ts +++ b/extensions/typescript-language-features/src/utils/surveyor.ts @@ -103,7 +103,7 @@ class Survey { } private get triggerCount(): number { - const count = this.memento.get(this.triggerCountMementoKey); + const count = this.memento.get(this.triggerCountMementoKey); return !count || isNaN(+count) ? 0 : +count; } diff --git a/extensions/typescript-language-features/src/utils/temp.ts b/extensions/typescript-language-features/src/utils/temp.ts index 79c5cf751d0..2af5f1732b0 100644 --- a/extensions/typescript-language-features/src/utils/temp.ts +++ b/extensions/typescript-language-features/src/utils/temp.ts @@ -7,7 +7,7 @@ import path = require('path'); import os = require('os'); 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']; + const chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; let result = ''; for (let i = 0; i < length; i++) { const idx = Math.floor(chars.length * Math.random()); diff --git a/extensions/typescript-language-features/src/utils/tsconfigProvider.ts b/extensions/typescript-language-features/src/utils/tsconfigProvider.ts index 7e2b85ef2d0..fe939032029 100644 --- a/extensions/typescript-language-features/src/utils/tsconfigProvider.ts +++ b/extensions/typescript-language-features/src/utils/tsconfigProvider.ts @@ -6,6 +6,7 @@ import * as vscode from 'vscode'; export interface TSConfig { readonly path: string; + readonly posixPath: string; readonly workspaceFolder?: vscode.WorkspaceFolder; } @@ -20,6 +21,7 @@ export default class TsConfigProvider { if (root) { configs.set(config.fsPath, { path: config.fsPath, + posixPath: config.path, workspaceFolder: root }); } diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 62a9ee588d1..968cb9074cd 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -2,10 +2,37 @@ # yarn lockfile v1 -"@types/node@8.0.33": - version "8.0.33" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.33.tgz#1126e94374014e54478092830704f6ea89df04cd" - integrity sha512-vmCdO8Bm1ExT+FWfC9sd9r4jwqM7o97gGy2WBshkkXbf/2nLAJQUrZfIhw27yVOtLUev6kSZc4cav/46KbDd8A== +"@types/events@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" + integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + +"@types/glob@*": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" + integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== + dependencies: + "@types/events" "*" + "@types/minimatch" "*" + "@types/node" "*" + +"@types/minimatch@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== + +"@types/node@*", "@types/node@^12.0.2": + version "12.0.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.2.tgz#3452a24edf9fea138b48fad4a0a028a683da1e40" + integrity sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA== + +"@types/rimraf@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e" + integrity sha512-Hm/bnWq0TCy7jmjeN5bKYij9vw5GrDFWME4IuxV08278NtU/VdGbzsBohcCUJ7+QMqmUq5hpRKB39HeQWJjztQ== + dependencies: + "@types/glob" "*" + "@types/node" "*" "@types/semver@^5.5.0": version "5.5.0" @@ -630,6 +657,18 @@ glob@^5.0.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.3: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glogg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" @@ -1490,6 +1529,13 @@ rimraf@2: dependencies: glob "^7.0.5" +rimraf@^2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index b9556197469..f6844162017 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -518,19 +518,20 @@ suite('window namespace tests', () => { return Promise.all([a, b]); }); - test('showWorkspaceFolderPick', async function () { - const p = window.showWorkspaceFolderPick(undefined); + // TODO@chrmarti Disabled due to flaky behaviour (https://github.com/Microsoft/vscode/issues/70887) + // test('showWorkspaceFolderPick', async function () { + // const p = window.showWorkspaceFolderPick(undefined); - await timeout(10); - await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); - try { - await p; - assert.ok(true); - } - catch (_error) { - assert.ok(false); - } - }); + // await timeout(10); + // await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); + // try { + // await p; + // assert.ok(true); + // } + // catch (_error) { + // assert.ok(false); + // } + // }); test('Default value for showInput Box not accepted when it fails validateInput, reversing #33691', async function () { const result = window.showInputBox({ diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index 53d4ae01136..dc700abdf1b 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -36,12 +36,14 @@ suite('workspace-namespace', () => { }); test('rootPath', () => { - if (vscode.workspace.rootPath) { - assert.ok(pathEquals(vscode.workspace.rootPath, join(__dirname, '../../testWorkspace'))); - } + assert.ok(pathEquals(vscode.workspace.rootPath!, join(__dirname, '../../testWorkspace'))); assert.throws(() => (vscode.workspace as any).rootPath = 'farboo'); }); + test('workspaceFile', () => { + assert.ok(!vscode.workspace.workspaceFile); + }); + test('workspaceFolders', () => { if (vscode.workspace.workspaceFolders) { assert.equal(vscode.workspace.workspaceFolders.length, 1); @@ -285,6 +287,30 @@ suite('workspace-namespace', () => { }); }); + test('events: onDidSaveTextDocument fires even for non dirty file when saved', () => { + return createRandomFile().then(file => { + let disposables: vscode.Disposable[] = []; + + let onDidSaveTextDocument = false; + disposables.push(vscode.workspace.onDidSaveTextDocument(e => { + assert.ok(pathEquals(e.uri.fsPath, file.fsPath)); + onDidSaveTextDocument = true; + })); + + return vscode.workspace.openTextDocument(file).then(doc => { + return vscode.window.showTextDocument(doc).then(() => { + return vscode.commands.executeCommand('workbench.action.files.save').then(() => { + assert.ok(onDidSaveTextDocument); + + disposeAll(disposables); + + return deleteFile(file); + }); + }); + }); + }); + }); + test('openTextDocument, with selection', function () { return createRandomFile('foo\nbar\nbar').then(file => { return vscode.workspace.openTextDocument(file).then(doc => { diff --git a/extensions/vscode-api-tests/src/workspace-tests/workspace.test.ts b/extensions/vscode-api-tests/src/workspace-tests/workspace.test.ts index f28967468a5..f018f581c42 100644 --- a/extensions/vscode-api-tests/src/workspace-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/workspace-tests/workspace.test.ts @@ -13,18 +13,18 @@ suite('workspace-namespace', () => { teardown(closeAllEditors); test('rootPath', () => { - if (vscode.workspace.rootPath) { - assert.ok(pathEquals(vscode.workspace.rootPath, join(__dirname, '../../testWorkspace'))); - } + assert.ok(pathEquals(vscode.workspace.rootPath!, join(__dirname, '../../testWorkspace'))); + }); + + test('workspaceFile', () => { + assert.ok(pathEquals(vscode.workspace.workspaceFile!.fsPath, join(__dirname, '../../testworkspace.code-workspace'))); }); test('workspaceFolders', () => { - if (vscode.workspace.workspaceFolders) { - assert.equal(vscode.workspace.workspaceFolders.length, 2); - assert.ok(pathEquals(vscode.workspace.workspaceFolders[0].uri.fsPath, join(__dirname, '../../testWorkspace'))); - assert.ok(pathEquals(vscode.workspace.workspaceFolders[1].uri.fsPath, join(__dirname, '../../testWorkspace2'))); - assert.ok(pathEquals(vscode.workspace.workspaceFolders[1].name, 'Test Workspace 2')); - } + assert.equal(vscode.workspace.workspaceFolders!.length, 2); + assert.ok(pathEquals(vscode.workspace.workspaceFolders![0].uri.fsPath, join(__dirname, '../../testWorkspace'))); + assert.ok(pathEquals(vscode.workspace.workspaceFolders![1].uri.fsPath, join(__dirname, '../../testWorkspace2'))); + assert.ok(pathEquals(vscode.workspace.workspaceFolders![1].name, 'Test Workspace 2')); }); test('getWorkspaceFolder', () => { diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index 763513a590a..517429bb6d7 100644 --- a/extensions/vscode-colorize-tests/package.json +++ b/extensions/vscode-colorize-tests/package.json @@ -8,8 +8,7 @@ "vscode": "*" }, "scripts": { - "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json", - "postinstall": "node ./node_modules/vscode/bin/install" + "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json" }, "devDependencies": { "@types/node": "^10.12.21", diff --git a/extensions/vscode-colorize-tests/src/typings/ref.d.ts b/extensions/vscode-colorize-tests/src/typings/ref.d.ts index 8ea9f802a47..a45a0c6353f 100644 --- a/extensions/vscode-colorize-tests/src/typings/ref.d.ts +++ b/extensions/vscode-colorize-tests/src/typings/ref.d.ts @@ -4,3 +4,4 @@ *--------------------------------------------------------------------------------------------*/ /// +/// diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 8c1f7f117c2..38c68f6e8f7 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.3.tgz#0eb320e4ace9b10eadf5bc6103286b0f8b7c224f" - integrity sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ== +typescript@3.5.0-dev.20190517: + version "3.5.0-dev.20190517" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.0-dev.20190517.tgz#5a85f1091cf33fde39b04f898c5730e30edd3e39" + integrity sha512-KoBHq6ytEApXKTDtmTu4Sp/tC5SPe4FpvwutLEANhwdMPblqZdh7APuH7I/ceMlgfHSa7B00JgF7NokUJQi0/g== diff --git a/package.json b/package.json index 9b65dde955a..aa3663cadbe 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.34.0", - "distro": "9abd1fcd07dd503b0ff9e7ceba3e7da178f8fbf0", + "version": "1.35.0", + "distro": "8301e897557c968307a198fe81379bb9fe5f87f3", "author": { "name": "Microsoft Corporation" }, @@ -23,7 +23,8 @@ "smoketest": "cd test/smoke && node test/index.js", "download-builtin-extensions": "node build/lib/builtInExtensions.js", "monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit", - "strict-initialization-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictPropertyInitialization" + "strict-initialization-watch": "tsc --watch -p src/tsconfig.json --noEmit --strictPropertyInitialization", + "web": "node scripts/code-web.js" }, "dependencies": { "applicationinsights": "1.0.8", @@ -51,7 +52,7 @@ "vscode-ripgrep": "^1.2.5", "vscode-sqlite3": "4.0.7", "vscode-textmate": "^4.0.1", - "vscode-xterm": "3.13.0-beta3", + "vscode-xterm": "3.14.0-beta2", "yauzl": "^2.9.1", "yazl": "^2.4.3" }, @@ -102,6 +103,7 @@ "gulp-uglify": "^3.0.0", "gulp-untar": "^0.0.7", "gulp-vinyl-zip": "^2.1.2", + "http-server": "^0.11.1", "husky": "^0.13.1", "innosetup-compiler": "^5.5.60", "is": "^3.1.0", @@ -114,6 +116,7 @@ "mkdirp": "^0.5.0", "mocha": "^2.2.5", "mocha-junit-reporter": "^1.17.0", + "opn": "^5.4.0", "optimist": "0.3.5", "p-all": "^1.0.0", "pump": "^1.0.1", @@ -124,8 +127,8 @@ "sinon": "^1.17.2", "source-map": "^0.4.4", "ts-loader": "^4.4.2", - "tslint": "^5.11.0", - "typescript": "3.4.1", + "tslint": "^5.16.0", + "typescript": "3.4.5", "typescript-formatter": "7.1.0", "typescript-tslint-plugin": "^0.0.7", "uglify-es": "^3.0.18", @@ -152,4 +155,4 @@ "windows-mutex": "0.2.1", "windows-process-tree": "0.2.3" } -} \ No newline at end of file +} diff --git a/resources/win32/bin/code.cmd b/resources/win32/bin/code.cmd index 4f31b474695..33c640f5dd1 100644 --- a/resources/win32/bin/code.cmd +++ b/resources/win32/bin/code.cmd @@ -2,5 +2,5 @@ setlocal set VSCODE_DEV= set ELECTRON_RUN_AS_NODE=1 -call "%~dp0..\@@NAME@@.exe" "%~dp0..\resources\app\out\cli.js" %* +"%~dp0..\@@NAME@@.exe" "%~dp0..\resources\app\out\cli.js" %* endlocal \ No newline at end of file diff --git a/resources/win32/bin/code.sh b/resources/win32/bin/code.sh index bf9f121dfe1..23e8a522007 100644 --- a/resources/win32/bin/code.sh +++ b/resources/win32/bin/code.sh @@ -6,30 +6,49 @@ COMMIT="@@COMMIT@@" APP_NAME="@@APPNAME@@" QUALITY="@@QUALITY@@" NAME="@@NAME@@" - -set -e - +VSCODE_PATH="$(dirname "$(dirname "$(realpath "$0")")")" +ELECTRON="$VSCODE_PATH/$NAME.exe" if grep -qi Microsoft /proc/version; then # in a wsl shell - WIN_CODE_CMD=$(wslpath -w "$(dirname "$(realpath "$0")")/$APP_NAME.cmd") - - WSL_EXT_ID="ms-vscode.remote-wsl" - WSL_EXT_WLOC=$(cmd.exe /c "$WIN_CODE_CMD" --locate-extension $WSL_EXT_ID) - if ! [ -z "$WSL_EXT_WLOC" ]; then - # replace \r\n with \n in WSL_EXT_WLOC, get linux path for - WSL_CODE=$(wslpath -u "${WSL_EXT_WLOC%%[[:cntrl:]]}")/scripts/wslCode.sh - $WSL_CODE $COMMIT $QUALITY "$WIN_CODE_CMD" "$APP_NAME" "$@" + fallback() { + # If running under older WSL, don't pass cli.js to Electron as + # environment vars cannot be transferred from WSL to Windows + # See: https://github.com/Microsoft/BashOnWindows/issues/1363 + # https://github.com/Microsoft/BashOnWindows/issues/1494 + "$ELECTRON" "$@" exit $? + } + WSL_BUILD=$(uname -r | sed -E 's/^.+-([0-9]+)-Microsoft/\1/') + # wslpath is not available prior to WSL build 17046 + # See: https://docs.microsoft.com/en-us/windows/wsl/release-notes#build-17046 + if [ -x /bin/wslpath ]; then + WIN_CODE_CMD=$(wslpath -w "$(dirname "$(realpath "$0")")/$APP_NAME.cmd") + # make sure the cwd is in the windows fs, otherwise there will be a warning from cmd + pushd "$(dirname "$0")" > /dev/null + WSL_EXT_ID="ms-vscode-remote.remote-wsl" + WSL_EXT_WLOC=$(cmd.exe /c "$WIN_CODE_CMD" --locate-extension $WSL_EXT_ID) + popd > /dev/null + if ! [ -z "$WSL_EXT_WLOC" ]; then + # replace \r\n with \n in WSL_EXT_WLOC, get linux path for + WSL_CODE=$(wslpath -u "${WSL_EXT_WLOC%%[[:cntrl:]]}")/scripts/wslCode.sh + "$WSL_CODE" $COMMIT $QUALITY "$WIN_CODE_CMD" "$APP_NAME" "$@" + exit $? + elif [ $WSL_BUILD -ge 17063 ] 2> /dev/null; then + # Since WSL build 17063, we just need to set WSLENV so that + # ELECTRON_RUN_AS_NODE is visible to the win32 process + # See: https://docs.microsoft.com/en-us/windows/wsl/release-notes#build-17063 + export WSLENV=ELECTRON_RUN_AS_NODE/w:$WSLENV + CLI=$(wslpath -m "$VSCODE_PATH/resources/app/out/cli.js") + else # $WSL_BUILD ∈ [17046, 17063) OR $WSL_BUILD is indeterminate + fallback "$@" + fi + else + fallback "$@" fi -fi - -VSCODE_PATH="$(dirname "$(dirname "$(realpath "$0")")")" - -if [ -x "$(command -v cygpath)" ]; then +elif [ -x "$(command -v cygpath)" ]; then CLI=$(cygpath -m "$VSCODE_PATH/resources/app/out/cli.js") else CLI="$VSCODE_PATH/resources/app/out/cli.js" fi -ELECTRON="$VSCODE_PATH/$NAME.exe" ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@" exit $? \ No newline at end of file diff --git a/scripts/code-cli.bat b/scripts/code-cli.bat index 927e3e00d22..6fbf5edd626 100644 --- a/scripts/code-cli.bat +++ b/scripts/code-cli.bat @@ -17,7 +17,7 @@ set CODE=".build\electron\%NAMESHORT%" node build\lib\electron.js if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js electron -:: Manage build-in extensions +:: Manage built-in extensions if "%1"=="--builtin" goto builtin :: Sync built-in extensions diff --git a/scripts/code-web.js b/scripts/code-web.js new file mode 100644 index 00000000000..da3afe33e47 --- /dev/null +++ b/scripts/code-web.js @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const httpServer = require('http-server'); +const opn = require('opn'); + +const url = 'http://127.0.0.1:8080/out/vs/code/browser/workbench/workbench.html'; + +httpServer.createServer({ root: '.', cache: 5 }).listen(8080); +console.log(`Open ${url} in your browser`); + +opn(url); \ No newline at end of file diff --git a/scripts/code.sh b/scripts/code.sh index 8227916f953..1f7cda590a1 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -60,7 +60,7 @@ function code-wsl() # in a wsl shell local WIN_CODE_CLI_CMD=$(wslpath -w "$ROOT/scripts/code-cli.bat") if ! [ -z "$WIN_CODE_CLI_CMD" ]; then - local WSL_EXT_ID="ms-vscode.remote-wsl" + local WSL_EXT_ID="ms-vscode-remote.remote-wsl" local WSL_EXT_WLOC=$(cmd.exe /c "$WIN_CODE_CLI_CMD" --locate-extension $WSL_EXT_ID) if ! [ -z "$WSL_EXT_WLOC" ]; then # replace \r\n with \n in WSL_EXT_WLOC diff --git a/src/buildfile.js b/src/buildfile.js index 889763f49b2..a7c0376985e 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -12,7 +12,7 @@ exports.base = [{ }]; exports.workbench = require('./vs/workbench/buildfile').collectModules(['vs/workbench/workbench.main']); -exports.workbenchNodeless = require('./vs/workbench/buildfile').collectModules(['vs/workbench/workbench.nodeless.main']); +exports.workbenchWeb = require('./vs/workbench/buildfile').collectModules(['vs/workbench/workbench.web.main']); exports.code = require('./vs/code/buildfile').collectModules(); diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index afcbcaff39c..32983503724 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -10,7 +10,7 @@ "preserveConstEnums": true, "target": "es5", "sourceMap": false, - "declaration": true, + "declaration": true }, "include": [ "typings/require.d.ts", diff --git a/src/typings/vscode-xterm.d.ts b/src/typings/vscode-xterm.d.ts index e425a37c43f..0e5e59a9b51 100644 --- a/src/typings/vscode-xterm.d.ts +++ b/src/typings/vscode-xterm.d.ts @@ -101,17 +101,6 @@ declare module 'vscode-xterm' { */ experimentalCharAtlas?: 'none' | 'static' | 'dynamic'; - /** - * (EXPERIMENTAL) Defines which implementation to use for buffer lines. - * - * - 'JsArray': The default/stable implementation. - * - 'TypedArray': The new experimental implementation based on TypedArrays that is expected to - * significantly boost performance and memory consumption. Use at your own risk. - * - * @deprecated This option will be removed in the future. - */ - experimentalBufferLineImpl?: 'JsArray' | 'TypedArray'; - /** * The font size used to render text. */ @@ -199,6 +188,18 @@ declare module 'vscode-xterm' { * The color theme of the terminal. */ theme?: ITheme; + + /** + * Whether "Windows mode" is enabled. Because Windows backends winpty and + * conpty operate by doing line wrapping on their side, xterm.js does not + * have access to wrapped lines. When Windows mode is enabled the following + * changes will be in effect: + * + * - Reflow is disabled. + * - Lines are assumed to be wrapped if the last character of the line is + * not whitespace. + */ + windowsMode?: boolean; } /** @@ -274,7 +275,7 @@ declare module 'vscode-xterm' { * A callback that fires when the mouse leaves a link. Note that this can * happen even when tooltipCallback hasn't fired for the link yet. */ - leaveCallback?: (event: MouseEvent, uri: string) => boolean | void; + leaveCallback?: () => void; /** * The priority of the link matcher, this defines the order in which the link @@ -306,6 +307,14 @@ declare module 'vscode-xterm' { dispose(): void; } + /** + * An event that can be listened to. + * @returns an `IDisposable` to stop listening. + */ + export interface IEvent { + (listener: (e: T) => any): IDisposable; + } + export interface IMarker extends IDisposable { readonly id: number; readonly isDisposed: boolean; @@ -325,28 +334,39 @@ declare module 'vscode-xterm' { /** * The element containing the terminal. */ - element: HTMLElement; + readonly element: HTMLElement; /** * The textarea that accepts input for the terminal. */ - textarea: HTMLTextAreaElement; + readonly textarea: HTMLTextAreaElement; /** - * The number of rows in the terminal's viewport. + * The number of rows in the terminal's viewport. Use + * `ITerminalOptions.rows` to set this in the constructor and + * `Terminal.resize` for when the terminal exists. */ - rows: number; + readonly rows: number; /** - * The number of columns in the terminal's viewport. + * The number of columns in the terminal's viewport. Use + * `ITerminalOptions.cols` to set this in the constructor and + * `Terminal.resize` for when the terminal exists. */ - cols: number; + readonly cols: number; + + /** + * (EXPERIMENTAL) The terminal's current buffer, this might be either the + * normal buffer or the alt buffer depending on what's running in the + * terminal. + */ + readonly buffer: IBuffer; /** * (EXPERIMENTAL) Get all markers registered against the buffer. If the alt * buffer is active this will always return []. */ - markers: IMarker[]; + readonly markers: ReadonlyArray; /** * Natural language strings that can be localized. @@ -360,6 +380,70 @@ declare module 'vscode-xterm' { */ constructor(options?: ITerminalOptions); + /** + * Adds an event listener for the cursor moves. + * @returns an `IDisposable` to stop listening. + */ + onCursorMove: IEvent; + + /** + * Adds an event listener for when a data event fires. This happens for + * example when the user types or pastes into the terminal. The event value + * is whatever `string` results, in a typical setup, this should be passed + * on to the backing pty. + * @returns an `IDisposable` to stop listening. + */ + onData: IEvent; + + /** + * Adds an event listener for a key is pressed. The event value contains the + * string that will be sent in the data event as well as the DOM event that + * triggered it. + * @returns an `IDisposable` to stop listening. + */ + onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>; + + /** + * Adds an event listener for when a line feed is added. + * @returns an `IDisposable` to stop listening. + */ + onLineFeed: IEvent; + + /** + * Adds an event listener for when a scroll occurs. The event value is the + * new position of the viewport. + * @returns an `IDisposable` to stop listening. + */ + onScroll: IEvent; + + /** + * Adds an event listener for when a selection change occurs. + * @returns an `IDisposable` to stop listening. + */ + onSelectionChange: IEvent; + + /** + * Adds an event listener for when rows are rendered. The event value + * contains the start row and end rows of the rendered area (ranges from `0` + * to `Terminal.rows - 1`). + * @returns an `IDisposable` to stop listening. + */ + onRender: IEvent<{ start: number, end: number }>; + + /** + * Adds an event listener for when the terminal is resized. The event value + * contains the new size. + * @returns an `IDisposable` to stop listening. + */ + onResize: IEvent<{ cols: number, rows: number }>; + + /** + * Adds an event listener for when an OSC 0 or OSC 2 title change occurs. + * The event value is the new title. + * @returns an `IDisposable` to stop listening. + */ + onTitleChange: IEvent; + /** * Unfocus the terminal. */ @@ -374,54 +458,63 @@ declare module 'vscode-xterm' { * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'blur' | 'focus' | 'linefeed' | 'selection', listener: () => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'data', listener: (...args: any[]) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'key', listener: (key: string, event: KeyboardEvent) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'keypress' | 'keydown', listener: (event: KeyboardEvent) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'refresh', listener: (data: { start: number, end: number }) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'resize', listener: (data: { cols: number, rows: number }) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'scroll', listener: (ydisp: number) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: 'title', listener: (title: string) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener)` instead. */ on(type: string, listener: (...args: any[]) => void): void; @@ -429,6 +522,7 @@ declare module 'vscode-xterm' { * Deregisters an event listener. * @param type The type of the event. * @param listener The listener. + * @deprecated use `Terminal.onEvent(listener).dispose()` instead. */ off(type: 'blur' | 'focus' | 'linefeed' | 'selection' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void; @@ -446,22 +540,19 @@ declare module 'vscode-xterm' { * be used to conveniently remove the event listener. * @param type The type of event. * @param handler The event handler. + * @deprecated use `Terminal.onEvent(listener)` instead. */ addDisposableListener(type: string, handler: (...args: any[]) => void): IDisposable; /** - * Resizes the terminal. + * Resizes the terminal. It's best practice to debounce calls to resize, + * this will help ensure that the pty can respond to the resize event + * before another one occurs. * @param x The number of columns to resize to. * @param y The number of rows to resize to. */ resize(columns: number, rows: number): void; - /** - * Writes text to the terminal, followed by a break line character (\n). - * @param data The text to write to the terminal. - */ - writeln(data: string): void; - /** * Opens the terminal within an element. * @param parent The element to create the terminal within. This element @@ -476,11 +567,35 @@ declare module 'vscode-xterm' { * should be processed by the terminal and what keys should not. * @param customKeyEventHandler The custom KeyboardEvent handler to attach. * This is a function that takes a KeyboardEvent, allowing consumers to stop - * propogation and/or prevent the default action. The function returns + * propagation and/or prevent the default action. The function returns * whether the event should be processed by xterm.js. */ attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; + /** + * (EXPERIMENTAL) Adds a handler for CSI escape sequences. + * @param flag The flag should be one-character string, which specifies the + * final character (e.g "m" for SGR) of the CSI sequence. + * @param callback The function to handle the escape sequence. The callback + * is called with the numerical params, as well as the special characters + * (e.g. "$" for DECSCPP). Return true if the sequence was handled; false if + * we should try a previous handler (set by addCsiHandler or setCsiHandler). + * The most recently-added handler is tried first. + * @return An IDisposable you can call to remove this handler. + */ + addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable; + + /** + * (EXPERIMENTAL) Adds a handler for OSC escape sequences. + * @param ident The number (first parameter) of the sequence. + * @param callback The function to handle the escape sequence. The callback + * is called with OSC data string. Return true if the sequence was handled; + * false if we should try a previous handler (set by addOscHandler or + * setOscHandler). The most recently-added handler is tried first. + * @return An IDisposable you can call to remove this handler. + */ + addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable; + /** * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to * be matched and handled. @@ -555,11 +670,24 @@ declare module 'vscode-xterm' { */ getSelection(): string; + /** + * Gets the selection position or undefined if there is no selection. + */ + getSelectionPosition(): ISelectionPosition | undefined; + /** * Clears the current terminal selection. */ clearSelection(): void; + /** + * Selects text within the terminal. + * @param column The column the selection starts at.. + * @param row The row the selection starts at. + * @param length The length of the selection. + */ + select(column: number, row: number, length: number): void; + /** * Selects all text within the terminal. */ @@ -624,6 +752,20 @@ declare module 'vscode-xterm' { */ write(data: string): void; + /** + * Writes text to the terminal, followed by a break line character (\n). + * @param data The text to write to the terminal. + */ + writeln(data: string): void; + + /** + * Writes UTF8 data to the terminal. + * This has a slight performance advantage over the string based write method + * due to lesser data conversions needed on the way from the pty to xterm.js. + * @param data The data to write to the terminal. + */ + writeUtf8(data: Uint8Array): void; + /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -633,7 +775,7 @@ declare module 'vscode-xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean; + getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode'): boolean; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -684,7 +826,7 @@ declare module 'vscode-xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void; + setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'windowsMode', value: boolean): void; /** * Sets an option on the terminal. * @param key The option key. @@ -739,8 +881,134 @@ declare module 'vscode-xterm' { * Applies an addon to the Terminal prototype, making it available to all * newly created Terminals. * @param addon The addon to apply. + * @deprecated Use the new loadAddon API/addon format. */ static applyAddon(addon: any): void; + + /** + * (EXPERIMENTAL) Loads an addon into this instance of xterm.js. + * @param addon The addon to load. + */ + loadAddon(addon: ITerminalAddon): void; + } + + /** + * An addon that can provide additional functionality to the terminal. + */ + export interface ITerminalAddon extends IDisposable { + /** + * (EXPERIMENTAL) This is called when the addon is activated within xterm.js. + */ + activate(terminal: Terminal): void; + } + + /** + * An object representing a selecrtion within the terminal. + */ + interface ISelectionPosition { + /** + * The start column of the selection. + */ + startColumn: number; + + /** + * The start row of the selection. + */ + startRow: number; + + /** + * The end column of the selection. + */ + endColumn: number; + + /** + * The end row of the selection. + */ + endRow: number; + } + + interface IBuffer { + /** + * The y position of the cursor. This ranges between `0` (when the + * cursor is at baseY) and `Terminal.rows - 1` (when the cursor is on the + * last row). + */ + readonly cursorY: number; + + /** + * The x position of the cursor. This ranges between `0` (left side) and + * `Terminal.cols - 1` (right side). + */ + readonly cursorX: number; + + /** + * The line within the buffer where the top of the viewport is. + */ + readonly viewportY: number; + + /** + * The line within the buffer where the top of the bottom page is (when + * fully scrolled down); + */ + readonly baseY: number; + + /** + * The amount of lines in the buffer. + */ + readonly length: number; + + /** + * Gets a line from the buffer, or undefined if the line index does not exist. + * + * Note that the result of this function should be used immediately after calling as when the + * terminal updates it could lead to unexpected behavior. + * + * @param y The line index to get. + */ + getLine(y: number): IBufferLine | undefined; + } + + interface IBufferLine { + /** + * Whether the line is wrapped from the previous line. + */ + readonly isWrapped: boolean; + + /** + * Gets a cell from the line, or undefined if the line index does not exist. + * + * Note that the result of this function should be used immediately after calling as when the + * terminal updates it could lead to unexpected behavior. + * + * @param x The character index to get. + */ + getCell(x: number): IBufferCell; + + /** + * Gets the line as a string. Note that this is gets only the string for the line, not taking + * isWrapped into account. + * + * @param trimRight Whether to trim any whitespace at the right of the line. + * @param startColumn The column to start from (inclusive). + * @param endColumn The column to end at (exclusive). + */ + translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string; + } + + interface IBufferCell { + /** + * The character within the cell. + */ + readonly char: string; + + /** + * The width of the character. Some examples: + * + * - This is `1` for most cells. + * - This is `2` for wide character like CJK glyphs. + * - This is `0` for cells immediately following cells with a width of `2`. + */ + readonly width: number; } } @@ -750,22 +1018,10 @@ declare module 'vscode-xterm' { interface TerminalCore { debug: boolean; - buffer: { - y: number; - ybase: number; - ydisp: number; - x: number; - lines: any[]; - - translateBufferLineToString(lineIndex: number, trimRight: boolean): string; - }; - handler(text: string): void; - /** - * Emit an event on the terminal. - */ - emit(type: string, data: any): void; + _onScroll: IEventEmitter2; + _onKey: IEventEmitter2<{ key: string }>; charMeasure?: { height: number, width: number }; @@ -775,6 +1031,10 @@ declare module 'vscode-xterm' { }; } + interface IEventEmitter2 { + fire(e: T): void; + } + interface ISearchOptions { /** * Whether the find should be done as a regex. @@ -794,7 +1054,6 @@ declare module 'vscode-xterm' { _core: TerminalCore; webLinksInit(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): void; - winptyCompatInit(): void; /** * Find the next instance of the term, then scroll to and select it. If it diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 16414ef34d3..6a0cc217897 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -45,13 +45,13 @@ class WindowManager { // --- Pixel Ratio public getPixelRatio(): number { - let ctx = document.createElement('canvas').getContext('2d'); + let ctx: any = document.createElement('canvas').getContext('2d'); let dpr = window.devicePixelRatio || 1; - let bsr = (ctx).webkitBackingStorePixelRatio || - (ctx).mozBackingStorePixelRatio || - (ctx).msBackingStorePixelRatio || - (ctx).oBackingStorePixelRatio || - (ctx).backingStorePixelRatio || 1; + let bsr = ctx.webkitBackingStorePixelRatio || + ctx.mozBackingStorePixelRatio || + ctx.msBackingStorePixelRatio || + ctx.oBackingStorePixelRatio || + ctx.backingStorePixelRatio || 1; return dpr / bsr; } diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index 4fa0bf17ce0..d943a131474 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IAction, IActionRunner } from 'vs/base/common/actions'; -import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { SubmenuAction } from 'vs/base/browser/ui/menu/menu'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; @@ -25,7 +25,7 @@ export class ContextSubMenu extends SubmenuAction { export interface IContextMenuDelegate { getAnchor(): HTMLElement | { x: number; y: number; width?: number; height?: number; }; getActions(): Array; - getActionItem?(action: IAction): IActionItem | undefined; + getActionViewItem?(action: IAction): IActionViewItem | undefined; getActionsContext?(event?: IContextMenuEvent): any; getKeyBinding?(action: IAction): ResolvedKeybinding | undefined; getMenuClassName?(): string; diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts index 93144774216..5d94e04008d 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -315,7 +315,7 @@ function parseFormattedText(content: string): IFormatParseTree { children: [] }; - let actionItemIndex = 0; + let actionViewItemIndex = 0; let current = root; const stack: IFormatParseTree[] = []; const stream = new StringStream(content); @@ -345,8 +345,8 @@ function parseFormattedText(content: string): IFormatParseTree { }; if (type === FormatType.Action) { - newCurrent.index = actionItemIndex; - actionItemIndex++; + newCurrent.index = actionViewItemIndex; + actionViewItemIndex++; } current.children!.push(newCurrent); diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index bbe176c19c0..131c756a817 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -18,7 +18,7 @@ import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview import { Event, Emitter } from 'vs/base/common/event'; import { asArray } from 'vs/base/common/arrays'; -export interface IActionItem { +export interface IActionViewItem { actionRunner: IActionRunner; setActionContext(context: any): void; render(element: HTMLElement): void; @@ -28,12 +28,12 @@ export interface IActionItem { dispose(): void; } -export interface IBaseActionItemOptions { +export interface IBaseActionViewItemOptions { draggable?: boolean; isMenu?: boolean; } -export class BaseActionItem extends Disposable implements IActionItem { +export class BaseActionViewItem extends Disposable implements IActionViewItem { element?: HTMLElement; _context: any; @@ -41,7 +41,7 @@ export class BaseActionItem extends Disposable implements IActionItem { private _actionRunner: IActionRunner; - constructor(context: any, action: IAction, protected options?: IBaseActionItemOptions) { + constructor(context: any, action: IAction, protected options?: IBaseActionViewItemOptions) { super(); this._context = context || this; @@ -226,20 +226,20 @@ export class Separator extends Action { } } -export interface IActionItemOptions extends IBaseActionItemOptions { +export interface IActionViewItemOptions extends IBaseActionViewItemOptions { icon?: boolean; label?: boolean; keybinding?: string | null; } -export class ActionItem extends BaseActionItem { +export class ActionViewItem extends BaseActionViewItem { protected label: HTMLElement; - protected options: IActionItemOptions; + protected options: IActionViewItemOptions; private cssClass?: string; - constructor(context: any, action: IAction, options: IActionItemOptions = {}) { + constructor(context: any, action: IAction, options: IActionViewItemOptions = {}) { super(context, action, options); this.options = options; @@ -363,14 +363,14 @@ export interface ActionTrigger { keyDown: boolean; } -export interface IActionItemProvider { - (action: IAction): IActionItem | undefined; +export interface IActionViewItemProvider { + (action: IAction): IActionViewItem | undefined; } export interface IActionBarOptions { orientation?: ActionsOrientation; context?: any; - actionItemProvider?: IActionItemProvider; + actionViewItemProvider?: IActionViewItemProvider; actionRunner?: IActionRunner; ariaLabel?: string; animated?: boolean; @@ -386,7 +386,7 @@ const defaultOptions: IActionBarOptions = { } }; -export interface IActionOptions extends IActionItemOptions { +export interface IActionOptions extends IActionViewItemOptions { index?: number; } @@ -397,8 +397,8 @@ export class ActionBar extends Disposable implements IActionRunner { private _actionRunner: IActionRunner; private _context: any; - // Items - items: IActionItem[]; + // View Items + viewItems: IActionViewItem[]; protected focusedItem?: number; private focusTracker: DOM.IFocusTracker; @@ -438,7 +438,7 @@ export class ActionBar extends Disposable implements IActionRunner { this._register(this._actionRunner.onDidRun(e => this._onDidRun.fire(e))); this._register(this._actionRunner.onDidBeforeRun(e => this._onDidBeforeRun.fire(e))); - this.items = []; + this.viewItems = []; this.focusedItem = undefined; this.domNode = document.createElement('div'); @@ -575,7 +575,7 @@ export class ActionBar extends Disposable implements IActionRunner { set context(context: any) { this._context = context; - this.items.forEach(i => i.setActionContext(context)); + this.viewItems.forEach(i => i.setActionContext(context)); } get actionRunner(): IActionRunner { @@ -585,7 +585,7 @@ export class ActionBar extends Disposable implements IActionRunner { set actionRunner(actionRunner: IActionRunner) { if (actionRunner) { this._actionRunner = actionRunner; - this.items.forEach(item => item.actionRunner = actionRunner); + this.viewItems.forEach(item => item.actionRunner = actionRunner); } } @@ -599,36 +599,36 @@ export class ActionBar extends Disposable implements IActionRunner { let index = types.isNumber(options.index) ? options.index : null; actions.forEach((action: IAction) => { - const actionItemElement = document.createElement('li'); - actionItemElement.className = 'action-item'; - actionItemElement.setAttribute('role', 'presentation'); + const actionViewItemElement = document.createElement('li'); + actionViewItemElement.className = 'action-item'; + actionViewItemElement.setAttribute('role', 'presentation'); // Prevent native context menu on actions - this._register(DOM.addDisposableListener(actionItemElement, DOM.EventType.CONTEXT_MENU, (e: DOM.EventLike) => { + this._register(DOM.addDisposableListener(actionViewItemElement, DOM.EventType.CONTEXT_MENU, (e: DOM.EventLike) => { e.preventDefault(); e.stopPropagation(); })); - let item: IActionItem | undefined; + let item: IActionViewItem | undefined; - if (this.options.actionItemProvider) { - item = this.options.actionItemProvider(action); + if (this.options.actionViewItemProvider) { + item = this.options.actionViewItemProvider(action); } if (!item) { - item = new ActionItem(this.context, action, options); + item = new ActionViewItem(this.context, action, options); } item.actionRunner = this._actionRunner; item.setActionContext(this.context); - item.render(actionItemElement); + item.render(actionViewItemElement); if (index === null || index < 0 || index >= this.actionsList.children.length) { - this.actionsList.appendChild(actionItemElement); - this.items.push(item); + this.actionsList.appendChild(actionViewItemElement); + this.viewItems.push(item); } else { - this.actionsList.insertBefore(actionItemElement, this.actionsList.children[index]); - this.items.splice(index, 0, item); + this.actionsList.insertBefore(actionViewItemElement, this.actionsList.children[index]); + this.viewItems.splice(index, 0, item); index++; } }); @@ -657,23 +657,23 @@ export class ActionBar extends Disposable implements IActionRunner { } pull(index: number): void { - if (index >= 0 && index < this.items.length) { + if (index >= 0 && index < this.viewItems.length) { this.actionsList.removeChild(this.actionsList.childNodes[index]); - dispose(this.items.splice(index, 1)); + dispose(this.viewItems.splice(index, 1)); } } clear(): void { - this.items = dispose(this.items); + this.viewItems = dispose(this.viewItems); DOM.clearNode(this.actionsList); } length(): number { - return this.items.length; + return this.viewItems.length; } isEmpty(): boolean { - return this.items.length === 0; + return this.viewItems.length === 0; } focus(index?: number): void; @@ -691,7 +691,7 @@ export class ActionBar extends Disposable implements IActionRunner { if (selectFirst && typeof this.focusedItem === 'undefined') { // Focus the first enabled item - this.focusedItem = this.items.length - 1; + this.focusedItem = this.viewItems.length - 1; this.focusNext(); } else { if (index !== undefined) { @@ -704,15 +704,15 @@ export class ActionBar extends Disposable implements IActionRunner { protected focusNext(): void { if (typeof this.focusedItem === 'undefined') { - this.focusedItem = this.items.length - 1; + this.focusedItem = this.viewItems.length - 1; } const startIndex = this.focusedItem; - let item: IActionItem; + let item: IActionViewItem; do { - this.focusedItem = (this.focusedItem + 1) % this.items.length; - item = this.items[this.focusedItem]; + this.focusedItem = (this.focusedItem + 1) % this.viewItems.length; + item = this.viewItems[this.focusedItem]; } while (this.focusedItem !== startIndex && !item.isEnabled()); if (this.focusedItem === startIndex && !item.isEnabled()) { @@ -728,16 +728,16 @@ export class ActionBar extends Disposable implements IActionRunner { } const startIndex = this.focusedItem; - let item: IActionItem; + let item: IActionViewItem; do { this.focusedItem = this.focusedItem - 1; if (this.focusedItem < 0) { - this.focusedItem = this.items.length - 1; + this.focusedItem = this.viewItems.length - 1; } - item = this.items[this.focusedItem]; + item = this.viewItems[this.focusedItem]; } while (this.focusedItem !== startIndex && !item.isEnabled()); if (this.focusedItem === startIndex && !item.isEnabled()) { @@ -752,21 +752,21 @@ export class ActionBar extends Disposable implements IActionRunner { this.actionsList.focus(); } - for (let i = 0; i < this.items.length; i++) { - const item = this.items[i]; - const actionItem = item; + for (let i = 0; i < this.viewItems.length; i++) { + const item = this.viewItems[i]; + const actionViewItem = item; if (i === this.focusedItem) { - if (types.isFunction(actionItem.isEnabled)) { - if (actionItem.isEnabled() && types.isFunction(actionItem.focus)) { - actionItem.focus(fromRight); + if (types.isFunction(actionViewItem.isEnabled)) { + if (actionViewItem.isEnabled() && types.isFunction(actionViewItem.focus)) { + actionViewItem.focus(fromRight); } else { this.actionsList.focus(); } } } else { - if (types.isFunction(actionItem.blur)) { - actionItem.blur(); + if (types.isFunction(actionViewItem.blur)) { + actionViewItem.blur(); } } } @@ -778,16 +778,16 @@ export class ActionBar extends Disposable implements IActionRunner { } // trigger action - const actionItem = this.items[this.focusedItem]; - if (actionItem instanceof BaseActionItem) { - const context = (actionItem._context === null || actionItem._context === undefined) ? event : actionItem._context; - this.run(actionItem._action, context); + const actionViewItem = this.viewItems[this.focusedItem]; + if (actionViewItem instanceof BaseActionViewItem) { + const context = (actionViewItem._context === null || actionViewItem._context === undefined) ? event : actionViewItem._context; + this.run(actionViewItem._action, context); } } private cancel(): void { if (document.activeElement instanceof HTMLElement) { - (document.activeElement).blur(); // remove focus from focused action + document.activeElement.blur(); // remove focus from focused action } this._onDidCancel.fire(); @@ -798,8 +798,8 @@ export class ActionBar extends Disposable implements IActionRunner { } dispose(): void { - dispose(this.items); - this.items = []; + dispose(this.viewItems); + this.viewItems = []; DOM.removeNode(this.getContainer()); @@ -807,7 +807,7 @@ export class ActionBar extends Disposable implements IActionRunner { } } -export class SelectActionItem extends BaseActionItem { +export class SelectActionViewItem extends BaseActionViewItem { protected selectBox: SelectBox; constructor(ctx: any, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) { diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index 73f7ca9722e..a4419eda426 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -11,7 +11,7 @@ import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import * as objects from 'vs/base/common/objects'; -import { BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; export interface ICheckboxOpts extends ICheckboxStyles { @@ -28,7 +28,7 @@ const defaultOpts = { inputActiveOptionBorder: Color.fromHex('#007ACC') }; -export class CheckboxActionItem extends BaseActionItem { +export class CheckboxActionViewItem extends BaseActionViewItem { private checkbox: Checkbox; private disposables: IDisposable[] = []; diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 193359ecc21..02ead6f7eaf 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -132,13 +132,13 @@ export class ContextView extends Disposable { ContextView.BUBBLE_UP_EVENTS.forEach(event => { toDisposeOnSetContainer.push(DOM.addStandardDisposableListener(this.container!, event, (e: Event) => { - this.onDOMEvent(e, document.activeElement, false); + this.onDOMEvent(e, false); })); }); ContextView.BUBBLE_DOWN_EVENTS.forEach(event => { toDisposeOnSetContainer.push(DOM.addStandardDisposableListener(this.container!, event, (e: Event) => { - this.onDOMEvent(e, document.activeElement, true); + this.onDOMEvent(e, true); }, true)); }); @@ -213,13 +213,11 @@ export class ContextView extends Disposable { height: elementPosition.height }; } else { - let realAnchor = anchor; - around = { - top: realAnchor.y, - left: realAnchor.x, - width: realAnchor.width || 1, - height: realAnchor.height || 2 + top: anchor.y, + left: anchor.x, + width: anchor.width || 1, + height: anchor.height || 2 }; } @@ -277,7 +275,7 @@ export class ContextView extends Disposable { return !!this.delegate; } - private onDOMEvent(e: Event, element: HTMLElement, onCapture: boolean): void { + private onDOMEvent(e: Event, onCapture: boolean): void { if (this.delegate) { if (this.delegate.onDOMEvent) { this.delegate.onDOMEvent(e, document.activeElement); diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 7172974334c..6dc7d9ee067 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -6,7 +6,7 @@ import 'vs/css!./dialog'; import * as nls from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; -import { $, hide, show, EventHelper, clearNode, removeClasses, addClass, removeNode } from 'vs/base/browser/dom'; +import { $, hide, show, EventHelper, clearNode, removeClasses, addClass, removeNode, isAncestor } from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -15,11 +15,13 @@ import { ButtonGroup, IButtonStyles } from 'vs/base/browser/ui/button/button'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { isMacintosh } from 'vs/base/common/platform'; export interface IDialogOptions { cancelId?: number; detail?: string; type?: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending'; + keyEventProcessor?: (event: StandardKeyboardEvent) => void; } export interface IDialogStyles extends IButtonStyles { @@ -29,6 +31,11 @@ export interface IDialogStyles extends IButtonStyles { dialogBorder?: Color; } +interface ButtonMapEntry { + label: string; + index: number; +} + export class Dialog extends Disposable { private element: HTMLElement | undefined; private modal: HTMLElement | undefined; @@ -38,6 +45,7 @@ export class Dialog extends Disposable { private toolbarContainer: HTMLElement | undefined; private buttonGroup: ButtonGroup | undefined; private styles: IDialogStyles | undefined; + private focusToReturn: HTMLElement | undefined; constructor(private container: HTMLElement, private message: string, private buttons: string[], private options: IDialogOptions) { super(); @@ -71,6 +79,8 @@ export class Dialog extends Disposable { } async show(): Promise { + this.focusToReturn = document.activeElement as HTMLElement; + return new Promise((resolve) => { if (!this.element || !this.buttonsContainer || !this.iconElement || !this.toolbarContainer) { resolve(0); @@ -88,12 +98,13 @@ export class Dialog extends Disposable { let focusedButton = 0; this.buttonGroup = new ButtonGroup(this.buttonsContainer, this.buttons.length, { title: true }); + const buttonMap = this.rearrangeButtons(this.buttons, this.options.cancelId); this.buttonGroup.buttons.forEach((button, index) => { - button.label = mnemonicButtonLabel(this.buttons[index], true); + button.label = mnemonicButtonLabel(buttonMap[index].label, true); this._register(button.onDidClick(e => { EventHelper.stop(e); - resolve(index); + resolve(buttonMap[index].index); })); }); @@ -103,19 +114,26 @@ export class Dialog extends Disposable { return; } + let eventHandled = false; if (this.buttonGroup) { if (evt.equals(KeyMod.Shift | KeyCode.Tab) || evt.equals(KeyCode.LeftArrow)) { focusedButton = focusedButton + this.buttonGroup.buttons.length - 1; focusedButton = focusedButton % this.buttonGroup.buttons.length; this.buttonGroup.buttons[focusedButton].focus(); + eventHandled = true; } else if (evt.equals(KeyCode.Tab) || evt.equals(KeyCode.RightArrow)) { focusedButton++; focusedButton = focusedButton % this.buttonGroup.buttons.length; this.buttonGroup.buttons[focusedButton].focus(); + eventHandled = true; } } - EventHelper.stop(e, true); + if (eventHandled) { + EventHelper.stop(e, true); + } else if (this.options.keyEventProcessor) { + this.options.keyEventProcessor(evt); + } })); this._register(domEvent(window, 'keyup', true)((e: KeyboardEvent) => { @@ -127,6 +145,19 @@ export class Dialog extends Disposable { } })); + this._register(domEvent(this.element, 'focusout', false)((e: FocusEvent) => { + if (!!e.relatedTarget && !!this.element) { + if (!isAncestor(e.relatedTarget as HTMLElement, this.element)) { + this.focusToReturn = e.relatedTarget as HTMLElement; + + if (e.target) { + (e.target as HTMLElement).focus(); + EventHelper.stop(e, true); + } + } + } + })); + removeClasses(this.iconElement, 'icon-error', 'icon-warning', 'icon-info'); switch (this.options.type) { @@ -198,5 +229,28 @@ export class Dialog extends Disposable { removeNode(this.modal); this.modal = undefined; } + + if (this.focusToReturn && isAncestor(this.focusToReturn, document.body)) { + this.focusToReturn.focus(); + this.focusToReturn = undefined; + } + } + + private rearrangeButtons(buttons: Array, cancelId: number | undefined): ButtonMapEntry[] { + const buttonMap: ButtonMapEntry[] = []; + // Maps each button to its current label and old index so that when we move them around it's not a problem + buttons.forEach((button, index) => { + buttonMap.push({ label: button, index: index }); + }); + + if (isMacintosh) { + if (cancelId !== undefined) { + const cancelButton = buttonMap.splice(cancelId, 1)[0]; + buttonMap.reverse(); + buttonMap.splice(buttonMap.length - 1, 0, cancelButton); + } + } + + return buttonMap; } } \ No newline at end of file diff --git a/src/vs/base/browser/ui/dialog/pending-dark.svg b/src/vs/base/browser/ui/dialog/pending-dark.svg index bbf6e8d84cf..5f388381162 100644 --- a/src/vs/base/browser/ui/dialog/pending-dark.svg +++ b/src/vs/base/browser/ui/dialog/pending-dark.svg @@ -2,7 +2,7 @@ { for (let i = 0; i < element.childNodes.length; i++) { - const child = element.childNodes.item(i); + const child = element.childNodes.item(i); - const tagName = (child).tagName && (child).tagName.toLowerCase(); + const tagName = child.tagName && child.tagName.toLowerCase(); if (tagName === 'img') { element.removeChild(child); } else { diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index b4142731b13..48b0cef6866 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -234,8 +234,8 @@ export class SplitView extends Disposable { }); const sashEventMapper = this.orientation === Orientation.VERTICAL - ? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey } as ISashEvent) - : (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX, alt: e.altKey } as ISashEvent); + ? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey }) + : (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX, alt: e.altKey }); const onStart = Event.map(sash.onDidStart, sashEventMapper); const onStartDisposable = onStart(this.onSashStart, this); diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 1c144b24096..557df5fada7 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -6,8 +6,8 @@ import 'vs/css!./toolbar'; import * as nls from 'vs/nls'; import { Action, IActionRunner, IAction } from 'vs/base/common/actions'; -import { ActionBar, ActionsOrientation, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IContextMenuProvider, DropdownMenuActionItem } from 'vs/base/browser/ui/dropdown/dropdown'; +import { ActionBar, ActionsOrientation, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IContextMenuProvider, DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; @@ -17,7 +17,7 @@ export const CONTEXT = 'context.toolbar'; export interface IToolBarOptions { orientation?: ActionsOrientation; - actionItemProvider?: IActionItemProvider; + actionViewItemProvider?: IActionViewItemProvider; ariaLabel?: string; getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined; actionRunner?: IActionRunner; @@ -32,7 +32,7 @@ export class ToolBar extends Disposable { private options: IToolBarOptions; private actionBar: ActionBar; private toggleMenuAction: ToggleMenuAction; - private toggleMenuActionItem?: DropdownMenuActionItem; + private toggleMenuActionViewItem?: DropdownMenuActionViewItem; private hasSecondaryActions: boolean; private lookupKeybindings: boolean; @@ -42,7 +42,7 @@ export class ToolBar extends Disposable { this.options = options; this.lookupKeybindings = typeof this.options.getKeyBinding === 'function'; - this.toggleMenuAction = this._register(new ToggleMenuAction(() => this.toggleMenuActionItem && this.toggleMenuActionItem.show(), options.toggleMenuTitle)); + this.toggleMenuAction = this._register(new ToggleMenuAction(() => this.toggleMenuActionViewItem && this.toggleMenuActionViewItem.show(), options.toggleMenuTitle)); let element = document.createElement('div'); element.className = 'monaco-toolbar'; @@ -52,33 +52,33 @@ export class ToolBar extends Disposable { orientation: options.orientation, ariaLabel: options.ariaLabel, actionRunner: options.actionRunner, - actionItemProvider: (action: Action) => { + actionViewItemProvider: (action: Action) => { // Return special action item for the toggle menu action if (action.id === ToggleMenuAction.ID) { // Dispose old - if (this.toggleMenuActionItem) { - this.toggleMenuActionItem.dispose(); + if (this.toggleMenuActionViewItem) { + this.toggleMenuActionViewItem.dispose(); } // Create new - this.toggleMenuActionItem = new DropdownMenuActionItem( + this.toggleMenuActionViewItem = new DropdownMenuActionViewItem( action, (action).menuActions, contextMenuProvider, - this.options.actionItemProvider, + this.options.actionViewItemProvider, this.actionRunner, this.options.getKeyBinding, 'toolbar-toggle-more', this.options.anchorAlignmentProvider ); - this.toggleMenuActionItem!.setActionContext(this.actionBar.context); + this.toggleMenuActionViewItem!.setActionContext(this.actionBar.context); - return this.toggleMenuActionItem; + return this.toggleMenuActionViewItem; } - return options.actionItemProvider ? options.actionItemProvider(action) : undefined; + return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined; } })); } @@ -93,8 +93,8 @@ export class ToolBar extends Disposable { set context(context: any) { this.actionBar.context = context; - if (this.toggleMenuActionItem) { - this.toggleMenuActionItem.setActionContext(context); + if (this.toggleMenuActionViewItem) { + this.toggleMenuActionViewItem.setActionContext(context); } } @@ -156,9 +156,9 @@ export class ToolBar extends Disposable { } dispose(): void { - if (this.toggleMenuActionItem) { - this.toggleMenuActionItem.dispose(); - this.toggleMenuActionItem = undefined; + if (this.toggleMenuActionViewItem) { + this.toggleMenuActionViewItem.dispose(); + this.toggleMenuActionViewItem = undefined; } super.dispose(); diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index dc8c82f4db3..14757bf2117 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -789,12 +789,18 @@ class Trait { return; } + this._set(nodes, false, browserEvent); + } + + private _set(nodes: ITreeNode[], silent: boolean, browserEvent?: UIEvent): void { this.nodes = [...nodes]; this.elements = undefined; this._nodeSet = undefined; - const that = this; - this._onDidChange.fire({ get elements() { return that.get(); }, browserEvent }); + if (!silent) { + const that = this; + this._onDidChange.fire({ get elements() { return that.get(); }, browserEvent }); + } } get(): T[] { @@ -827,6 +833,7 @@ class Trait { insertedNodes.forEach(node => dfs(node, insertedNodesVisitor)); const nodes: ITreeNode[] = []; + let silent = true; for (const node of this.nodes) { const id = this.identityProvider.getId(node.element).toString(); @@ -839,11 +846,13 @@ class Trait { if (insertedNode) { nodes.push(insertedNode); + } else { + silent = false; } } } - this.set(nodes); + this._set(nodes, silent); } private createNodeSet(): Set> { diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 2e4fbf152d0..067704d7c21 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -29,7 +29,7 @@ export interface IActionRunner extends IDisposable { onDidBeforeRun: Event; } -export interface IActionItem { +export interface IActionViewItem { actionRunner: IActionRunner; setActionContext(context: any): void; render(element: any /* HTMLElement */): void; diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts index fc14fd47829..cf34296e74c 100644 --- a/src/vs/base/common/buffer.ts +++ b/src/vs/base/common/buffer.ts @@ -11,7 +11,7 @@ let textDecoder: TextDecoder | null; export class VSBuffer { - public static alloc(byteLength: number): VSBuffer { + static alloc(byteLength: number): VSBuffer { if (hasBuffer) { return new VSBuffer(Buffer.allocUnsafe(byteLength)); } else { @@ -19,7 +19,7 @@ export class VSBuffer { } } - public static wrap(actual: Uint8Array): VSBuffer { + static wrap(actual: Uint8Array): VSBuffer { if (hasBuffer && !(Buffer.isBuffer(actual))) { // https://nodejs.org/dist/latest-v10.x/docs/api/buffer.html#buffer_class_method_buffer_from_arraybuffer_byteoffset_length // Create a zero-copy Buffer wrapper around the ArrayBuffer pointed to by the Uint8Array @@ -28,7 +28,7 @@ export class VSBuffer { return new VSBuffer(actual); } - public static fromString(source: string): VSBuffer { + static fromString(source: string): VSBuffer { if (hasBuffer) { return new VSBuffer(Buffer.from(source)); } else { @@ -39,7 +39,7 @@ export class VSBuffer { } } - public static concat(buffers: VSBuffer[], totalLength?: number): VSBuffer { + static concat(buffers: VSBuffer[], totalLength?: number): VSBuffer { if (typeof totalLength === 'undefined') { totalLength = 0; for (let i = 0, len = buffers.length; i < len; i++) { @@ -58,15 +58,15 @@ export class VSBuffer { return ret; } - public readonly buffer: Uint8Array; - public readonly byteLength: number; + readonly buffer: Uint8Array; + readonly byteLength: number; private constructor(buffer: Uint8Array) { this.buffer = buffer; this.byteLength = this.buffer.byteLength; } - public toString(): string { + toString(): string { if (hasBuffer) { return this.buffer.toString(); } else { @@ -77,33 +77,32 @@ export class VSBuffer { } } - public slice(start?: number, end?: number): VSBuffer { + slice(start?: number, end?: number): VSBuffer { return new VSBuffer(this.buffer.slice(start, end)); } - public set(array: VSBuffer, offset?: number): void { + set(array: VSBuffer, offset?: number): void { this.buffer.set(array.buffer, offset); } - public readUint32BE(offset: number): number { - return readUint32BE(this.buffer, offset); + readUInt32BE(offset: number): number { + return readUInt32BE(this.buffer, offset); } - public writeUint32BE(value: number, offset: number): void { - writeUint32BE(this.buffer, value, offset); + writeUInt32BE(value: number, offset: number): void { + writeUInt32BE(this.buffer, value, offset); } - public readUint8(offset: number): number { - return readUint8(this.buffer, offset); + readUInt8(offset: number): number { + return readUInt8(this.buffer, offset); } - public writeUint8(value: number, offset: number): void { - writeUint8(this.buffer, value, offset); + writeUInt8(value: number, offset: number): void { + writeUInt8(this.buffer, value, offset); } - } -function readUint32BE(source: Uint8Array, offset: number): number { +function readUInt32BE(source: Uint8Array, offset: number): number { return ( source[offset] * 2 ** 24 + source[offset + 1] * 2 ** 16 @@ -112,7 +111,7 @@ function readUint32BE(source: Uint8Array, offset: number): number { ); } -function writeUint32BE(destination: Uint8Array, value: number, offset: number): void { +function writeUInt32BE(destination: Uint8Array, value: number, offset: number): void { destination[offset + 3] = value; value = value >>> 8; destination[offset + 2] = value; @@ -122,11 +121,11 @@ function writeUint32BE(destination: Uint8Array, value: number, offset: number): destination[offset] = value; } -function readUint8(source: Uint8Array, offset: number): number { +function readUInt8(source: Uint8Array, offset: number): number { return source[offset]; } -function writeUint8(destination: Uint8Array, value: number, offset: number): void { +function writeUInt8(destination: Uint8Array, value: number, offset: number): void { destination[offset] = value; } @@ -139,6 +138,47 @@ export interface VSBufferReadable { read(): VSBuffer | null; } +/** + * A buffer readable stream emits data to listeners. The stream + * will only start emitting when the first data listener has + * been added or the resume() method has been called. + */ +export interface VSBufferReadableStream { + + /** + * The 'data' event is emitted whenever the stream is + * relinquishing ownership of a chunk of data to a consumer. + */ + on(event: 'data', callback: (chunk: VSBuffer) => void): void; + + /** + * Emitted when any error occurs. + */ + on(event: 'error', callback: (err: any) => void): void; + + /** + * The 'end' event is emitted when there is no more data + * to be consumed from the stream. The 'end' event will + * not be emitted unless the data is completely consumed. + */ + on(event: 'end', callback: () => void): void; + + /** + * Stops emitting any events until resume() is called. + */ + pause(): void; + + /** + * Starts emitting events again after pause() was called. + */ + resume(): void; + + /** + * Destroys the stream and stops emitting any event. + */ + destroy(): void; +} + /** * Helper to fully read a VSBuffer readable into a single buffer. */ @@ -158,6 +198,7 @@ export function readableToBuffer(readable: VSBufferReadable): VSBuffer { */ export function bufferToReadable(buffer: VSBuffer): VSBufferReadable { let done = false; + return { read: () => { if (done) { @@ -169,4 +210,231 @@ export function bufferToReadable(buffer: VSBuffer): VSBufferReadable { return buffer; } }; +} + +/** + * Helper to fully read a VSBuffer stream into a single buffer. + */ +export function streamToBuffer(stream: VSBufferReadableStream): Promise { + return new Promise((resolve, reject) => { + const chunks: VSBuffer[] = []; + + stream.on('data', chunk => chunks.push(chunk)); + stream.on('error', error => reject(error)); + stream.on('end', () => resolve(VSBuffer.concat(chunks))); + }); +} + +/** + * Helper to create a VSBufferStream from an existing VSBuffer. + */ +export function bufferToStream(buffer: VSBuffer): VSBufferReadableStream { + const stream = writeableBufferStream(); + + stream.end(buffer); + + return stream; +} + +/** + * Helper to create a VSBufferStream that can be pushed + * buffers to. Will only start to emit data when a listener + * is added. + */ +export function writeableBufferStream(): VSBufferWriteableStream { + return new VSBufferWriteableStreamImpl(); +} + +export interface VSBufferWriteableStream extends VSBufferReadableStream { + write(chunk: VSBuffer): void; + error(error: Error): void; + end(result?: VSBuffer | Error): void; +} + +class VSBufferWriteableStreamImpl implements VSBufferWriteableStream { + + private readonly state = { + flowing: false, + ended: false, + destroyed: false + }; + + private readonly buffer = { + data: [] as VSBuffer[], + error: [] as Error[] + }; + + private readonly listeners = { + data: [] as { (chunk: VSBuffer): void }[], + error: [] as { (error: Error): void }[], + end: [] as { (): void }[] + }; + + pause(): void { + if (this.state.destroyed) { + return; + } + + this.state.flowing = false; + } + + resume(): void { + if (this.state.destroyed) { + return; + } + + if (!this.state.flowing) { + this.state.flowing = true; + + // emit buffered events + this.flowData(); + this.flowErrors(); + this.flowEnd(); + } + } + + write(chunk: VSBuffer): void { + if (this.state.destroyed) { + return; + } + + // flowing: directly send the data to listeners + if (this.state.flowing) { + this.listeners.data.forEach(listener => listener(chunk)); + } + + // not yet flowing: buffer data until flowing + else { + this.buffer.data.push(chunk); + } + } + + error(error: Error): void { + if (this.state.destroyed) { + return; + } + + // flowing: directly send the error to listeners + if (this.state.flowing) { + this.listeners.error.forEach(listener => listener(error)); + } + + // not yet flowing: buffer errors until flowing + else { + this.buffer.error.push(error); + } + } + + end(result?: VSBuffer | Error): void { + if (this.state.destroyed) { + return; + } + + // end with data or error if provided + if (result instanceof Error) { + this.error(result); + } else if (result) { + this.write(result); + } + + // flowing: send end event to listeners + if (this.state.flowing) { + this.listeners.end.forEach(listener => listener()); + + this.destroy(); + } + + // not yet flowing: remember state + else { + this.state.ended = true; + } + } + + on(event: 'data', callback: (chunk: VSBuffer) => void): void; + on(event: 'error', callback: (err: any) => void): void; + on(event: 'end', callback: () => void): void; + on(event: 'data' | 'error' | 'end', callback: (arg0?: any) => void): void { + if (this.state.destroyed) { + return; + } + + switch (event) { + case 'data': + this.listeners.data.push(callback); + + // switch into flowing mode as soon as the first 'data' + // listener is added and we are not yet in flowing mode + this.resume(); + + break; + + case 'end': + this.listeners.end.push(callback); + + // emit 'end' event directly if we are flowing + // and the end has already been reached + // + // finish() when it went through + if (this.state.flowing && this.flowEnd()) { + this.destroy(); + } + + break; + + case 'error': + this.listeners.error.push(callback); + + // emit buffered 'error' events unless done already + // now that we know that we have at least one listener + if (this.state.flowing) { + this.flowErrors(); + } + + break; + } + } + + private flowData(): void { + if (this.buffer.data.length > 0) { + const fullDataBuffer = VSBuffer.concat(this.buffer.data); + + this.listeners.data.forEach(listener => listener(fullDataBuffer)); + + this.buffer.data.length = 0; + } + } + + private flowErrors(): void { + if (this.listeners.error.length > 0) { + for (const error of this.buffer.error) { + this.listeners.error.forEach(listener => listener(error)); + } + + this.buffer.error.length = 0; + } + } + + private flowEnd(): boolean { + if (this.state.ended) { + this.listeners.end.forEach(listener => listener()); + + return this.listeners.end.length > 0; + } + + return false; + } + + destroy(): void { + if (!this.state.destroyed) { + this.state.destroyed = true; + this.state.ended = true; + + this.buffer.data.length = 0; + this.buffer.error.length = 0; + + this.listeners.data.length = 0; + this.listeners.error.length = 0; + this.listeners.end.length = 0; + } + } } \ No newline at end of file diff --git a/src/vs/base/common/console.ts b/src/vs/base/common/console.ts index 2ac39e3d707..ccd30d07a1e 100644 --- a/src/vs/base/common/console.ts +++ b/src/vs/base/common/console.ts @@ -79,7 +79,7 @@ export function getFirstFrame(arg0: IRemoteConsoleLog | string | undefined): ISt uri: URI.file(matches[1]), line: Number(matches[2]), column: Number(matches[3]) - } as IStackFrame; + }; } } diff --git a/src/vs/base/common/extpath.ts b/src/vs/base/common/extpath.ts index 8d664f3a650..8f47ac2cc5e 100644 --- a/src/vs/base/common/extpath.ts +++ b/src/vs/base/common/extpath.ts @@ -139,19 +139,22 @@ export function isUNC(path: string): boolean { } // Reference: https://en.wikipedia.org/wiki/Filename -const INVALID_FILE_CHARS = isWindows ? /[\\/:\*\?"<>\|]/g : /[\\/]/g; +const WINDOWS_INVALID_FILE_CHARS = /[\\/:\*\?"<>\|]/g; +const UNIX_INVALID_FILE_CHARS = /[\\/]/g; const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])$/i; -export function isValidBasename(name: string | null | undefined): boolean { +export function isValidBasename(name: string | null | undefined, isWindowsOS: boolean = isWindows): boolean { + const invalidFileChars = isWindowsOS ? WINDOWS_INVALID_FILE_CHARS : UNIX_INVALID_FILE_CHARS; + if (!name || name.length === 0 || /^\s+$/.test(name)) { return false; // require a name that is not just whitespace } - INVALID_FILE_CHARS.lastIndex = 0; // the holy grail of software development - if (INVALID_FILE_CHARS.test(name)) { + invalidFileChars.lastIndex = 0; // the holy grail of software development + if (invalidFileChars.test(name)) { return false; // check for certain invalid file characters } - if (isWindows && WINDOWS_FORBIDDEN_NAMES.test(name)) { + if (isWindowsOS && WINDOWS_FORBIDDEN_NAMES.test(name)) { return false; // check for certain invalid file names } @@ -159,16 +162,16 @@ export function isValidBasename(name: string | null | undefined): boolean { return false; // check for reserved values } - if (isWindows && name[name.length - 1] === '.') { + if (isWindowsOS && name[name.length - 1] === '.') { return false; // Windows: file cannot end with a "." } - if (isWindows && name.length !== name.trim().length) { + if (isWindowsOS && name.length !== name.trim().length) { return false; // Windows: file cannot end with a whitespace } if (name.length > 255) { - return false; // most file systems do not allow files > 255 lenth + return false; // most file systems do not allow files > 255 length } return true; diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index 76bb974d567..ea24c82b02c 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -362,25 +362,6 @@ export function matchesFuzzy2(pattern: string, word: string): IMatch[] | null { return score ? createMatches(score) : null; } -export function anyScore(pattern: string, lowPattern: string, _patternPos: number, word: string, lowWord: string, _wordPos: number): FuzzyScore { - const result = fuzzyScore(pattern, lowPattern, 0, word, lowWord, 0, true); - if (result) { - return result; - } - let matches = 0; - let score = 0; - let idx = _wordPos; - for (let patternPos = 0; patternPos < lowPattern.length && patternPos < _maxLen; ++patternPos) { - const wordPos = lowWord.indexOf(lowPattern.charAt(patternPos), idx); - if (wordPos >= 0) { - score += 1; - matches += 2 ** wordPos; - idx = wordPos + 1; - } - } - return [score, matches, _wordPos]; -} - //#region --- fuzzyScore --- export function createMatches(score: undefined | FuzzyScore): IMatch[] { diff --git a/src/vs/base/common/marked/cgmanifest.json b/src/vs/base/common/marked/cgmanifest.json index b0c1ce8f3d5..f3477931d13 100644 --- a/src/vs/base/common/marked/cgmanifest.json +++ b/src/vs/base/common/marked/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "marked", "repositoryUrl": "https://github.com/markedjs/marked", - "commitHash": "78c977bc3a47f9e2fb146477d1ca3dad0cb134e6" + "commitHash": "529a8d4e185a8aa561e4d8d2891f8556b5717cd4" } }, "license": "MIT", - "version": "0.5.0" + "version": "0.6.2" } ], "version": 1 diff --git a/src/vs/base/common/marked/marked.js b/src/vs/base/common/marked/marked.js index bde18dff976..1288f459647 100644 --- a/src/vs/base/common/marked/marked.js +++ b/src/vs/base/common/marked/marked.js @@ -23,7 +23,7 @@ var block = { heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/, nptable: noop, blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, - list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, + list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, html: '^ {0,3}(?:' // optional indentation + '<(script|pre|style)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)' // (1) + '|comment[^\\n]*(\\n+|$)' // (2) @@ -31,8 +31,8 @@ var block = { + '|\\n*' // (4) + '|\\n*' // (5) + '|)[\\s\\S]*?(?:\\n{2,}|$)' // (6) - + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag - + '|(?=\\h*\\n)[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag + + '|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)' // (7) open tag + + '|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)' // (7) closing tag + ')', def: /^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, table: noop, @@ -48,8 +48,8 @@ block.def = edit(block.def) .replace('title', block._title) .getRegex(); -block.bullet = /(?:[*+-]|\d+\.)/; -block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; +block.bullet = /(?:[*+-]|\d{1,9}\.)/; +block.item = /^( *)(bull) ?[^\n]*(?:\n(?!\1bull ?)[^\n]*)*/; block.item = edit(block.item, 'gm') .replace(/bull/g, block.bullet) .getRegex(); @@ -95,7 +95,7 @@ block.normal = merge({}, block); */ block.gfm = merge({}, block.normal, { - fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/, + fences: /^ {0,3}(`{3,}|~{3,})([^`\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/, paragraph: /^/, heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ }); @@ -235,7 +235,7 @@ Lexer.prototype.token = function(src, top) { src = src.substring(cap[0].length); this.tokens.push({ type: 'code', - lang: cap[2], + lang: cap[2] ? cap[2].trim() : cap[2], text: cap[3] || '' }); continue; @@ -253,7 +253,7 @@ Lexer.prototype.token = function(src, top) { } // table no leading pipe (gfm) - if (top && (cap = this.rules.nptable.exec(src))) { + if (cap = this.rules.nptable.exec(src)) { item = { type: 'table', header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), @@ -346,7 +346,7 @@ Lexer.prototype.token = function(src, top) { // Remove the list item's bullet // so it is seen as the next token. space = item.length; - item = item.replace(/^ *([*+-]|\d+\.) +/, ''); + item = item.replace(/^ *([*+-]|\d+\.) */, ''); // Outdent whatever the // list item contains. Hacky. @@ -359,9 +359,10 @@ Lexer.prototype.token = function(src, top) { // Determine whether the next list item belongs here. // Backpedal if it does not belong in this list. - if (this.options.smartLists && i !== l - 1) { + if (i !== l - 1) { b = block.bullet.exec(cap[i + 1])[0]; - if (bull !== b && !(bull.length > 1 && b.length > 1)) { + if (bull.length > 1 ? b.length === 1 + : (b.length > 1 || (this.options.smartLists && b !== bull))) { src = cap.slice(i + 1).join('\n') + src; i = l - 1; } @@ -450,12 +451,12 @@ Lexer.prototype.token = function(src, top) { } // table (gfm) - if (top && (cap = this.rules.table.exec(src))) { + if (cap = this.rules.table.exec(src)) { item = { type: 'table', header: splitCells(cap[1].replace(/^ *| *\| *$/g, '')), align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), - cells: cap[3] ? cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') : [] + cells: cap[3] ? cap[3].replace(/\n$/, '').split('\n') : [] }; if (item.header.length === item.align.length) { @@ -544,14 +545,19 @@ var inline = { link: /^!?\[(label)\]\(href(?:\s+(title))?\s*\)/, reflink: /^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/, nolink: /^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/, - strong: /^__([^\s])__(?!_)|^\*\*([^\s])\*\*(?!\*)|^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)/, - em: /^_([^\s_])_(?!_)|^\*([^\s*"<\[])\*(?!\*)|^_([^\s][\s\S]*?[^\s_])_(?!_)|^_([^\s_][\s\S]*?[^\s])_(?!_)|^\*([^\s"<\[][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*"<\[][\s\S]*?[^\s])\*(?!\*)/, - code: /^(`+)\s*([\s\S]*?[^`]?)\s*\1(?!`)/, + strong: /^__([^\s_])__(?!_)|^\*\*([^\s*])\*\*(?!\*)|^__([^\s][\s\S]*?[^\s])__(?!_)|^\*\*([^\s][\s\S]*?[^\s])\*\*(?!\*)/, + em: /^_([^\s_])_(?!_)|^\*([^\s*"<\[])\*(?!\*)|^_([^\s][\s\S]*?[^\s_])_(?!_|[^\spunctuation])|^_([^\s_][\s\S]*?[^\s])_(?!_|[^\spunctuation])|^\*([^\s"<\[][\s\S]*?[^\s*])\*(?!\*)|^\*([^\s*"<\[][\s\S]*?[^\s])\*(?!\*)/, + code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/, br: /^( {2,}|\\)\n(?!\s*$)/, del: noop, - text: /^[\s\S]+?(?=[\\?@\\[^_{|}~'; +inline.em = edit(inline.em).replace(/punctuation/g, inline._punctuation).getRegex(); + inline._escapes = /\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g; inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; @@ -568,8 +574,8 @@ inline.tag = edit(inline.tag) .replace('attribute', inline._attribute) .getRegex(); -inline._label = /(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?/; -inline._href = /\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f\\]*\)|[^\s\x00-\x1f()\\])*?)/; +inline._label = /(?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|`(?!`)|[^\[\]\\`])*?/; +inline._href = /\s*(<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*)/; inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; inline.link = edit(inline.link) @@ -609,24 +615,23 @@ inline.pedantic = merge({}, inline.normal, { inline.gfm = merge({}, inline.normal, { escape: edit(inline.escape).replace('])', '~|])').getRegex(), - url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/) - .replace('email', inline._email) - .getRegex(), + _extended_email: /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/, + url: /^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, del: /^~+(?=\S)([\s\S]*?\S)~+/, - text: edit(inline.text) - .replace(']|', '~]|') - .replace('|', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|') - .getRegex() + text: /^(`+|[^`])(?:[\s\S]*?(?:(?=[\\/i.test(cap[0])) { this.inLink = false; } + if (!this.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { + this.inRawBlock = true; + } else if (this.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) { + this.inRawBlock = false; + } + src = src.substring(cap[0].length); out += this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0]) - : cap[0] + : cap[0]; continue; } // link if (cap = this.rules.link.exec(src)) { + var lastParenIndex = findClosingBracket(cap[2], '()'); + if (lastParenIndex > -1) { + var linkLen = cap[0].length - (cap[2].length - lastParenIndex) - (cap[3] || '').length; + cap[2] = cap[2].substring(0, lastParenIndex); + cap[0] = cap[0].substring(0, linkLen).trim(); + cap[3] = ''; + } src = src.substring(cap[0].length); this.inLink = true; href = cap[2]; @@ -821,10 +803,51 @@ InlineLexer.prototype.output = function(src) { continue; } + // autolink + if (cap = this.rules.autolink.exec(src)) { + src = src.substring(cap[0].length); + if (cap[2] === '@') { + text = escape(this.mangle(cap[1])); + href = 'mailto:' + text; + } else { + text = escape(cap[1]); + href = text; + } + out += this.renderer.link(href, null, text); + continue; + } + + // url (gfm) + if (!this.inLink && (cap = this.rules.url.exec(src))) { + if (cap[2] === '@') { + text = escape(cap[0]); + href = 'mailto:' + text; + } else { + // do extended autolink path validation + do { + prevCapZero = cap[0]; + cap[0] = this.rules._backpedal.exec(cap[0])[0]; + } while (prevCapZero !== cap[0]); + text = escape(cap[0]); + if (cap[1] === 'www.') { + href = 'http://' + text; + } else { + href = text; + } + } + src = src.substring(cap[0].length); + out += this.renderer.link(href, null, text); + continue; + } + // text if (cap = this.rules.text.exec(src)) { src = src.substring(cap[0].length); - out += this.renderer.text(escape(this.smartypants(cap[0]))); + if (this.inRawBlock) { + out += this.renderer.text(cap[0]); + } else { + out += this.renderer.text(escape(this.smartypants(cap[0]))); + } continue; } @@ -838,7 +861,7 @@ InlineLexer.prototype.output = function(src) { InlineLexer.escapes = function(text) { return text ? text.replace(InlineLexer.rules._escapes, '$1') : text; -} +}; /** * Compile Link @@ -906,7 +929,8 @@ function Renderer(options) { this.options = options || marked.defaults; } -Renderer.prototype.code = function(code, lang, escaped) { +Renderer.prototype.code = function(code, infostring, escaped) { + var lang = (infostring || '').match(/\S*/)[0]; if (this.options.highlight) { var out = this.options.highlight(code, lang); if (out != null && out !== code) { @@ -937,13 +961,13 @@ Renderer.prototype.html = function(html) { return html; }; -Renderer.prototype.heading = function(text, level, raw) { +Renderer.prototype.heading = function(text, level, raw, slugger) { if (this.options.headerIds) { return '' + text + ' '; -} +}; Renderer.prototype.paragraph = function(text) { return '

' + text + '

\n'; @@ -1025,24 +1049,8 @@ Renderer.prototype.del = function(text) { }; Renderer.prototype.link = function(href, title, text) { - if (this.options.sanitize) { - try { - var prot = decodeURIComponent(unescape(href)) - .replace(/[^\w:]/g, '') - .toLowerCase(); - } catch (e) { - return text; - } - if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { - return text; - } - } - if (this.options.baseUrl && !originIndependentUrl.test(href)) { - href = resolveUrl(this.options.baseUrl, href); - } - try { - href = encodeURI(href).replace(/%25/g, '%'); - } catch (e) { + href = cleanUrl(this.options.sanitize, this.options.baseUrl, href); + if (href === null) { return text; } var out = '?@[\]^`{|}~]/g, '') + .replace(/\s/g, '-'); + + if (this.seen.hasOwnProperty(slug)) { + var originalSlug = slug; + do { + this.seen[originalSlug]++; + slug = originalSlug + '-' + this.seen[originalSlug]; + } while (this.seen.hasOwnProperty(slug)); + } + this.seen[slug] = 0; + + return slug; +}; + /** * Helpers */ function escape(html, encode) { - return html - .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); + if (encode) { + if (escape.escapeTest.test(html)) { + return html.replace(escape.escapeReplace, function (ch) { return escape.replacements[ch]; }); + } + } else { + if (escape.escapeTestNoEncode.test(html)) { + return html.replace(escape.escapeReplaceNoEncode, function (ch) { return escape.replacements[ch]; }); + } + } + + return html; } +escape.escapeTest = /[&<>"']/; +escape.escapeReplace = /[&<>"']/g; +escape.replacements = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' +}; + +escape.escapeTestNoEncode = /[<>"']|&(?!#?\w+;)/; +escape.escapeReplaceNoEncode = /[<>"']|&(?!#?\w+;)/g; + function unescape(html) { // explicitly match decimal, hex, and named HTML entities return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) { @@ -1316,6 +1386,30 @@ function edit(regex, opt) { }; } +function cleanUrl(sanitize, base, href) { + if (sanitize) { + try { + var prot = decodeURIComponent(unescape(href)) + .replace(/[^\w:]/g, '') + .toLowerCase(); + } catch (e) { + return null; + } + if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0 || prot.indexOf('data:') === 0) { + return null; + } + } + if (base && !originIndependentUrl.test(href)) { + href = resolveUrl(base, href); + } + try { + href = encodeURI(href).replace(/%25/g, '%'); + } catch (e) { + return null; + } + return href; +} + function resolveUrl(base, href) { if (!baseUrls[' ' + base]) { // we can ignore everything in base after the last slash of its path component, @@ -1418,6 +1512,26 @@ function rtrim(str, c, invert) { return str.substr(0, str.length - suffLen); } +function findClosingBracket(str, b) { + if (str.indexOf(b[1]) === -1) { + return -1; + } + var level = 0; + for (var i = 0; i < str.length; i++) { + if (str[i] === '\\') { + i++; + } else if (str[i] === b[0]) { + level++; + } else if (str[i] === b[1]) { + level--; + if (level < 0) { + return i; + } + } + } + return -1; +} + /** * Marked */ @@ -1446,7 +1560,7 @@ function marked(src, opt, callback) { i = 0; try { - tokens = Lexer.lex(src, opt) + tokens = Lexer.lex(src, opt); } catch (e) { return callback(e); } @@ -1545,7 +1659,7 @@ marked.getDefaults = function () { tables: true, xhtml: false }; -} +}; marked.defaults = marked.getDefaults(); @@ -1565,6 +1679,8 @@ marked.lexer = Lexer.lex; marked.InlineLexer = InlineLexer; marked.inlineLexer = InlineLexer.output; +marked.Slugger = Slugger; + marked.parse = marked; // BEGIN MONACOCHANGE @@ -1582,7 +1698,7 @@ __marked_exports = marked; // ESM-comment-begin define(function() { return __marked_exports; }); // ESM-comment-end - + // ESM-uncomment-begin // export var marked = __marked_exports; // export var Parser = __marked_exports.Parser; diff --git a/src/vs/base/common/mime.ts b/src/vs/base/common/mime.ts index de19c7c03f8..70e70de33a1 100644 --- a/src/vs/base/common/mime.ts +++ b/src/vs/base/common/mime.ts @@ -197,7 +197,11 @@ function guessMimeTypeByFirstline(firstLine: string): string | null { } if (firstLine.length > 0) { - for (const association of registeredAssociations) { + + // We want to prioritize associations based on the order they are registered so that the last registered + // association wins over all other. This is for https://github.com/Microsoft/vscode/issues/20074 + for (let i = registeredAssociations.length - 1; i >= 0; i--) { + const association = registeredAssociations[i]; if (!association.firstline) { continue; } @@ -230,10 +234,11 @@ export function isUnspecific(mime: string[] | string): boolean { * 2. Otherwise, if there are other extensions, suggest the first one. * 3. Otherwise, suggest the prefix. */ -export function suggestFilename(langId: string | null, prefix: string): string { +export function suggestFilename(mode: string | undefined, prefix: string): string { const extensions = registeredAssociations - .filter(assoc => !assoc.userConfigured && assoc.extension && assoc.id === langId) + .filter(assoc => !assoc.userConfigured && assoc.extension && assoc.id === mode) .map(assoc => assoc.extension); + const extensionsWithDotFirst = coalesce(extensions) .filter(assoc => startsWith(assoc, '.')); diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index 661863eb27a..97c8f9196c0 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -14,7 +14,7 @@ export function deepClone(obj: T): T { return obj as any; } const result: any = Array.isArray(obj) ? [] : {}; - Object.keys(obj).forEach((key: string) => { + Object.keys(obj as any).forEach((key: string) => { if (obj[key] && typeof obj[key] === 'object') { result[key] = deepClone(obj[key]); } else { diff --git a/src/vs/base/common/parsers.ts b/src/vs/base/common/parsers.ts index 4ea4e95373d..748abd4fd47 100644 --- a/src/vs/base/common/parsers.ts +++ b/src/vs/base/common/parsers.ts @@ -79,7 +79,7 @@ export abstract class Parser { this._problemReporter.fatal(message); } - protected static merge(destination: T, source: T, overwrite: boolean): void { + protected static merge(destination: T, source: T, overwrite: boolean): void { Object.keys(source).forEach((key: string) => { const destValue = destination[key]; const sourceValue = source[key]; diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index c55d331661e..521a02a4b57 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -146,7 +146,7 @@ export function normalizePath(resource: URI): URI { export function originalFSPath(uri: URI): string { let value: string; const uriPath = uri.path; - if (uri.authority && uriPath.length > 1 && uri.scheme === 'file') { + if (uri.authority && uriPath.length > 1 && uri.scheme === Schemas.file) { // unc path: file://shares/c$/far/boo value = `//${uri.authority}${uriPath}`; } else if ( @@ -176,28 +176,46 @@ export function isAbsolutePath(resource: URI): boolean { /** * Returns true if the URI path has a trailing path separator */ -export function hasTrailingPathSeparator(resource: URI): boolean { +export function hasTrailingPathSeparator(resource: URI, sep: string = paths.sep): boolean { if (resource.scheme === Schemas.file) { const fsp = originalFSPath(resource); - return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === paths.sep; + return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === sep; } else { const p = resource.path; return p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash; // ignore the slash at offset 0 } } - /** - * Removes a trailing path seperator, if theres one. + * Removes a trailing path separator, if there's one. * Important: Doesn't remove the first slash, it would make the URI invalid */ -export function removeTrailingPathSeparator(resource: URI): URI { - if (hasTrailingPathSeparator(resource)) { +export function removeTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI { + if (hasTrailingPathSeparator(resource, sep)) { return resource.with({ path: resource.path.substr(0, resource.path.length - 1) }); } return resource; } +/** + * Adds a trailing path separator to the URI if there isn't one already. + * For example, c:\ would be unchanged, but c:\users would become c:\users\ + */ +export function addTrailingPathSeparator(resource: URI, sep: string = paths.sep): URI { + let isRootSep: boolean = false; + if (resource.scheme === Schemas.file) { + const fsp = originalFSPath(resource); + isRootSep = ((fsp !== undefined) && (fsp.length === extpath.getRoot(fsp).length) && (fsp[fsp.length - 1] === sep)); + } else { + sep = '/'; + const p = resource.path; + isRootSep = p.length === 1 && p.charCodeAt(p.length - 1) === CharCode.Slash; + } + if (!isRootSep && !hasTrailingPathSeparator(resource, sep)) { + return resource.with({ path: resource.path + '/' }); + } + return resource; +} /** * Returns a relative path between two URIs. If the URIs don't have the same schema or authority, `undefined` is returned. diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 37c57c26f1c..d4397d3279d 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -233,7 +233,7 @@ export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean { // We check against an empty string. If the regular expression doesn't advance // (e.g. ends in an endless loop) it will match an empty string. const match = regexp.exec(''); - return !!(match && regexp.lastIndex === 0); + return !!(match && regexp.lastIndex === 0); } export function regExpContainsBackreference(regexpValue: string): boolean { diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts index 9ef06471a15..9b04d9f0a96 100644 --- a/src/vs/base/node/encoding.ts +++ b/src/vs/base/node/encoding.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as stream from 'vs/base/node/stream'; import * as iconv from 'iconv-lite'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { exec } from 'child_process'; import { Readable, Writable } from 'stream'; +import { VSBuffer } from 'vs/base/common/buffer'; export const UTF8 = 'utf8'; export const UTF8_with_bom = 'utf8bom'; @@ -18,105 +18,117 @@ export const UTF16be_BOM = [0xFE, 0xFF]; export const UTF16le_BOM = [0xFF, 0xFE]; export const UTF8_BOM = [0xEF, 0xBB, 0xBF]; +const ZERO_BYTE_DETECTION_BUFFER_MAX_LEN = 512; // number of bytes to look at to decide about a file being binary or not +const NO_GUESS_BUFFER_MAX_LEN = 512; // when not auto guessing the encoding, small number of bytes are enough +const AUTO_GUESS_BUFFER_MAX_LEN = 512 * 8; // with auto guessing we want a lot more content to be read for guessing + export interface IDecodeStreamOptions { - guessEncoding?: boolean; + guessEncoding: boolean; minBytesRequiredForDetection?: number; - overwriteEncoding?(detectedEncoding: string | null): string; + + overwriteEncoding(detectedEncoding: string | null): string; } -export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): Promise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }> { +export interface IDecodeStreamResult { + stream: NodeJS.ReadableStream; + detected: IDetectedEncodingResult; +} + +export function toDecodeStream(readable: Readable, options: IDecodeStreamOptions): Promise { if (!options.minBytesRequiredForDetection) { options.minBytesRequiredForDetection = options.guessEncoding ? AUTO_GUESS_BUFFER_MAX_LEN : NO_GUESS_BUFFER_MAX_LEN; } - if (!options.overwriteEncoding) { - options.overwriteEncoding = detected => detected || UTF8; - } + return new Promise((resolve, reject) => { + const writer = new class extends Writable { + private decodeStream: NodeJS.ReadWriteStream; + private decodeStreamPromise: Promise; - return new Promise<{ detected: IDetectedEncodingResult, stream: NodeJS.ReadableStream }>((resolve, reject) => { + private bufferedChunks: Buffer[] = []; + private bytesBuffered = 0; - readable.on('error', reject); - - readable.pipe(new class extends Writable { - - private _decodeStream: NodeJS.ReadWriteStream; - private _decodeStreamConstruction: Promise; - private _buffer: Buffer[] = []; - private _bytesBuffered = 0; - - _write(chunk: any, encoding: string, callback: Function): void { + _write(chunk: Buffer, encoding: string, callback: (error: Error | null) => void): void { if (!Buffer.isBuffer(chunk)) { - callback(new Error('data must be a buffer')); + return callback(new Error('toDecodeStream(): data must be a buffer')); } - if (this._decodeStream) { - // just a forwarder now - this._decodeStream.write(chunk, callback); + // if the decode stream is ready, we just write directly + if (this.decodeStream) { + this.decodeStream.write(chunk, callback); + return; } - this._buffer.push(chunk); - this._bytesBuffered += chunk.length; + // otherwise we need to buffer the data until the stream is ready + this.bufferedChunks.push(chunk); + this.bytesBuffered += chunk.byteLength; - if (this._decodeStreamConstruction) { - // waiting for the decoder to be ready - this._decodeStreamConstruction.then(_ => callback(), err => callback(err)); + // waiting for the decoder to be ready + if (this.decodeStreamPromise) { + this.decodeStreamPromise.then(() => callback(null), error => callback(error)); + } - } else if (typeof options.minBytesRequiredForDetection === 'number' && this._bytesBuffered >= options.minBytesRequiredForDetection) { - // buffered enough data, create stream and forward data + // buffered enough data for encoding detection, create stream and forward data + else if (typeof options.minBytesRequiredForDetection === 'number' && this.bytesBuffered >= options.minBytesRequiredForDetection) { this._startDecodeStream(callback); + } - } else { - // only buffering - callback(); + // only buffering until enough data for encoding detection is there + else { + callback(null); } } - _startDecodeStream(callback: Function): void { + _startDecodeStream(callback: (error: Error | null) => void): void { - this._decodeStreamConstruction = Promise.resolve(detectEncodingFromBuffer({ - buffer: Buffer.concat(this._buffer), bytesRead: this._bytesBuffered + // detect encoding from buffer + this.decodeStreamPromise = Promise.resolve(detectEncodingFromBuffer({ + buffer: Buffer.concat(this.bufferedChunks), + bytesRead: this.bytesBuffered }, options.guessEncoding)).then(detected => { - if (options.overwriteEncoding) { - detected.encoding = options.overwriteEncoding(detected.encoding); - } - this._decodeStream = decodeStream(detected.encoding); - for (const buffer of this._buffer) { - this._decodeStream.write(buffer); - } - callback(); - resolve({ detected, stream: this._decodeStream }); - }, err => { - this.emit('error', err); - callback(err); + // ensure to respect overwrite of encoding + detected.encoding = options.overwriteEncoding(detected.encoding); + + // decode and write buffer + this.decodeStream = decodeStream(detected.encoding); + this.decodeStream.write(Buffer.concat(this.bufferedChunks), callback); + this.bufferedChunks.length = 0; + + // signal to the outside our detected encoding + // and final decoder stream + resolve({ detected, stream: this.decodeStream }); + }, error => { + this.emit('error', error); + + callback(error); }); } - _final(callback: (err?: any) => any) { - if (this._decodeStream) { - // normal finish - this._decodeStream.end(callback); - } else { - // we were still waiting for data... - this._startDecodeStream(() => this._decodeStream.end(callback)); + + _final(callback: (error: Error | null) => void) { + + // normal finish + if (this.decodeStream) { + this.decodeStream.end(callback); + } + + // we were still waiting for data to do the encoding + // detection. thus, wrap up starting the stream even + // without all the data to get things going + else { + this._startDecodeStream(() => this.decodeStream.end(callback)); } } - }); + }; + + // errors + readable.on('error', reject); + + // pipe through + readable.pipe(writer); }); } -export function bomLength(encoding: string): number { - switch (encoding) { - case UTF8: - return 3; - case UTF16be: - case UTF16le: - return 2; - } - - return 0; -} - export function decode(buffer: Buffer, encoding: string): string { return iconv.decode(buffer, toNodeEncoding(encoding)); } @@ -129,7 +141,7 @@ export function encodingExists(encoding: string): boolean { return iconv.encodingExists(toNodeEncoding(encoding)); } -export function decodeStream(encoding: string | null): NodeJS.ReadWriteStream { +function decodeStream(encoding: string | null): NodeJS.ReadWriteStream { return iconv.decodeStream(toNodeEncoding(encoding)); } @@ -145,8 +157,8 @@ function toNodeEncoding(enc: string | null): string { return enc; } -export function detectEncodingByBOMFromBuffer(buffer: Buffer | null, bytesRead: number): string | null { - if (!buffer || bytesRead < 2) { +export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null, bytesRead: number): string | null { + if (!buffer || bytesRead < UTF16be_BOM.length) { return null; } @@ -163,7 +175,7 @@ export function detectEncodingByBOMFromBuffer(buffer: Buffer | null, bytesRead: return UTF16le; } - if (bytesRead < 3) { + if (bytesRead < UTF8_BOM.length) { return null; } @@ -177,39 +189,31 @@ export function detectEncodingByBOMFromBuffer(buffer: Buffer | null, bytesRead: return null; } -/** - * Detects the Byte Order Mark in a given file. - * If no BOM is detected, null will be passed to callback. - */ -export function detectEncodingByBOM(file: string): Promise { - return stream.readExactlyByFile(file, 3).then(({ buffer, bytesRead }) => detectEncodingByBOMFromBuffer(buffer, bytesRead), error => null); -} - const MINIMUM_THRESHOLD = 0.2; const IGNORE_ENCODINGS = ['ascii', 'utf-8', 'utf-16', 'utf-32']; /** * Guesses the encoding from buffer. */ -export function guessEncodingByBuffer(buffer: Buffer): Promise { - return import('jschardet').then(jschardet => { - jschardet.Constants.MINIMUM_THRESHOLD = MINIMUM_THRESHOLD; +async function guessEncodingByBuffer(buffer: Buffer): Promise { + const jschardet = await import('jschardet'); - const guessed = jschardet.detect(buffer); - if (!guessed || !guessed.encoding) { - return null; - } + jschardet.Constants.MINIMUM_THRESHOLD = MINIMUM_THRESHOLD; - const enc = guessed.encoding.toLowerCase(); + const guessed = jschardet.detect(buffer); + if (!guessed || !guessed.encoding) { + return null; + } - // Ignore encodings that cannot guess correctly - // (http://chardet.readthedocs.io/en/latest/supported-encodings.html) - if (0 <= IGNORE_ENCODINGS.indexOf(enc)) { - return null; - } + const enc = guessed.encoding.toLowerCase(); - return toIconvLiteEncoding(guessed.encoding); - }); + // Ignore encodings that cannot guess correctly + // (http://chardet.readthedocs.io/en/latest/supported-encodings.html) + if (0 <= IGNORE_ENCODINGS.indexOf(enc)) { + return null; + } + + return toIconvLiteEncoding(guessed.encoding); } const JSCHARDET_TO_ICONV_ENCODINGS: { [name: string]: string } = { @@ -261,18 +265,19 @@ export function toCanonicalName(enc: string): string { } } -const ZERO_BYTE_DETECTION_BUFFER_MAX_LEN = 512; // number of bytes to look at to decide about a file being binary or not -const NO_GUESS_BUFFER_MAX_LEN = 512; // when not auto guessing the encoding, small number of bytes are enough -const AUTO_GUESS_BUFFER_MAX_LEN = 512 * 8; // with auto guessing we want a lot more content to be read for guessing - export interface IDetectedEncodingResult { encoding: string | null; seemsBinary: boolean; } -export function detectEncodingFromBuffer(readResult: stream.ReadResult, autoGuessEncoding?: false): IDetectedEncodingResult; -export function detectEncodingFromBuffer(readResult: stream.ReadResult, autoGuessEncoding?: boolean): Promise; -export function detectEncodingFromBuffer({ buffer, bytesRead }: stream.ReadResult, autoGuessEncoding?: boolean): Promise | IDetectedEncodingResult { +export interface IReadResult { + buffer: Buffer | null; + bytesRead: number; +} + +export function detectEncodingFromBuffer(readResult: IReadResult, autoGuessEncoding?: false): IDetectedEncodingResult; +export function detectEncodingFromBuffer(readResult: IReadResult, autoGuessEncoding?: boolean): Promise; +export function detectEncodingFromBuffer({ buffer, bytesRead }: IReadResult, autoGuessEncoding?: boolean): Promise | IDetectedEncodingResult { // Always first check for BOM to find out about encoding let encoding = detectEncodingByBOMFromBuffer(buffer, bytesRead); @@ -357,7 +362,7 @@ const windowsTerminalEncodings = { '1252': 'cp1252' // West European Latin }; -export function resolveTerminalEncoding(verbose?: boolean): Promise { +export async function resolveTerminalEncoding(verbose?: boolean): Promise { let rawEncodingPromise: Promise; // Support a global environment variable to win over other mechanics @@ -403,24 +408,23 @@ export function resolveTerminalEncoding(verbose?: boolean): Promise { }); } - return rawEncodingPromise.then(rawEncoding => { - if (verbose) { - console.log(`Detected raw terminal encoding: ${rawEncoding}`); - } - - if (!rawEncoding || rawEncoding.toLowerCase() === 'utf-8' || rawEncoding.toLowerCase() === UTF8) { - return UTF8; - } - - const iconvEncoding = toIconvLiteEncoding(rawEncoding); - if (iconv.encodingExists(iconvEncoding)) { - return iconvEncoding; - } - - if (verbose) { - console.log('Unsupported terminal encoding, falling back to UTF-8.'); - } + const rawEncoding = await rawEncodingPromise; + if (verbose) { + console.log(`Detected raw terminal encoding: ${rawEncoding}`); + } + if (!rawEncoding || rawEncoding.toLowerCase() === 'utf-8' || rawEncoding.toLowerCase() === UTF8) { return UTF8; - }); + } + + const iconvEncoding = toIconvLiteEncoding(rawEncoding); + if (iconv.encodingExists(iconvEncoding)) { + return iconvEncoding; + } + + if (verbose) { + console.log('Unsupported terminal encoding, falling back to UTF-8.'); + } + + return UTF8; } diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 37693ce1042..6e6783f9f1f 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -670,4 +670,15 @@ export async function mkdirp(path: string, mode?: number, token?: CancellationTo // Any other error return Promise.reject(error); } -} \ No newline at end of file +} + +// See https://github.com/Microsoft/vscode/issues/30180 +const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB +const GENERAL_MAX_FILE_SIZE = 16 * 1024 * 1024 * 1024; // 16 GB + +// See https://github.com/v8/v8/blob/5918a23a3d571b9625e5cce246bdd5b46ff7cd8b/src/heap/heap.cc#L149 +const WIN32_MAX_HEAP_SIZE = 700 * 1024 * 1024; // 700 MB +const GENERAL_MAX_HEAP_SIZE = 700 * 2 * 1024 * 1024; // 1400 MB + +export const MAX_FILE_SIZE = process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE; +export const MAX_HEAP_SIZE = process.arch === 'ia32' ? WIN32_MAX_HEAP_SIZE : GENERAL_MAX_HEAP_SIZE; \ No newline at end of file diff --git a/src/vs/base/node/request.ts b/src/vs/base/node/request.ts index ea24cdb7589..17731debc5b 100644 --- a/src/vs/base/node/request.ts +++ b/src/vs/base/node/request.ts @@ -159,7 +159,7 @@ export function asText(context: IRequestContext): Promise { }); } -export function asJson(context: IRequestContext): Promise { +export function asJson(context: IRequestContext): Promise { return new Promise((c, e) => { if (!isSuccess(context)) { return e('Server returned ' + context.res.statusCode); diff --git a/src/vs/base/node/stream.ts b/src/vs/base/node/stream.ts index 1c8d7e73ede..12f66a76556 100644 --- a/src/vs/base/node/stream.ts +++ b/src/vs/base/node/stream.ts @@ -5,63 +5,6 @@ import * as fs from 'fs'; -export interface ReadResult { - buffer: Buffer | null; - bytesRead: number; -} - -/** - * Reads totalBytes from the provided file. - */ -export function readExactlyByFile(file: string, totalBytes: number): Promise { - return new Promise((resolve, reject) => { - fs.open(file, 'r', null, (err, fd) => { - if (err) { - return reject(err); - } - - function end(err: Error | null, resultBuffer: Buffer | null, bytesRead: number): void { - fs.close(fd, closeError => { - if (closeError) { - return reject(closeError); - } - - if (err && (err).code === 'EISDIR') { - return reject(err); // we want to bubble this error up (file is actually a folder) - } - - return resolve({ buffer: resultBuffer, bytesRead }); - }); - } - - const buffer = Buffer.allocUnsafe(totalBytes); - let offset = 0; - - function readChunk(): void { - fs.read(fd, buffer, offset, totalBytes - offset, null, (err, bytesRead) => { - if (err) { - return end(err, null, 0); - } - - if (bytesRead === 0) { - return end(null, buffer, offset); - } - - offset += bytesRead; - - if (offset === totalBytes) { - return end(null, buffer, offset); - } - - return readChunk(); - }); - } - - readChunk(); - }); - }); -} - /** * Reads a file until a matching string is found. * diff --git a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts index fa579321906..f375750dc20 100644 --- a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts @@ -16,7 +16,12 @@ export function registerContextMenuListener(): void { y: options ? options.y : undefined, positioningItem: options ? options.positioningItem : undefined, callback: () => { - event.sender.send(CONTEXT_MENU_CLOSE_CHANNEL, contextMenuId); + // Workaround for https://github.com/Microsoft/vscode/issues/72447 + // It turns out that the menu gets GC'ed if not referenced anymore + // As such we drag it into this scope so that it is not being GC'ed + if (menu) { + event.sender.send(CONTEXT_MENU_CLOSE_CHANNEL, contextMenuId); + } } }); }); diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 5ef925cc692..f22f6c0aa73 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -126,7 +126,8 @@ const enum ProtocolMessageType { Regular = 1, Control = 2, Ack = 3, - KeepAlive = 4 + KeepAlive = 4, + Disconnect = 5 } export const enum ProtocolConstants { @@ -140,17 +141,17 @@ export const enum ProtocolConstants { */ AcknowledgeTimeoutTime = 10000, // 10 seconds /** - * Send at least a message every 30s for keep alive reasons. + * Send at least a message every 5s for keep alive reasons. */ - KeepAliveTime = 30000, // 30 seconds + KeepAliveTime = 5000, // 5 seconds /** - * If there is no message received for 60 seconds, consider the connection closed... + * If there is no message received for 10 seconds, consider the connection closed... */ - KeepAliveTimeoutTime = 60000, // 60 seconds + KeepAliveTimeoutTime = 10000, // 10 seconds /** * If there is no reconnection within this time-frame, consider the connection permanently closed... */ - ReconnectionGraceTime = 60 * 60 * 1000, // 1hr + ReconnectionGraceTime = 3 * 60 * 60 * 1000, // 3hrs } class ProtocolMessage { @@ -216,10 +217,10 @@ class ProtocolReader extends Disposable { // save new state => next time will read the body this._state.readHead = false; - this._state.readLen = buff.readUint32BE(9); - this._state.messageType = buff.readUint8(0); - this._state.id = buff.readUint32BE(1); - this._state.ack = buff.readUint32BE(5); + this._state.readLen = buff.readUInt32BE(9); + this._state.messageType = buff.readUInt8(0); + this._state.id = buff.readUInt32BE(1); + this._state.ack = buff.readUInt32BE(5); } else { // buff is the body const messageType = this._state.messageType; @@ -288,10 +289,10 @@ class ProtocolWriter { msg.writtenTime = Date.now(); this.lastWriteTime = Date.now(); const header = VSBuffer.alloc(ProtocolConstants.HeaderLength); - header.writeUint8(msg.type, 0); - header.writeUint32BE(msg.id, 1); - header.writeUint32BE(msg.ack, 5); - header.writeUint32BE(msg.data.byteLength, 9); + header.writeUInt8(msg.type, 0); + header.writeUInt32BE(msg.id, 1); + header.writeUInt32BE(msg.ack, 5); + header.writeUInt32BE(msg.data.byteLength, 9); this._writeSoon(header, msg.data); } @@ -373,6 +374,10 @@ export class Protocol extends Disposable implements IMessagePassingProtocol { return this._socket; } + sendDisconnect(): void { + // Nothing to do... + } + send(buffer: VSBuffer): void { this._socketWriter.write(new ProtocolMessage(ProtocolMessageType.Regular, 0, 0, buffer)); } @@ -393,6 +398,7 @@ export class Client extends IPCClient { dispose(): void { super.dispose(); const socket = this.protocol.getSocket(); + this.protocol.sendDisconnect(); this.protocol.dispose(); socket.end(); } @@ -572,7 +578,6 @@ export class PersistentProtocol { this._socketDisposables.push(this._socketReader); this._socketDisposables.push(this._socketReader.onMessage(msg => this._receiveMessage(msg))); this._socketDisposables.push(this._socket.onClose(() => this._onSocketClose.fire())); - this._socketDisposables.push(this._socket.onEnd(() => this._onClose.fire())); if (initialChunk) { this._socketReader.acceptChunk(initialChunk); } @@ -601,6 +606,12 @@ export class PersistentProtocol { this._socketDisposables = dispose(this._socketDisposables); } + sendDisconnect(): void { + const msg = new ProtocolMessage(ProtocolMessageType.Disconnect, 0, 0, getEmptyBuffer()); + this._socketWriter.write(msg); + this._socketWriter.flush(); + } + private _sendKeepAliveCheck(): void { if (this._outgoingKeepAliveTimeout) { // there will be a check in the near future @@ -659,7 +670,6 @@ export class PersistentProtocol { this._socketDisposables.push(this._socketReader); this._socketDisposables.push(this._socketReader.onMessage(msg => this._receiveMessage(msg))); this._socketDisposables.push(this._socket.onClose(() => this._onSocketClose.fire())); - this._socketDisposables.push(this._socket.onEnd(() => this._onClose.fire())); this._socketReader.acceptChunk(initialDataChunk); } @@ -677,6 +687,10 @@ export class PersistentProtocol { this._recvKeepAliveCheck(); } + public acceptDisconnect(): void { + this._onClose.fire(); + } + private _receiveMessage(msg: ProtocolMessage): void { if (msg.ack > this._outgoingAckId) { this._outgoingAckId = msg.ack; @@ -703,6 +717,8 @@ export class PersistentProtocol { } } else if (msg.type === ProtocolMessageType.Control) { this._onControlMessage.fire(msg.data); + } else if (msg.type === ProtocolMessageType.Disconnect) { + this._onClose.fire(); } } diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index b724c59011d..6a2ff55e2d1 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -166,17 +166,17 @@ enum DataType { function createSizeBuffer(size: number): VSBuffer { const result = VSBuffer.alloc(4); - result.writeUint32BE(size, 0); + result.writeUInt32BE(size, 0); return result; } function readSizeBuffer(reader: IReader): number { - return reader.read(4).readUint32BE(0); + return reader.read(4).readUInt32BE(0); } function createOneByteBuffer(value: number): VSBuffer { const result = VSBuffer.alloc(1); - result.writeUint8(value, 0); + result.writeUInt8(value, 0); return result; } @@ -225,7 +225,7 @@ function serialize(writer: IWriter, data: any): void { } function deserialize(reader: IReader): any { - const type = reader.read(1).readUint8(0); + const type = reader.read(1).readUInt8(0); switch (type) { case DataType.Undefined: return undefined; diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index 410e336c18f..957329d032b 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -136,10 +136,10 @@ suite('IPC, Socket Protocol', () => { assert.equal(msg1.toString(), 'foobarfarboo'); const buffer = VSBuffer.alloc(1); - buffer.writeUint8(123, 0); + buffer.writeUInt8(123, 0); a.send(buffer); const msg2 = await bMessages.waitForOne(); - assert.equal(msg2.readUint8(0), 123); + assert.equal(msg2.readUInt8(0), 123); }); diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts index 0347e161b09..165db403b5d 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts @@ -36,11 +36,11 @@ let IDS = 0; export class QuickOpenItemAccessorClass implements IItemAccessor { getItemLabel(entry: QuickOpenEntry): string | null { - return entry.getLabel(); + return types.withUndefinedAsNull(entry.getLabel()); } getItemDescription(entry: QuickOpenEntry): string | null { - return entry.getDescription(); + return types.withUndefinedAsNull(entry.getDescription()); } getItemPath(entry: QuickOpenEntry): string | undefined { @@ -75,15 +75,15 @@ export class QuickOpenEntry { /** * The label of the entry to identify it from others in the list */ - getLabel(): string | null { - return null; + getLabel(): string | undefined { + return undefined; } /** * The options for the label to use for this entry */ - getLabelOptions(): IIconLabelValueOptions | null { - return null; + getLabelOptions(): IIconLabelValueOptions | undefined { + return undefined; } /** @@ -97,51 +97,51 @@ export class QuickOpenEntry { /** * Detail information about the entry that is optional and can be shown below the label */ - getDetail(): string | null { - return null; + getDetail(): string | undefined { + return undefined; } /** * The icon of the entry to identify it from others in the list */ - getIcon(): string | null { - return null; + getIcon(): string | undefined { + return undefined; } /** * A secondary description that is optional and can be shown right to the label */ - getDescription(): string | null { - return null; + getDescription(): string | undefined { + return undefined; } /** * A tooltip to show when hovering over the entry. */ - getTooltip(): string | null { - return null; + getTooltip(): string | undefined { + return undefined; } /** * A tooltip to show when hovering over the description portion of the entry. */ - getDescriptionTooltip(): string | null { - return null; + getDescriptionTooltip(): string | undefined { + return undefined; } /** * An optional keybinding to show for an entry. */ - getKeybinding(): ResolvedKeybinding | null { - return null; + getKeybinding(): ResolvedKeybinding | undefined { + return undefined; } /** * A resource for this entry. Resource URIs can be used to compare different kinds of entries and group * them together. */ - getResource(): URI | null { - return null; + getResource(): URI | undefined { + return undefined; } /** @@ -229,11 +229,11 @@ export class QuickOpenEntryGroup extends QuickOpenEntry { this.withBorder = showBorder; } - getLabel(): string | null { + getLabel(): string | undefined { return this.entry ? this.entry.getLabel() : super.getLabel(); } - getLabelOptions(): IIconLabelValueOptions | null { + getLabelOptions(): IIconLabelValueOptions | undefined { return this.entry ? this.entry.getLabelOptions() : super.getLabelOptions(); } @@ -241,19 +241,19 @@ export class QuickOpenEntryGroup extends QuickOpenEntry { return this.entry ? this.entry.getAriaLabel() : super.getAriaLabel(); } - getDetail(): string | null { + getDetail(): string | undefined { return this.entry ? this.entry.getDetail() : super.getDetail(); } - getResource(): URI | null { + getResource(): URI | undefined { return this.entry ? this.entry.getResource() : super.getResource(); } - getIcon(): string | null { + getIcon(): string | undefined { return this.entry ? this.entry.getIcon() : super.getIcon(); } - getDescription(): string | null { + getDescription(): string | undefined { return this.entry ? this.entry.getDescription() : super.getDescription(); } @@ -459,13 +459,13 @@ class Renderer implements IRenderer { // Label const options: IIconLabelValueOptions = entry.getLabelOptions() || Object.create(null); options.matches = labelHighlights || []; - options.title = types.withNullAsUndefined(entry.getTooltip()); - options.descriptionTitle = entry.getDescriptionTooltip() || types.withNullAsUndefined(entry.getDescription()); // tooltip over description because it could overflow + options.title = entry.getTooltip(); + options.descriptionTitle = entry.getDescriptionTooltip() || entry.getDescription(); // tooltip over description because it could overflow options.descriptionMatches = descriptionHighlights || []; - data.label.setLabel(types.withNullAsUndefined(entry.getLabel()), types.withNullAsUndefined(entry.getDescription()), options); + data.label.setLabel(types.withNullAsUndefined(entry.getLabel()), entry.getDescription(), options); // Meta - data.detail.set(types.withNullAsUndefined(entry.getDetail()), detailHighlights); + data.detail.set(entry.getDetail(), detailHighlights); // Keybinding data.keybinding.set(entry.getKeybinding()!); @@ -556,7 +556,7 @@ export class QuickOpenModel implements } getLabel(entry: QuickOpenEntry): string | null { - return entry.getLabel(); + return types.withUndefinedAsNull(entry.getLabel()); } getAriaLabel(entry: QuickOpenEntry): string { diff --git a/src/vs/base/test/common/buffer.test.ts b/src/vs/base/test/common/buffer.test.ts index a158aeeb519..c5a3cd676ee 100644 --- a/src/vs/base/test/common/buffer.test.ts +++ b/src/vs/base/test/common/buffer.test.ts @@ -3,18 +3,365 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as assert from 'assert'; -import { hasBuffer, VSBuffer } from 'vs/base/common/buffer'; +import { VSBuffer, bufferToReadable, readableToBuffer, bufferToStream, streamToBuffer, writeableBufferStream } from 'vs/base/common/buffer'; +import { timeout } from 'vs/base/common/async'; suite('Buffer', () => { - if (hasBuffer) { - test('issue #71993 - VSBuffer#toString returns numbers', () => { - const data = new Uint8Array([1, 2, 3, 'h'.charCodeAt(0), 'i'.charCodeAt(0), 4, 5]).buffer; - const buffer = VSBuffer.wrap(new Uint8Array(data, 3, 2)); - assert.deepEqual(buffer.toString(), 'hi'); - }); - } + test('issue #71993 - VSBuffer#toString returns numbers', () => { + const data = new Uint8Array([1, 2, 3, 'h'.charCodeAt(0), 'i'.charCodeAt(0), 4, 5]).buffer; + const buffer = VSBuffer.wrap(new Uint8Array(data, 3, 2)); + assert.deepEqual(buffer.toString(), 'hi'); + }); + test('bufferToReadable / readableToBuffer', () => { + const content = 'Hello World'; + const readable = bufferToReadable(VSBuffer.fromString(content)); + + assert.equal(readableToBuffer(readable).toString(), content); + }); + + test('bufferToStream / streamToBuffer', async () => { + const content = 'Hello World'; + const stream = bufferToStream(VSBuffer.fromString(content)); + + assert.equal((await streamToBuffer(stream)).toString(), content); + }); + + test('bufferWriteableStream - basics (no error)', async () => { + const stream = writeableBufferStream(); + + let chunks: VSBuffer[] = []; + stream.on('data', data => { + chunks.push(data); + }); + + let ended = false; + stream.on('end', () => { + ended = true; + }); + + let errors: Error[] = []; + stream.on('error', error => { + errors.push(error); + }); + + await timeout(0); + stream.write(VSBuffer.fromString('Hello')); + await timeout(0); + stream.end(VSBuffer.fromString('World')); + + assert.equal(chunks.length, 2); + assert.equal(chunks[0].toString(), 'Hello'); + assert.equal(chunks[1].toString(), 'World'); + assert.equal(ended, true); + assert.equal(errors.length, 0); + }); + + test('bufferWriteableStream - basics (error)', async () => { + const stream = writeableBufferStream(); + + let chunks: VSBuffer[] = []; + stream.on('data', data => { + chunks.push(data); + }); + + let ended = false; + stream.on('end', () => { + ended = true; + }); + + let errors: Error[] = []; + stream.on('error', error => { + errors.push(error); + }); + + await timeout(0); + stream.write(VSBuffer.fromString('Hello')); + await timeout(0); + stream.end(new Error()); + + assert.equal(chunks.length, 1); + assert.equal(chunks[0].toString(), 'Hello'); + assert.equal(ended, true); + assert.equal(errors.length, 1); + }); + + test('bufferWriteableStream - buffers data when no listener', async () => { + const stream = writeableBufferStream(); + + await timeout(0); + stream.write(VSBuffer.fromString('Hello')); + await timeout(0); + stream.end(VSBuffer.fromString('World')); + + let chunks: VSBuffer[] = []; + stream.on('data', data => { + chunks.push(data); + }); + + let ended = false; + stream.on('end', () => { + ended = true; + }); + + let errors: Error[] = []; + stream.on('error', error => { + errors.push(error); + }); + + assert.equal(chunks.length, 1); + assert.equal(chunks[0].toString(), 'HelloWorld'); + assert.equal(ended, true); + assert.equal(errors.length, 0); + }); + + test('bufferWriteableStream - buffers errors when no listener', async () => { + const stream = writeableBufferStream(); + + await timeout(0); + stream.write(VSBuffer.fromString('Hello')); + await timeout(0); + stream.error(new Error()); + + let chunks: VSBuffer[] = []; + stream.on('data', data => { + chunks.push(data); + }); + + let errors: Error[] = []; + stream.on('error', error => { + errors.push(error); + }); + + let ended = false; + stream.on('end', () => { + ended = true; + }); + + stream.end(); + + assert.equal(chunks.length, 1); + assert.equal(chunks[0].toString(), 'Hello'); + assert.equal(ended, true); + assert.equal(errors.length, 1); + }); + + test('bufferWriteableStream - buffers end when no listener', async () => { + const stream = writeableBufferStream(); + + await timeout(0); + stream.write(VSBuffer.fromString('Hello')); + await timeout(0); + stream.end(VSBuffer.fromString('World')); + + let ended = false; + stream.on('end', () => { + ended = true; + }); + + let chunks: VSBuffer[] = []; + stream.on('data', data => { + chunks.push(data); + }); + + let errors: Error[] = []; + stream.on('error', error => { + errors.push(error); + }); + + assert.equal(chunks.length, 1); + assert.equal(chunks[0].toString(), 'HelloWorld'); + assert.equal(ended, true); + assert.equal(errors.length, 0); + }); + + test('bufferWriteableStream - nothing happens after end()', async () => { + const stream = writeableBufferStream(); + + let chunks: VSBuffer[] = []; + stream.on('data', data => { + chunks.push(data); + }); + + await timeout(0); + stream.write(VSBuffer.fromString('Hello')); + await timeout(0); + stream.end(VSBuffer.fromString('World')); + + let dataCalledAfterEnd = false; + stream.on('data', data => { + dataCalledAfterEnd = true; + }); + + let errorCalledAfterEnd = false; + stream.on('error', error => { + errorCalledAfterEnd = true; + }); + + let endCalledAfterEnd = false; + stream.on('end', () => { + endCalledAfterEnd = true; + }); + + await timeout(0); + stream.write(VSBuffer.fromString('Hello')); + await timeout(0); + stream.error(new Error()); + await timeout(0); + stream.end(VSBuffer.fromString('World')); + + assert.equal(dataCalledAfterEnd, false); + assert.equal(errorCalledAfterEnd, false); + assert.equal(endCalledAfterEnd, false); + + assert.equal(chunks.length, 2); + assert.equal(chunks[0].toString(), 'Hello'); + assert.equal(chunks[1].toString(), 'World'); + }); + + test('bufferWriteableStream - pause/resume (simple)', async () => { + const stream = writeableBufferStream(); + + let chunks: VSBuffer[] = []; + stream.on('data', data => { + chunks.push(data); + }); + + let ended = false; + stream.on('end', () => { + ended = true; + }); + + let errors: Error[] = []; + stream.on('error', error => { + errors.push(error); + }); + + stream.pause(); + + await timeout(0); + stream.write(VSBuffer.fromString('Hello')); + await timeout(0); + stream.end(VSBuffer.fromString('World')); + + assert.equal(chunks.length, 0); + assert.equal(errors.length, 0); + assert.equal(ended, false); + + stream.resume(); + + assert.equal(chunks.length, 1); + assert.equal(chunks[0].toString(), 'HelloWorld'); + assert.equal(ended, true); + assert.equal(errors.length, 0); + }); + + test('bufferWriteableStream - pause/resume (pause after first write)', async () => { + const stream = writeableBufferStream(); + + let chunks: VSBuffer[] = []; + stream.on('data', data => { + chunks.push(data); + }); + + let ended = false; + stream.on('end', () => { + ended = true; + }); + + let errors: Error[] = []; + stream.on('error', error => { + errors.push(error); + }); + + await timeout(0); + stream.write(VSBuffer.fromString('Hello')); + + stream.pause(); + + await timeout(0); + stream.end(VSBuffer.fromString('World')); + + assert.equal(chunks.length, 1); + assert.equal(chunks[0].toString(), 'Hello'); + assert.equal(errors.length, 0); + assert.equal(ended, false); + + stream.resume(); + + assert.equal(chunks.length, 2); + assert.equal(chunks[0].toString(), 'Hello'); + assert.equal(chunks[1].toString(), 'World'); + assert.equal(ended, true); + assert.equal(errors.length, 0); + }); + + test('bufferWriteableStream - pause/resume (error)', async () => { + const stream = writeableBufferStream(); + + let chunks: VSBuffer[] = []; + stream.on('data', data => { + chunks.push(data); + }); + + let ended = false; + stream.on('end', () => { + ended = true; + }); + + let errors: Error[] = []; + stream.on('error', error => { + errors.push(error); + }); + + stream.pause(); + + await timeout(0); + stream.write(VSBuffer.fromString('Hello')); + await timeout(0); + stream.end(new Error()); + + assert.equal(chunks.length, 0); + assert.equal(ended, false); + assert.equal(errors.length, 0); + + stream.resume(); + + assert.equal(chunks.length, 1); + assert.equal(chunks[0].toString(), 'Hello'); + assert.equal(ended, true); + assert.equal(errors.length, 1); + }); + + test('bufferWriteableStream - destroy', async () => { + const stream = writeableBufferStream(); + + let chunks: VSBuffer[] = []; + stream.on('data', data => { + chunks.push(data); + }); + + let ended = false; + stream.on('end', () => { + ended = true; + }); + + let errors: Error[] = []; + stream.on('error', error => { + errors.push(error); + }); + + stream.destroy(); + + await timeout(0); + stream.write(VSBuffer.fromString('Hello')); + await timeout(0); + stream.end(VSBuffer.fromString('World')); + + assert.equal(chunks.length, 0); + assert.equal(ended, false); + assert.equal(errors.length, 0); + }); }); diff --git a/src/vs/base/test/common/mime.test.ts b/src/vs/base/test/common/mime.test.ts index 75f6d531fe7..4cea0feb565 100644 --- a/src/vs/base/test/common/mime.test.ts +++ b/src/vs/base/test/common/mime.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { guessMimeTypes, registerTextMime, suggestFilename } from 'vs/base/common/mime'; suite('Mime', () => { + test('Dynamically Register Text Mime', () => { let guess = guessMimeTypes('foo.monaco'); assert.deepEqual(guess, ['application/unknown']); @@ -56,6 +57,11 @@ suite('Mime', () => { registerTextMime({ id: 'docker', filepattern: 'dockerfile*', mime: 'text/looser' }); guess = guessMimeTypes('dockerfile'); assert.deepEqual(guess, ['text/winner', 'text/plain']); + + registerTextMime({ id: 'azure-looser', mime: 'text/azure-looser', firstline: /azure/ }); + registerTextMime({ id: 'azure-winner', mime: 'text/azure-winner', firstline: /azure/ }); + guess = guessMimeTypes('azure', 'azure'); + assert.deepEqual(guess, ['text/azure-winner', 'text/plain']); }); test('Specificity priority 1', () => { diff --git a/src/vs/base/test/common/resources.test.ts b/src/vs/base/test/common/resources.test.ts index 22d576dd3f1..b9d1a99d539 100644 --- a/src/vs/base/test/common/resources.test.ts +++ b/src/vs/base/test/common/resources.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath } from 'vs/base/common/resources'; +import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath, addTrailingPathSeparator } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { isWindows } from 'vs/base/common/platform'; import { toSlashes } from 'vs/base/common/extpath'; @@ -178,6 +178,9 @@ suite('Resources', () => { assertEqualURI(removeTrailingPathSeparator(u1), expected, u1.toString()); } + function assertAddTrailingSeparator(u1: URI, expected: URI) { + assertEqualURI(addTrailingPathSeparator(u1), expected, u1.toString()); + } test('trailingPathSeparator', () => { assertTrailingSeparator(URI.parse('foo://a/foo'), false); @@ -190,6 +193,11 @@ suite('Resources', () => { assertRemoveTrailingSeparator(URI.parse('foo://a/'), URI.parse('foo://a/')); assertRemoveTrailingSeparator(URI.parse('foo://a'), URI.parse('foo://a')); + assertAddTrailingSeparator(URI.parse('foo://a/foo'), URI.parse('foo://a/foo/')); + assertAddTrailingSeparator(URI.parse('foo://a/foo/'), URI.parse('foo://a/foo/')); + assertAddTrailingSeparator(URI.parse('foo://a/'), URI.parse('foo://a/')); + assertAddTrailingSeparator(URI.parse('foo://a'), URI.parse('foo://a/')); + if (isWindows) { assertTrailingSeparator(URI.file('c:\\a\\foo'), false); assertTrailingSeparator(URI.file('c:\\a\\foo\\'), true); @@ -202,6 +210,12 @@ suite('Resources', () => { assertRemoveTrailingSeparator(URI.file('c:\\'), URI.file('c:\\')); assertRemoveTrailingSeparator(URI.file('\\\\server\\share\\some\\'), URI.file('\\\\server\\share\\some')); assertRemoveTrailingSeparator(URI.file('\\\\server\\share\\'), URI.file('\\\\server\\share\\')); + + assertAddTrailingSeparator(URI.file('c:\\a\\foo'), URI.file('c:\\a\\foo\\')); + assertAddTrailingSeparator(URI.file('c:\\a\\foo\\'), URI.file('c:\\a\\foo\\')); + assertAddTrailingSeparator(URI.file('c:\\'), URI.file('c:\\')); + assertAddTrailingSeparator(URI.file('\\\\server\\share\\some'), URI.file('\\\\server\\share\\some\\')); + assertAddTrailingSeparator(URI.file('\\\\server\\share\\some\\'), URI.file('\\\\server\\share\\some\\')); } else { assertTrailingSeparator(URI.file('/foo/bar'), false); assertTrailingSeparator(URI.file('/foo/bar/'), true); @@ -210,12 +224,16 @@ suite('Resources', () => { assertRemoveTrailingSeparator(URI.file('/foo/bar'), URI.file('/foo/bar')); assertRemoveTrailingSeparator(URI.file('/foo/bar/'), URI.file('/foo/bar')); assertRemoveTrailingSeparator(URI.file('/'), URI.file('/')); + + assertAddTrailingSeparator(URI.file('/foo/bar'), URI.file('/foo/bar/')); + assertAddTrailingSeparator(URI.file('/foo/bar/'), URI.file('/foo/bar/')); + assertAddTrailingSeparator(URI.file('/'), URI.file('/')); } }); function assertEqualURI(actual: URI, expected: URI, message?: string) { if (!isEqual(expected, actual)) { - assert.equal(expected.toString(), actual.toString(), message); + assert.equal(actual.toString(), expected.toString(), message); } } diff --git a/src/vs/base/test/node/config.test.ts b/src/vs/base/test/node/config.test.ts index 05539350074..3fa8f78de68 100644 --- a/src/vs/base/test/node/config.test.ts +++ b/src/vs/base/test/node/config.test.ts @@ -20,7 +20,7 @@ suite('Config', () => { const newDir = path.join(parentDir, 'config', id); const testFile = path.join(newDir, 'config.json'); - let watcher = new ConfigWatcher(testFile); + let watcher = new ConfigWatcher<{}>(testFile); let config = watcher.getConfig(); assert.ok(config); diff --git a/src/vs/base/test/node/encoding/encoding.test.ts b/src/vs/base/test/node/encoding/encoding.test.ts index 5c38f2429ab..77ad22264cf 100644 --- a/src/vs/base/test/node/encoding/encoding.test.ts +++ b/src/vs/base/test/node/encoding/encoding.test.ts @@ -6,51 +6,114 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as encoding from 'vs/base/node/encoding'; -import { readExactlyByFile } from 'vs/base/node/stream'; import { Readable } from 'stream'; import { getPathFromAmdModule } from 'vs/base/common/amd'; +export async function detectEncodingByBOM(file: string): Promise { + try { + const { buffer, bytesRead } = await readExactlyByFile(file, 3); + + return encoding.detectEncodingByBOMFromBuffer(buffer, bytesRead); + } catch (error) { + return null; // ignore errors (like file not found) + } +} + +interface ReadResult { + buffer: Buffer | null; + bytesRead: number; +} + +function readExactlyByFile(file: string, totalBytes: number): Promise { + return new Promise((resolve, reject) => { + fs.open(file, 'r', null, (err, fd) => { + if (err) { + return reject(err); + } + + function end(err: Error | null, resultBuffer: Buffer | null, bytesRead: number): void { + fs.close(fd, closeError => { + if (closeError) { + return reject(closeError); + } + + if (err && (err).code === 'EISDIR') { + return reject(err); // we want to bubble this error up (file is actually a folder) + } + + return resolve({ buffer: resultBuffer, bytesRead }); + }); + } + + const buffer = Buffer.allocUnsafe(totalBytes); + let offset = 0; + + function readChunk(): void { + fs.read(fd, buffer, offset, totalBytes - offset, null, (err, bytesRead) => { + if (err) { + return end(err, null, 0); + } + + if (bytesRead === 0) { + return end(null, buffer, offset); + } + + offset += bytesRead; + + if (offset === totalBytes) { + return end(null, buffer, offset); + } + + return readChunk(); + }); + } + + readChunk(); + }); + }); +} + suite('Encoding', () => { test('detectBOM does not return error for non existing file', async () => { const file = getPathFromAmdModule(require, './fixtures/not-exist.css'); - const detectedEncoding = await encoding.detectEncodingByBOM(file); + const detectedEncoding = await detectEncodingByBOM(file); assert.equal(detectedEncoding, null); }); test('detectBOM UTF-8', async () => { const file = getPathFromAmdModule(require, './fixtures/some_utf8.css'); - const detectedEncoding = await encoding.detectEncodingByBOM(file); + const detectedEncoding = await detectEncodingByBOM(file); assert.equal(detectedEncoding, 'utf8'); }); test('detectBOM UTF-16 LE', async () => { const file = getPathFromAmdModule(require, './fixtures/some_utf16le.css'); - const detectedEncoding = await encoding.detectEncodingByBOM(file); + const detectedEncoding = await detectEncodingByBOM(file); assert.equal(detectedEncoding, 'utf16le'); }); test('detectBOM UTF-16 BE', async () => { const file = getPathFromAmdModule(require, './fixtures/some_utf16be.css'); - const detectedEncoding = await encoding.detectEncodingByBOM(file); + const detectedEncoding = await detectEncodingByBOM(file); assert.equal(detectedEncoding, 'utf16be'); }); test('detectBOM ANSI', async function () { const file = getPathFromAmdModule(require, './fixtures/some_ansi.css'); - const detectedEncoding = await encoding.detectEncodingByBOM(file); + const detectedEncoding = await detectEncodingByBOM(file); assert.equal(detectedEncoding, null); }); test('detectBOM ANSI', async function () { const file = getPathFromAmdModule(require, './fixtures/empty.txt'); - const detectedEncoding = await encoding.detectEncodingByBOM(file); + const detectedEncoding = await detectEncodingByBOM(file); assert.equal(detectedEncoding, null); }); @@ -177,7 +240,7 @@ suite('Encoding', () => { } }); - let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4 }); + let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 }); assert.ok(detected); assert.ok(stream); @@ -197,7 +260,7 @@ suite('Encoding', () => { } }); - let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64 }); + let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 }); assert.ok(detected); assert.ok(stream); @@ -214,7 +277,7 @@ suite('Encoding', () => { } }); - let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 512 }); + let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 512, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 }); assert.ok(detected); assert.ok(stream); @@ -229,7 +292,7 @@ suite('Encoding', () => { let path = getPathFromAmdModule(require, './fixtures/some_utf16be.css'); let source = fs.createReadStream(path); - let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64 }); + let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 }); assert.equal(detected.encoding, 'utf16be'); assert.equal(detected.seemsBinary, false); @@ -244,7 +307,7 @@ suite('Encoding', () => { let path = getPathFromAmdModule(require, './fixtures/empty.txt'); let source = fs.createReadStream(path); - let { detected, stream } = await encoding.toDecodeStream(source, {}); + let { detected, stream } = await encoding.toDecodeStream(source, { guessEncoding: false, overwriteEncoding: detected => detected || encoding.UTF8 }); let expected = await readAndDecodeFromDisk(path, detected.encoding); let actual = await readAllAsString(stream); diff --git a/src/vs/base/test/node/stream/stream.test.ts b/src/vs/base/test/node/stream/stream.test.ts index 632787e68bb..f813b0a4893 100644 --- a/src/vs/base/test/node/stream/stream.test.ts +++ b/src/vs/base/test/node/stream/stream.test.ts @@ -9,23 +9,6 @@ import * as stream from 'vs/base/node/stream'; import { getPathFromAmdModule } from 'vs/base/common/amd'; suite('Stream', () => { - test('readExactlyByFile - ANSI', function () { - const file = getPathFromAmdModule(require, './fixtures/file.css'); - - return stream.readExactlyByFile(file, 10).then(({ buffer, bytesRead }) => { - assert.equal(bytesRead, 10); - assert.equal(buffer!.toString(), '/*--------'); - }); - }); - - test('readExactlyByFile - empty', function () { - const file = getPathFromAmdModule(require, './fixtures/empty.txt'); - - return stream.readExactlyByFile(file, 10).then(({ bytesRead }) => { - assert.equal(bytesRead, 0); - }); - }); - test('readToMatchingString - ANSI', function () { const file = getPathFromAmdModule(require, './fixtures/file.css'); diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html new file mode 100644 index 00000000000..832b6033bf7 --- /dev/null +++ b/src/vs/code/browser/workbench/workbench.html @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/vs/code/browser/workbench/workbench.js b/src/vs/code/browser/workbench/workbench.js new file mode 100644 index 00000000000..76e66c5c8d2 --- /dev/null +++ b/src/vs/code/browser/workbench/workbench.js @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check +'use strict'; + +(function () { + + function loadScript(path, callback) { + let script = document.createElement('script'); + script.onload = callback; + script.async = true; + script.type = 'text/javascript'; + script.src = path; + document.head.appendChild(script); + } + + loadScript('../../../../../out/vs/loader.js', function () { + + // @ts-ignore + require.config({ + baseUrl: `${window.location.origin}/out` + }); + + // @ts-ignore + require([ + 'vs/workbench/workbench.web.main', + 'vs/nls!vs/workbench/workbench.web.main', + 'vs/css!vs/workbench/workbench.web.main' + ], + // @ts-ignore + function () { + + // @ts-ignore + require('vs/workbench/browser/web.main').main().then(undefined, console.error); + }); + }); +})(); \ No newline at end of file diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 2d09466d9bb..90767e1a856 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -40,7 +40,7 @@ import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil'; import { Button } from 'vs/base/browser/ui/button/button'; import { withUndefinedAsNull } from 'vs/base/common/types'; -import { SystemInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; const MAX_URL_LENGTH = platform.isWindows ? 2081 : 5400; @@ -938,15 +938,24 @@ export class IssueReporter extends Disposable { `; systemInfo.remoteData.forEach(remote => { - renderedData += ` -
- - - - - - -
Remote${remote.hostName}
OS${remote.machineInfo.os}
CPUs${remote.machineInfo.cpus}
Memory (System)${remote.machineInfo.memory}
VM${remote.machineInfo.vmHint}
`; + if (isRemoteDiagnosticError(remote)) { + renderedData += ` +
+ + + +
Remote${remote.hostName}
${remote.errorMessage}
`; + } else { + renderedData += ` +
+ + + + + + +
Remote${remote.hostName}
OS${remote.machineInfo.os}
CPUs${remote.machineInfo.cpus}
Memory (System)${remote.machineInfo.memory}
VM${remote.machineInfo.vmHint}
`; + } }); target.innerHTML = renderedData; diff --git a/src/vs/code/electron-browser/issue/issueReporterModel.ts b/src/vs/code/electron-browser/issue/issueReporterModel.ts index b4158cf6bb4..c1f78969153 100644 --- a/src/vs/code/electron-browser/issue/issueReporterModel.ts +++ b/src/vs/code/electron-browser/issue/issueReporterModel.ts @@ -5,7 +5,7 @@ import { assign } from 'vs/base/common/objects'; import { IssueType, ISettingSearchResult, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; -import { SystemInfo } from 'vs/platform/diagnostics/common/diagnosticsService'; +import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnosticsService'; export interface IssueReporterData { issueType: IssueType; @@ -75,7 +75,8 @@ ${this.getInfos()} private getRemoteOSes(): string { if (this._data.systemInfo && this._data.systemInfo.remoteData.length) { - return this._data.systemInfo.remoteData.map(remote => `Remote OS version: ${remote.machineInfo.os}`).join('\n') + '\n'; + return this._data.systemInfo.remoteData + .map(remote => isRemoteDiagnosticError(remote) ? remote.errorMessage : `Remote OS version: ${remote.machineInfo.os}`).join('\n') + '\n'; } return ''; @@ -168,7 +169,10 @@ ${this.getInfos()} |VM|${this._data.systemInfo.vmHint}|`; this._data.systemInfo.remoteData.forEach(remote => { - md += ` + if (isRemoteDiagnosticError(remote)) { + md += `\n\n${remote.errorMessage}`; + } else { + md += ` |Item|Value| |---|---| @@ -177,6 +181,7 @@ ${this.getInfos()} |CPUs|${remote.machineInfo.cpus}| |Memory (System)|${remote.machineInfo.memory}| |VM|${remote.machineInfo.vmHint}|`; + } }); } diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html index 13232577758..536ba2549b3 100644 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ b/src/vs/code/electron-browser/workbench/workbench.html @@ -3,7 +3,7 @@ - + diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index dbec9eadbbb..28e3fefafcc 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -87,8 +87,8 @@ function showPartsSplash(configuration) { const style = document.createElement('style'); style.className = 'initialShellColors'; document.head.appendChild(style); - document.body.className = `monaco-shell ${baseTheme}`; - style.innerHTML = `.monaco-shell { background-color: ${shellBackground}; color: ${shellForeground}; }`; + document.body.className = baseTheme; + style.innerHTML = `body { background-color: ${shellBackground}; color: ${shellForeground}; }`; if (data && data.layoutInfo) { // restore parts if possible (we might not always store layout info) diff --git a/src/vs/code/electron-browser/workbench/workbench.nodeless.html b/src/vs/code/electron-browser/workbench/workbench.nodeless.html deleted file mode 100644 index e37abfdf5ae..00000000000 --- a/src/vs/code/electron-browser/workbench/workbench.nodeless.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/src/vs/code/electron-browser/workbench/workbench.nodeless.js b/src/vs/code/electron-browser/workbench/workbench.nodeless.js deleted file mode 100644 index 4d7098fa3e4..00000000000 --- a/src/vs/code/electron-browser/workbench/workbench.nodeless.js +++ /dev/null @@ -1,67 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check -'use strict'; - -(function () { - - function uriFromPath(_path) { - let pathName = _path.replace(/\\/g, '/'); - if (pathName.length > 0 && pathName.charAt(0) !== '/') { - pathName = '/' + pathName; - } - - let uri; - if (navigator.userAgent.indexOf('Windows') >= 0 && pathName.startsWith('//')) { // specially handle Windows UNC paths - uri = encodeURI('file:' + pathName); - } else { - uri = encodeURI('file://' + pathName); - } - - return uri.replace(/#/g, '%23'); - } - - function parseURLQueryArgs() { - const search = window.location.search || ''; - - return search.split(/[?&]/) - .filter(function (param) { return !!param; }) - .map(function (param) { return param.split('='); }) - .filter(function (param) { return param.length === 2; }) - .reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {}); - } - - function loadScript(path, callback) { - let script = document.createElement('script'); - script.onload = callback; - script.async = true; - script.type = 'text/javascript'; - script.src = path; - document.head.appendChild(script); - } - - loadScript('../../../../../out/vs/loader.js', function () { - - const args = parseURLQueryArgs(); - const configuration = JSON.parse(args['config'] || '{}') || {}; - - // @ts-ignore - require.config({ - baseUrl: uriFromPath(configuration.appRoot) + '/out', - }); - - // @ts-ignore - require([ - 'vs/workbench/workbench.nodeless.main', - 'vs/nls!vs/workbench/workbench.nodeless.main', - 'vs/css!vs/workbench/workbench.nodeless.main' - ], function () { - - // @ts-ignore - require('vs/workbench/browser/nodeless.main').main().then(undefined, console.error); - }); - }); -})(); \ No newline at end of file diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 5ba6e094404..8b1c92ffa72 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -234,7 +234,7 @@ export class CodeApplication extends Disposable { ipc.on('vscode:fetchShellEnv', (event: Event) => { const webContents = event.sender; - getShellEnvironment().then(shellEnv => { + getShellEnvironment(this.logService).then(shellEnv => { if (!webContents.isDestroyed()) { webContents.send('vscode:acceptShellEnv', shellEnv); } @@ -678,7 +678,7 @@ export class CodeApplication extends Disposable { historyMainService.onRecentlyOpenedChange(() => historyMainService.updateWindowsJumpList()); // Start shared process after a while - const sharedProcessSpawn = this._register(new RunOnceScheduler(() => getShellEnvironment().then(userEnv => this.sharedProcess.spawn(userEnv)), 3000)); + const sharedProcessSpawn = this._register(new RunOnceScheduler(() => getShellEnvironment(this.logService).then(userEnv => this.sharedProcess.spawn(userEnv)), 3000)); sharedProcessSpawn.schedule(); } diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 76d042d1e91..e356b0463ef 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -55,7 +55,7 @@ function setupIPC(accessor: ServicesAccessor): Promise { logService.trace('Sending some foreground love to the running instance:', processId); try { - const { allowSetForegroundWindow } = require.__$__nodeRequire('windows-foreground-love'); + const { allowSetForegroundWindow } = require.__$__nodeRequire('windows-foreground-love'); allowSetForegroundWindow(processId); } catch (e) { // noop diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 75370bf03c5..a2b26377ec2 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects'; import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { IStateService } from 'vs/platform/state/common/state'; -import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage } from 'electron'; +import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display } from 'electron'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -25,7 +25,6 @@ import * as perf from 'vs/base/common/performance'; import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { getBackgroundColor } from 'vs/code/electron-main/theme'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { endsWith } from 'vs/base/common/strings'; export interface IWindowCreationOptions { @@ -80,8 +79,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { private readonly touchBarGroups: Electron.TouchBarSegmentedControl[]; - private nodeless: boolean; - constructor( config: IWindowCreationOptions, @ILogService private readonly logService: ILogService, @@ -98,8 +95,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._readyState = ReadyState.NONE; this.whenReadyCallbacks = []; - this.nodeless = !!(environmentService.args.nodeless && !environmentService.isBuilt); - // create browser window this.createBrowserWindow(config); @@ -129,7 +124,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { height: this.windowState.height, x: this.windowState.x, y: this.windowState.y, - backgroundColor: this.nodeless ? undefined : getBackgroundColor(this.stateService), + backgroundColor: getBackgroundColor(this.stateService), minWidth: CodeWindow.MIN_WIDTH, minHeight: CodeWindow.MIN_HEIGHT, show: !isFullscreenOrMaximized, @@ -143,10 +138,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { } }; - if (this.nodeless) { - options.webPreferences!.nodeIntegration = false; // simulate Electron 5 behaviour - } - if (isLinux) { options.icon = path.join(this.environmentService.appRoot, 'resources/linux/code.png'); // Windows and Mac are better off using the embedded icon(s) } @@ -198,10 +189,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - if (this.nodeless) { - this._win.webContents.toggleDevTools(); - } - this._lastFocusTime = Date.now(); // since we show directly, we need to set the last focus time too } @@ -554,8 +541,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { const configuration = configurationIn ? configurationIn : objects.mixin({}, this.currentConfig); // Delete some properties we do not want during reload - delete configuration.filesToOpen; - delete configuration.filesToCreate; + delete configuration.filesToOpenOrCreate; delete configuration.filesToDiff; delete configuration.filesToWait; @@ -638,10 +624,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private doGetUrl(config: object): string { - if (this.nodeless) { - return `${require.toUrl('vs/code/electron-browser/workbench/workbench.nodeless.html')}?config=${encodeURIComponent(JSON.stringify(config))}`; - } - return `${require.toUrl('vs/code/electron-browser/workbench/workbench.html')}?config=${encodeURIComponent(JSON.stringify(config))}`; } @@ -707,66 +689,55 @@ export class CodeWindow extends Disposable implements ICodeWindow { private restoreWindowState(state?: IWindowState): IWindowState { if (state) { try { - state = withNullAsUndefined(this.validateWindowState(state)); + state = this.validateWindowState(state); } catch (err) { this.logService.warn(`Unexpected error validating window state: ${err}\n${err.stack}`); // somehow display API can be picky about the state to validate } } - - if (!state) { - state = defaultWindowState(); - } - - return state; + return state || defaultWindowState(); } - private validateWindowState(state: IWindowState): IWindowState | null { - if (!state) { - return null; - } - + private validateWindowState(state: IWindowState): IWindowState | undefined { if (typeof state.x !== 'number' || typeof state.y !== 'number' || typeof state.width !== 'number' || typeof state.height !== 'number' ) { - return null; + return undefined; } if (state.width <= 0 || state.height <= 0) { - return null; + return undefined; } const displays = screen.getAllDisplays(); // Single Monitor: be strict about x/y positioning if (displays.length === 1) { - const displayBounds = displays[0].bounds; - - // Careful with maximized: in that mode x/y can well be negative! - if (state.mode !== WindowMode.Maximized && displayBounds.width > 0 && displayBounds.height > 0 /* Linux X11 sessions sometimes report wrong display bounds */) { - if (state.x < displayBounds.x) { - state.x = displayBounds.x; // prevent window from falling out of the screen to the left + const displayWorkingArea = this.getWorkingArea(displays[0]); + if (state.mode !== WindowMode.Maximized && displayWorkingArea) { + if (state.x < displayWorkingArea.x) { + state.x = displayWorkingArea.x; // prevent window from falling out of the screen to the left } - if (state.y < displayBounds.y) { - state.y = displayBounds.y; // prevent window from falling out of the screen to the top + if (state.y < displayWorkingArea.y) { + state.y = displayWorkingArea.y; // prevent window from falling out of the screen to the top } - if (state.x > (displayBounds.x + displayBounds.width)) { - state.x = displayBounds.x; // prevent window from falling out of the screen to the right + if (state.x > (displayWorkingArea.x + displayWorkingArea.width)) { + state.x = displayWorkingArea.x; // prevent window from falling out of the screen to the right } - if (state.y > (displayBounds.y + displayBounds.height)) { - state.y = displayBounds.y; // prevent window from falling out of the screen to the bottom + if (state.y > (displayWorkingArea.y + displayWorkingArea.height)) { + state.y = displayWorkingArea.y; // prevent window from falling out of the screen to the bottom } - if (state.width > displayBounds.width) { - state.width = displayBounds.width; // prevent window from exceeding display bounds width + if (state.width > displayWorkingArea.width) { + state.width = displayWorkingArea.width; // prevent window from exceeding display bounds width } - if (state.height > displayBounds.height) { - state.height = displayBounds.height; // prevent window from exceeding display bounds height + if (state.height > displayWorkingArea.height) { + state.height = displayWorkingArea.height; // prevent window from exceeding display bounds height } } @@ -792,12 +763,14 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Multi Monitor (non-fullscreen): be less strict because metrics can be crazy const bounds = { x: state.x, y: state.y, width: state.width, height: state.height }; const display = screen.getDisplayMatching(bounds); + const displayWorkingArea = this.getWorkingArea(display); if ( - display && // we have a display matching the desired bounds - bounds.x < display.bounds.x + display.bounds.width && // prevent window from falling out of the screen to the right - bounds.y < display.bounds.y + display.bounds.height && // prevent window from falling out of the screen to the bottom - bounds.x + bounds.width > display.bounds.x && // prevent window from falling out of the screen to the left - bounds.y + bounds.height > display.bounds.y // prevent window from falling out of the scree nto the top + display && // we have a display matching the desired bounds + displayWorkingArea && // we have valid working area bounds + bounds.x < displayWorkingArea.x + displayWorkingArea.width && // prevent window from falling out of the screen to the right + bounds.y < displayWorkingArea.y + displayWorkingArea.height && // prevent window from falling out of the screen to the bottom + bounds.x + bounds.width > displayWorkingArea.x && // prevent window from falling out of the screen to the left + bounds.y + bounds.height > displayWorkingArea.y // prevent window from falling out of the scree nto the top ) { if (state.mode === WindowMode.Maximized) { const defaults = defaultWindowState(WindowMode.Maximized); // when maximized, make sure we have good values when the user restores the window @@ -810,7 +783,25 @@ export class CodeWindow extends Disposable implements ICodeWindow { return state; } - return null; + return undefined; + } + + private getWorkingArea(display: Display): Rectangle | undefined { + + // Prefer the working area of the display to account for taskbars on the + // desktop being positioned somewhere (https://github.com/Microsoft/vscode/issues/50830). + // + // Linux X11 sessions sometimes report wrong display bounds, so we validate + // the reported sizes are positive. + if (display.workArea.width > 0 && display.workArea.height > 0) { + return display.workArea; + } + + if (display.bounds.width > 0 && display.bounds.height > 0) { + return display.bounds; + } + + return undefined; } getBounds(): Electron.Rectangle { diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index fb3da124d0b..26db33a6d80 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -91,8 +91,7 @@ interface IPathParseOptions { } interface IFileInputs { - filesToOpen: IPath[]; - filesToCreate: IPath[]; + filesToOpenOrCreate: IPath[]; filesToDiff: IPath[]; filesToWait?: IPathsToWaitFor; remoteAuthority?: string; @@ -112,9 +111,6 @@ interface IPathToOpen extends IPath { // the remote authority for the Code instance to open. Undefined if not remote. remoteAuthority?: string; - // indicator to create the file path in the Code instance - createFilePath?: boolean; - // optional label for the recent history label?: string; } @@ -397,13 +393,9 @@ export class WindowsManager implements IWindowsMainService { workspacesToOpen.push(path); } else if (path.fileUri) { if (!fileInputs) { - fileInputs = { filesToCreate: [], filesToOpen: [], filesToDiff: [], remoteAuthority: path.remoteAuthority }; - } - if (!path.createFilePath) { - fileInputs.filesToOpen.push(path); - } else { - fileInputs.filesToCreate.push(path); + fileInputs = { filesToOpenOrCreate: [], filesToDiff: [], remoteAuthority: path.remoteAuthority }; } + fileInputs.filesToOpenOrCreate.push(path); } else if (path.backupPath) { emptyToRestore.push({ backupFolder: basename(path.backupPath), remoteAuthority: path.remoteAuthority }); } else { @@ -413,15 +405,14 @@ export class WindowsManager implements IWindowsMainService { // When run with --diff, take the files to open as files to diff // if there are exactly two files provided. - if (fileInputs && openConfig.diffMode && fileInputs.filesToOpen.length === 2) { - fileInputs.filesToDiff = fileInputs.filesToOpen; - fileInputs.filesToOpen = []; - fileInputs.filesToCreate = []; // diff ignores other files that do not exist + if (fileInputs && openConfig.diffMode && fileInputs.filesToOpenOrCreate.length === 2) { + fileInputs.filesToDiff = fileInputs.filesToOpenOrCreate; + fileInputs.filesToOpenOrCreate = []; } // When run with --wait, make sure we keep the paths to wait for if (fileInputs && openConfig.waitMarkerFileURI) { - fileInputs.filesToWait = { paths: [...fileInputs.filesToDiff, ...fileInputs.filesToOpen, ...fileInputs.filesToCreate], waitMarkerFileUri: openConfig.waitMarkerFileURI }; + fileInputs.filesToWait = { paths: [...fileInputs.filesToDiff, ...fileInputs.filesToOpenOrCreate], waitMarkerFileUri: openConfig.waitMarkerFileURI }; } // @@ -551,7 +542,7 @@ export class WindowsManager implements IWindowsMainService { if (potentialWindowsCount === 0 && fileInputs) { // Find suitable window or folder path to open files in - const fileToCheck = fileInputs.filesToOpen[0] || fileInputs.filesToCreate[0] || fileInputs.filesToDiff[0]; + const fileToCheck = fileInputs.filesToOpenOrCreate[0] || fileInputs.filesToDiff[0]; // only look at the windows with correct authority const windows = WindowsManager.WINDOWS.filter(w => w.remoteAuthority === fileInputs!.remoteAuthority); @@ -746,10 +737,9 @@ export class WindowsManager implements IWindowsMainService { private doOpenFilesInExistingWindow(configuration: IOpenConfiguration, window: ICodeWindow, fileInputs?: IFileInputs): ICodeWindow { window.focus(); // make sure window has focus - const params: { filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[], filesToWait?: IPathsToWaitFor, termProgram?: string } = {}; + const params: { filesToOpenOrCreate?: IPath[], filesToDiff?: IPath[], filesToWait?: IPathsToWaitFor, termProgram?: string } = {}; if (fileInputs) { - params.filesToOpen = fileInputs.filesToOpen; - params.filesToCreate = fileInputs.filesToCreate; + params.filesToOpenOrCreate = fileInputs.filesToOpenOrCreate; params.filesToDiff = fileInputs.filesToDiff; params.filesToWait = fileInputs.filesToWait; } @@ -958,7 +948,7 @@ export class WindowsManager implements IWindowsMainService { if (pathToOpen && pathToOpen.folderUri) { windowsToOpen.push(pathToOpen); } - } else if (restoreWindows !== 'folders' && openedWindow.backupPath) { // Windows that were Empty + } else if (restoreWindows !== 'folders' && openedWindow.backupPath && !openedWindow.remoteAuthority) { // Local windows that were empty. Empty windows with backups will always be restored in open() windowsToOpen.push({ backupPath: openedWindow.backupPath, remoteAuthority: openedWindow.remoteAuthority }); } } @@ -1065,47 +1055,57 @@ export class WindowsManager implements IWindowsMainService { anyPath = parsedPath.path; } - // open remote if either specified in the cli even if it is a local file. TODO: Future idea: resolve in remote host context. + // open remote if either specified in the cli even if it is a local file. TODO@aeschli: Future idea: resolve in remote host context. const remoteAuthority = options.remoteAuthority; const candidate = normalize(anyPath); try { const candidateStat = fs.statSync(candidate); - if (candidateStat) { - if (candidateStat.isFile()) { + if (candidateStat.isFile()) { - // Workspace (unless disabled via flag) - if (!forceOpenWorkspaceAsFile) { - const workspace = this.workspacesMainService.resolveLocalWorkspaceSync(URI.file(candidate)); - if (workspace) { - return { workspace: { id: workspace.id, configPath: workspace.configPath }, remoteAuthority: workspace.remoteAuthority }; - } + // Workspace (unless disabled via flag) + if (!forceOpenWorkspaceAsFile) { + const workspace = this.workspacesMainService.resolveLocalWorkspaceSync(URI.file(candidate)); + if (workspace) { + return { + workspace: { id: workspace.id, configPath: workspace.configPath }, + remoteAuthority: workspace.remoteAuthority, + exists: true + }; } - - // File - return { - fileUri: URI.file(candidate), - lineNumber, - columnNumber, - remoteAuthority - }; } - // Folder (we check for isDirectory() because e.g. paths like /dev/null - // are neither file nor folder but some external tools might pass them - // over to us) - else if (candidateStat.isDirectory()) { - return { - folderUri: URI.file(candidate), - remoteAuthority - }; - } + // File + return { + fileUri: URI.file(candidate), + lineNumber, + columnNumber, + remoteAuthority, + exists: true + }; + } + + // Folder (we check for isDirectory() because e.g. paths like /dev/null + // are neither file nor folder but some external tools might pass them + // over to us) + else if (candidateStat.isDirectory()) { + return { + folderUri: URI.file(candidate), + remoteAuthority, + exists: true + }; } } catch (error) { const fileUri = URI.file(candidate); this.historyMainService.removeFromRecentlyOpened([fileUri]); // since file does not seem to exist anymore, remove from recent + + // assume this is a file that does not yet exist if (options && options.ignoreFileNotFound) { - return { fileUri, createFilePath: true, remoteAuthority }; // assume this is a file that does not yet exist + return { + fileUri, + remoteAuthority, + exists: false + }; } } @@ -1190,46 +1190,62 @@ export class WindowsManager implements IWindowsMainService { } } - // Make sure we are not asked to open a workspace or folder that is already opened - if (cliArgs.length && cliArgs.some(path => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, URI.file(path)))) { - cliArgs = []; + if (!Array.isArray(extensionDevelopmentPath)) { + extensionDevelopmentPath = [extensionDevelopmentPath]; } - if (folderUris.length && folderUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.argToUri(uri)))) { - folderUris = []; + let authority = ''; + for (let p of extensionDevelopmentPath) { + if (p.match(/^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/)) { + const url = URI.parse(p); + if (url.scheme === Schemas.vscodeRemote) { + if (authority) { + if (url.authority !== authority) { + this.logService.error('more than one extension development path authority'); + } + } else { + authority = url.authority; + } + } + } } - if (fileUris.length && fileUris.some(uri => !!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, this.argToUri(uri)))) { - fileUris = []; - } + // Make sure that we do not try to open: + // - a workspace or folder that is already opened + // - a workspace or file that has a different authority as the extension development. + + cliArgs = cliArgs.filter(path => { + const uri = URI.file(path); + if (!!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, uri)) { + return false; + } + return uri.authority === authority; + }); + + folderUris = folderUris.filter(uri => { + const u = this.argToUri(uri); + if (!!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, u)) { + return false; + } + return u ? u.authority === authority : false; + }); + + fileUris = fileUris.filter(uri => { + const u = this.argToUri(uri); + if (!!findWindowOnWorkspaceOrFolderUri(WindowsManager.WINDOWS, u)) { + return false; + } + return u ? u.authority === authority : false; + }); openConfig.cli._ = cliArgs; openConfig.cli['folder-uri'] = folderUris; openConfig.cli['file-uri'] = fileUris; - if (Array.isArray(extensionDevelopmentPath)) { - let authority: string | undefined = undefined; - for (let p of extensionDevelopmentPath) { - const match = p.match(/^vscode-remote:\/\/([^\/]+)/); - if (match) { - const auth = URI.parse(p).authority; - if (authority) { - if (auth !== authority) { - console.log('more than one authority'); - } - } else { - authority = auth; - } - } - } + // if there are no files or folders cli args left, use the "remote" cli argument + if (!cliArgs.length && !folderUris.length && !fileUris.length) { if (authority) { - openConfig.cli['remote'] = authority; - } - - } else { - const match = extensionDevelopmentPath.match(/^vscode-remote:\/\/([^\/]+)/); - if (match) { - openConfig.cli['remote'] = URI.parse(extensionDevelopmentPath).authority; + openConfig.cli.remote = authority; } } @@ -1263,8 +1279,7 @@ export class WindowsManager implements IWindowsMainService { const fileInputs = options.fileInputs; if (fileInputs) { - configuration.filesToOpen = fileInputs.filesToOpen; - configuration.filesToCreate = fileInputs.filesToCreate; + configuration.filesToOpenOrCreate = fileInputs.filesToOpenOrCreate; configuration.filesToDiff = fileInputs.filesToDiff; configuration.filesToWait = fileInputs.filesToWait; } @@ -1580,8 +1595,9 @@ export class WindowsManager implements IWindowsMainService { if (cli && (cli.remote !== remote)) { cli = { ...cli, remote }; } - const forceNewWindow = !(options && options.reuseWindow); - return this.open({ context, cli, forceNewWindow, forceEmpty: true }); + const forceReuseWindow = options && options.reuseWindow; + const forceNewWindow = !forceReuseWindow; + return this.open({ context, cli, forceEmpty: true, forceNewWindow, forceReuseWindow }); } openNewTabbedWindow(context: OpenContext): ICodeWindow[] { diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 0e496be8ae1..d45f35087c1 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -112,6 +112,10 @@ export class Main { private async installExtensions(extensions: string[], force: boolean): Promise { const failed: string[] = []; const installedExtensionsManifests: IExtensionManifest[] = []; + if (extensions.length) { + console.log(localize('installingExtensions', "Installing extensions...")); + } + for (const extension of extensions) { try { const manifest = await this.installExtension(extension, force); @@ -142,11 +146,11 @@ export class Main { if (valid) { return this.extensionManagementService.install(URI.file(extension)).then(id => { - console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed!", getBaseLabel(extension))); + console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed.", getBaseLabel(extension))); return manifest; }, error => { if (isPromiseCanceledError(error)) { - console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", getBaseLabel(extension))); + console.log(localize('cancelVsixInstall', "Cancelled installing extension '{0}'.", getBaseLabel(extension))); return null; } else { return Promise.reject(error); @@ -191,9 +195,7 @@ export class Main { console.log(localize('forceUpdate', "Extension '{0}' v{1} is already installed, but a newer version {2} is available in the marketplace. Use '--force' option to update to newer version.", id, installedExtension.manifest.version, extension.version)); return Promise.resolve(null); } - console.log(localize('updateMessage', "Updating the Extension '{0}' to the version {1}", id, extension.version)); - } else { - console.log(localize('foundExtension', "Found '{0}' in the marketplace.", id)); + console.log(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, extension.version)); } await this.installFromGallery(id, extension); return manifest; @@ -210,7 +212,7 @@ export class Main { const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, local.identifier) && semver.gt(local.manifest.version, manifest.version))[0]; if (newer && !force) { - console.log(localize('forceDowngrade', "A newer version of this extension '{0}' v{1} is already installed. Use '--force' option to downgrade to older version.", newer.identifier.id, newer.manifest.version, manifest.version)); + console.log(localize('forceDowngrade', "A newer version of extension '{0}' v{1} is already installed. Use '--force' option to downgrade to older version.", newer.identifier.id, newer.manifest.version, manifest.version)); return false; } @@ -218,14 +220,14 @@ export class Main { } private async installFromGallery(id: string, extension: IGalleryExtension): Promise { - console.log(localize('installing', "Installing...")); + console.log(localize('installing', "Installing extension '{0}' v{1}...", id, extension.version)); try { await this.extensionManagementService.installFromGallery(extension); - console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed!", id, extension.version)); + console.log(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, extension.version)); } catch (error) { if (isPromiseCanceledError(error)) { - console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", id)); + console.log(localize('cancelVsixInstall', "Cancelled installing extension '{0}'.", id)); } else { throw error; } diff --git a/src/vs/code/node/shellEnv.ts b/src/vs/code/node/shellEnv.ts index 75c4863068e..baabaf7b959 100644 --- a/src/vs/code/node/shellEnv.ts +++ b/src/vs/code/node/shellEnv.ts @@ -7,11 +7,16 @@ import * as cp from 'child_process'; import { assign } from 'vs/base/common/objects'; import { generateUuid } from 'vs/base/common/uuid'; import { isWindows } from 'vs/base/common/platform'; +import { ILogService } from 'vs/platform/log/common/log'; -function getUnixShellEnvironment(): Promise { +function getUnixShellEnvironment(logService: ILogService): Promise { const promise = new Promise((resolve, reject) => { const runAsNode = process.env['ELECTRON_RUN_AS_NODE']; + logService.trace('getUnixShellEnvironment#runAsNode', runAsNode); + const noAttach = process.env['ELECTRON_NO_ATTACH_CONSOLE']; + logService.trace('getUnixShellEnvironment#noAttach', noAttach); + const mark = generateUuid().replace(/-/g, '').substr(0, 12); const regex = new RegExp(mark + '(.*)' + mark); @@ -21,6 +26,9 @@ function getUnixShellEnvironment(): Promise { }); const command = `'${process.execPath}' -p '"${mark}" + JSON.stringify(process.env) + "${mark}"'`; + logService.trace('getUnixShellEnvironment#env', env); + logService.trace('getUnixShellEnvironment#spawn', command); + const child = cp.spawn(process.env.SHELL!, ['-ilc', command], { detached: true, stdio: ['ignore', 'pipe', process.stderr], @@ -37,6 +45,8 @@ function getUnixShellEnvironment(): Promise { } const raw = Buffer.concat(buffers).toString('utf8'); + logService.trace('getUnixShellEnvironment#raw', raw); + const match = regex.exec(raw); const rawStripped = match ? match[1] : '{}'; @@ -58,8 +68,10 @@ function getUnixShellEnvironment(): Promise { // https://github.com/Microsoft/vscode/issues/22593#issuecomment-336050758 delete env['XDG_RUNTIME_DIR']; + logService.trace('getUnixShellEnvironment#result', env); resolve(env); } catch (err) { + logService.error('getUnixShellEnvironment#error', err); reject(err); } }); @@ -77,14 +89,17 @@ let _shellEnv: Promise; * This should only be done when Code itself is not launched * from within a shell. */ -export function getShellEnvironment(): Promise { +export function getShellEnvironment(logService: ILogService): Promise { if (_shellEnv === undefined) { if (isWindows) { + logService.trace('getShellEnvironment: runing on windows, skipping'); _shellEnv = Promise.resolve({}); } else if (process.env['VSCODE_CLI'] === '1') { + logService.trace('getShellEnvironment: runing on CLI, skipping'); _shellEnv = Promise.resolve({}); } else { - _shellEnv = getUnixShellEnvironment(); + logService.trace('getShellEnvironment: running on Unix'); + _shellEnv = getUnixShellEnvironment(logService); } } diff --git a/src/vs/code/test/node/windowsFinder.test.ts b/src/vs/code/test/node/windowsFinder.test.ts index e18521facc4..7327dd74013 100644 --- a/src/vs/code/test/node/windowsFinder.test.ts +++ b/src/vs/code/test/node/windowsFinder.test.ts @@ -18,13 +18,15 @@ const testWorkspace: IWorkspaceIdentifier = { configPath: URI.file(path.join(fixturesFolder, 'workspaces.json')) }; +const testWorkspaceFolders = toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }], testWorkspace.configPath); + function options(custom?: Partial>): IBestWindowOrFolderOptions { return { windows: [], newWindow: false, context: OpenContext.CLI, codeSettingsFolder: '_vscode', - localWorkspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }]) } : null!; }, + localWorkspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: testWorkspaceFolders } : null; }, ...custom }; } diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index f1abe319f58..e72acd359f3 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -301,13 +301,13 @@ export namespace CoreNavigationCommands { export const MoveTo: CoreEditorCommand = registerEditorCommand(new BaseMoveToCommand({ id: '_moveTo', inSelectionMode: false, - precondition: null + precondition: undefined })); export const MoveToSelect: CoreEditorCommand = registerEditorCommand(new BaseMoveToCommand({ id: '_moveToSelect', inSelectionMode: true, - precondition: null + precondition: undefined })); abstract class ColumnSelectCommand extends CoreEditorCommand { @@ -330,7 +330,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'columnSelect', - precondition: null + precondition: undefined }); } @@ -354,7 +354,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'cursorColumnSelectLeft', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -373,7 +373,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'cursorColumnSelectRight', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -405,7 +405,7 @@ export namespace CoreNavigationCommands { export const CursorColumnSelectUp: CoreEditorCommand = registerEditorCommand(new ColumnSelectUpCommand({ isPaged: false, id: 'cursorColumnSelectUp', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -417,7 +417,7 @@ export namespace CoreNavigationCommands { export const CursorColumnSelectPageUp: CoreEditorCommand = registerEditorCommand(new ColumnSelectUpCommand({ isPaged: true, id: 'cursorColumnSelectPageUp', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -443,7 +443,7 @@ export namespace CoreNavigationCommands { export const CursorColumnSelectDown: CoreEditorCommand = registerEditorCommand(new ColumnSelectDownCommand({ isPaged: false, id: 'cursorColumnSelectDown', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -455,7 +455,7 @@ export namespace CoreNavigationCommands { export const CursorColumnSelectPageDown: CoreEditorCommand = registerEditorCommand(new ColumnSelectDownCommand({ isPaged: true, id: 'cursorColumnSelectPageDown', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -468,7 +468,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'cursorMove', - precondition: null, + precondition: undefined, description: CursorMove_.description }); } @@ -531,7 +531,7 @@ export namespace CoreNavigationCommands { value: 1 }, id: 'cursorLeft', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -548,7 +548,7 @@ export namespace CoreNavigationCommands { value: 1 }, id: 'cursorLeftSelect', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -564,7 +564,7 @@ export namespace CoreNavigationCommands { value: 1 }, id: 'cursorRight', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -581,7 +581,7 @@ export namespace CoreNavigationCommands { value: 1 }, id: 'cursorRightSelect', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -597,7 +597,7 @@ export namespace CoreNavigationCommands { value: 1 }, id: 'cursorUp', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -614,7 +614,7 @@ export namespace CoreNavigationCommands { value: 1 }, id: 'cursorUpSelect', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -633,7 +633,7 @@ export namespace CoreNavigationCommands { value: Constants.PAGE_SIZE_MARKER }, id: 'cursorPageUp', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -649,7 +649,7 @@ export namespace CoreNavigationCommands { value: Constants.PAGE_SIZE_MARKER }, id: 'cursorPageUpSelect', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -665,7 +665,7 @@ export namespace CoreNavigationCommands { value: 1 }, id: 'cursorDown', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -682,7 +682,7 @@ export namespace CoreNavigationCommands { value: 1 }, id: 'cursorDownSelect', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -701,7 +701,7 @@ export namespace CoreNavigationCommands { value: Constants.PAGE_SIZE_MARKER }, id: 'cursorPageDown', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -717,7 +717,7 @@ export namespace CoreNavigationCommands { value: Constants.PAGE_SIZE_MARKER }, id: 'cursorPageDownSelect', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -729,7 +729,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'createCursor', - precondition: null + precondition: undefined }); } @@ -790,7 +790,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: '_lastCursorMoveToSelect', - precondition: null + precondition: undefined }); } @@ -835,7 +835,7 @@ export namespace CoreNavigationCommands { export const CursorHome: CoreEditorCommand = registerEditorCommand(new HomeCommand({ inSelectionMode: false, id: 'cursorHome', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -847,7 +847,7 @@ export namespace CoreNavigationCommands { export const CursorHomeSelect: CoreEditorCommand = registerEditorCommand(new HomeCommand({ inSelectionMode: true, id: 'cursorHomeSelect', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -860,7 +860,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'cursorLineStart', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -914,7 +914,7 @@ export namespace CoreNavigationCommands { export const CursorEnd: CoreEditorCommand = registerEditorCommand(new EndCommand({ inSelectionMode: false, id: 'cursorEnd', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -926,7 +926,7 @@ export namespace CoreNavigationCommands { export const CursorEndSelect: CoreEditorCommand = registerEditorCommand(new EndCommand({ inSelectionMode: true, id: 'cursorEndSelect', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -939,7 +939,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'cursorLineEnd', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -994,7 +994,7 @@ export namespace CoreNavigationCommands { export const CursorTop: CoreEditorCommand = registerEditorCommand(new TopCommand({ inSelectionMode: false, id: 'cursorTop', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -1006,7 +1006,7 @@ export namespace CoreNavigationCommands { export const CursorTopSelect: CoreEditorCommand = registerEditorCommand(new TopCommand({ inSelectionMode: true, id: 'cursorTopSelect', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -1038,7 +1038,7 @@ export namespace CoreNavigationCommands { export const CursorBottom: CoreEditorCommand = registerEditorCommand(new BottomCommand({ inSelectionMode: false, id: 'cursorBottom', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -1050,7 +1050,7 @@ export namespace CoreNavigationCommands { export const CursorBottomSelect: CoreEditorCommand = registerEditorCommand(new BottomCommand({ inSelectionMode: true, id: 'cursorBottomSelect', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -1063,7 +1063,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'editorScroll', - precondition: null, + precondition: undefined, description: EditorScroll_.description }); } @@ -1134,7 +1134,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'scrollLineUp', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -1159,7 +1159,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'scrollPageUp', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -1185,7 +1185,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'scrollLineDown', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -1210,7 +1210,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'scrollPageDown', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -1257,20 +1257,20 @@ export namespace CoreNavigationCommands { export const WordSelect: CoreEditorCommand = registerEditorCommand(new WordCommand({ inSelectionMode: false, id: '_wordSelect', - precondition: null + precondition: undefined })); export const WordSelectDrag: CoreEditorCommand = registerEditorCommand(new WordCommand({ inSelectionMode: true, id: '_wordSelectDrag', - precondition: null + precondition: undefined })); export const LastCursorWordSelect: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { constructor() { super({ id: 'lastCursorWordSelect', - precondition: null + precondition: undefined }); } @@ -1317,13 +1317,13 @@ export namespace CoreNavigationCommands { export const LineSelect: CoreEditorCommand = registerEditorCommand(new LineCommand({ inSelectionMode: false, id: '_lineSelect', - precondition: null + precondition: undefined })); export const LineSelectDrag: CoreEditorCommand = registerEditorCommand(new LineCommand({ inSelectionMode: true, id: '_lineSelectDrag', - precondition: null + precondition: undefined })); class LastCursorLineCommand extends CoreEditorCommand { @@ -1353,20 +1353,20 @@ export namespace CoreNavigationCommands { export const LastCursorLineSelect: CoreEditorCommand = registerEditorCommand(new LastCursorLineCommand({ inSelectionMode: false, id: 'lastCursorLineSelect', - precondition: null + precondition: undefined })); export const LastCursorLineSelectDrag: CoreEditorCommand = registerEditorCommand(new LastCursorLineCommand({ inSelectionMode: true, id: 'lastCursorLineSelectDrag', - precondition: null + precondition: undefined })); export const ExpandLineSelection: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand { constructor() { super({ id: 'expandLineSelection', - precondition: null, + precondition: undefined, kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, @@ -1445,7 +1445,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'revealLine', - precondition: null, + precondition: undefined, description: RevealLine_.description }); } @@ -1493,7 +1493,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'selectAll', - precondition: null + precondition: undefined }); } @@ -1513,7 +1513,7 @@ export namespace CoreNavigationCommands { constructor() { super({ id: 'setSelection', - precondition: null + precondition: undefined }); } @@ -1728,7 +1728,7 @@ class EditorHandlerCommand extends Command { constructor(id: string, handlerId: string, description?: ICommandHandlerDescription) { super({ id: id, - precondition: null, + precondition: undefined, description: description }); this._handlerId = handlerId; diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index ac0a7331d21..640002cbd26 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -40,17 +40,17 @@ export interface ICommandMenubarOptions { } export interface ICommandOptions { id: string; - precondition: ContextKeyExpr | null; - kbOpts?: ICommandKeybindingsOptions | null; + precondition: ContextKeyExpr | undefined; + kbOpts?: ICommandKeybindingsOptions; description?: ICommandHandlerDescription; menubarOpts?: ICommandMenubarOptions; } export abstract class Command { public readonly id: string; - public readonly precondition: ContextKeyExpr | null; - private readonly _kbOpts: ICommandKeybindingsOptions | null | undefined; - private readonly _menubarOpts: ICommandMenubarOptions | null | undefined; - private readonly _description: ICommandHandlerDescription | null | undefined; + public readonly precondition: ContextKeyExpr | undefined; + private readonly _kbOpts: ICommandKeybindingsOptions | undefined; + private readonly _menubarOpts: ICommandMenubarOptions | undefined; + private readonly _description: ICommandHandlerDescription | undefined; constructor(opts: ICommandOptions) { this.id = opts.id; diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 1c4f38ef491..c175034f969 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -55,7 +55,7 @@ export class OpenerService implements IOpenerService { if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https) || equalsIgnoreCase(scheme, Schemas.mailto)) { // open http or default mail application - dom.windowOpenNoOpener(resource.toString(true)); + dom.windowOpenNoOpener(encodeURI(resource.toString(true))); return Promise.resolve(true); } else if (equalsIgnoreCase(scheme, Schemas.command)) { diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 48a268b0bd6..ebc34ca227f 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -13,7 +13,6 @@ import { Selection } from 'vs/editor/common/core/selection'; import { IModelContentChange, IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent, ModelRawContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { SearchData } from 'vs/editor/common/model/textModelSearch'; import { LanguageId, LanguageIdentifier, FormattingOptions } from 'vs/editor/common/modes'; -import { ITextSnapshot } from 'vs/platform/files/common/files'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; /** @@ -460,6 +459,17 @@ export interface IActiveIndentGuideInfo { indent: number; } +/** + * Text snapshot that works like an iterator. + * Will try to return chunks of roughly ~64KB size. + * Will return null when finished. + * + * @internal + */ +export interface ITextSnapshot { + read(): string | null; +} + /** * A model. */ diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts index 7b7439f32c4..04de0bc01fc 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts @@ -6,10 +6,9 @@ import { CharCode } from 'vs/base/common/charCode'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { FindMatch } from 'vs/editor/common/model'; +import { FindMatch, ITextSnapshot } from 'vs/editor/common/model'; import { NodeColor, SENTINEL, TreeNode, fixInsert, leftest, rbDelete, righttest, updateTreeMetadata } from 'vs/editor/common/model/pieceTreeTextBuffer/rbTreeBase'; import { SearchData, Searcher, createFindMatch, isValidMatch } from 'vs/editor/common/model/textModelSearch'; -import { ITextSnapshot } from 'vs/platform/files/common/files'; // const lfRegex = new RegExp(/\r\n|\r|\n/g); export const AverageBufferSize = 65535; diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts index 1424533f32f..b6d05389439 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts @@ -6,10 +6,9 @@ import * as strings from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { ApplyEditsResult, EndOfLinePreference, FindMatch, IIdentifiedSingleEditOperation, IInternalModelContentChange, ISingleEditOperationIdentifier, ITextBuffer } from 'vs/editor/common/model'; +import { ApplyEditsResult, EndOfLinePreference, FindMatch, IIdentifiedSingleEditOperation, IInternalModelContentChange, ISingleEditOperationIdentifier, ITextBuffer, ITextSnapshot } from 'vs/editor/common/model'; import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase'; import { SearchData } from 'vs/editor/common/model/textModelSearch'; -import { ITextSnapshot } from 'vs/platform/files/common/files'; export interface IValidatedEditOperation { sortIndex: number; diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index c9e1c4b1652..2a640ac9d01 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -30,9 +30,9 @@ import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageCo import { NULL_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/nullMode'; import { ignoreBracketsInToken } from 'vs/editor/common/modes/supports'; import { BracketsUtils, RichEditBracket, RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets'; -import { IStringStream, ITextSnapshot } from 'vs/platform/files/common/files'; import { ITheme, ThemeColor } from 'vs/platform/theme/common/themeService'; import { withUndefinedAsNull } from 'vs/base/common/types'; +import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer'; const CHEAP_TOKENIZATION_LENGTH_LIMIT = 2048; @@ -46,36 +46,54 @@ export function createTextBufferFactory(text: string): model.ITextBufferFactory return builder.finish(); } -export function createTextBufferFactoryFromStream(stream: IStringStream, filter?: (chunk: string) => string): Promise { - return new Promise((c, e) => { - let done = false; - let builder = createTextBufferBuilder(); +interface ITextStream { + on(event: 'data', callback: (data: string) => void): void; + on(event: 'error', callback: (err: Error) => void): void; + on(event: 'end', callback: () => void): void; + on(event: string, callback: any): void; +} + +export function createTextBufferFactoryFromStream(stream: ITextStream, filter?: (chunk: string) => string, validator?: (chunk: string) => Error | undefined): Promise; +export function createTextBufferFactoryFromStream(stream: VSBufferReadableStream, filter?: (chunk: VSBuffer) => VSBuffer, validator?: (chunk: VSBuffer) => Error | undefined): Promise; +export function createTextBufferFactoryFromStream(stream: ITextStream | VSBufferReadableStream, filter?: (chunk: any) => string | VSBuffer, validator?: (chunk: any) => Error | undefined): Promise { + return new Promise((resolve, reject) => { + const builder = createTextBufferBuilder(); + + let done = false; + + stream.on('data', (chunk: string | VSBuffer) => { + if (validator) { + const error = validator(chunk); + if (error) { + done = true; + reject(error); + } + } - stream.on('data', (chunk) => { if (filter) { chunk = filter(chunk); } - builder.acceptChunk(chunk); + builder.acceptChunk((typeof chunk === 'string') ? chunk : chunk.toString()); }); stream.on('error', (error) => { if (!done) { done = true; - e(error); + reject(error); } }); stream.on('end', () => { if (!done) { done = true; - c(builder.finish()); + resolve(builder.finish()); } }); }); } -export function createTextBufferFactoryFromSnapshot(snapshot: ITextSnapshot): model.ITextBufferFactory { +export function createTextBufferFactoryFromSnapshot(snapshot: model.ITextSnapshot): model.ITextBufferFactory { let builder = createTextBufferBuilder(); let chunk: string | null; @@ -111,12 +129,12 @@ function singleLetter(result: number): string { const LIMIT_FIND_COUNT = 999; export const LONG_LINE_BOUNDARY = 10000; -class TextModelSnapshot implements ITextSnapshot { +class TextModelSnapshot implements model.ITextSnapshot { - private readonly _source: ITextSnapshot; + private readonly _source: model.ITextSnapshot; private _eos: boolean; - constructor(source: ITextSnapshot) { + constructor(source: model.ITextSnapshot) { this._source = source; this._eos = false; } @@ -731,7 +749,7 @@ export class TextModel extends Disposable implements model.ITextModel { return fullModelValue; } - public createSnapshot(preserveBOM: boolean = false): ITextSnapshot { + public createSnapshot(preserveBOM: boolean = false): model.ITextSnapshot { return new TextModelSnapshot(this._buffer.createSnapshot(preserveBOM)); } diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 6b7fee2665f..082dcf810f6 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1211,6 +1211,17 @@ export interface Command { arguments?: any[]; } +/** + * @internal + */ +export interface CommentThreadTemplate { + controllerHandle: number; + label: string; + acceptInputCommand?: Command; + additionalCommands?: Command[]; + deleteCommand?: Command; +} + /** * @internal */ @@ -1220,6 +1231,7 @@ export interface CommentInfo { commentingRanges?: (IRange[] | CommentingRanges); reply?: Command; draftMode?: DraftMode; + template?: CommentThreadTemplate; } /** @@ -1270,6 +1282,7 @@ export interface CommentInput { */ export interface CommentThread2 { commentThreadHandle: number; + controllerHandle: number; extensionId?: string; threadId: string | null; resource: string | null; @@ -1288,6 +1301,7 @@ export interface CommentThread2 { onDidChangeRange: Event; onDidChangeLabel: Event; onDidChangeCollasibleState: Event; + isDisposed: boolean; } /** @@ -1297,8 +1311,7 @@ export interface CommentThread2 { export interface CommentingRanges { readonly resource: URI; ranges: IRange[]; - newCommentThreadCommand?: Command; - newCommentThreadCallback?: (uri: UriComponents, range: IRange) => Promise; + newCommentThreadCallback?: (uri: UriComponents, range: IRange) => Promise; } /** @@ -1312,6 +1325,7 @@ export interface CommentThread { comments: Comment[] | undefined; collapsibleState?: CommentThreadCollapsibleState; reply?: Command; + isDisposed?: boolean; } /** diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index acbc2fe7795..a4c9dd6e8fd 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -399,7 +399,7 @@ export abstract class BaseEditorSimpleWorker { // ---- BEGIN minimal edits --------------------------------------------------------------- - private static readonly _diffLimit = 10000; + private static readonly _diffLimit = 100000; public computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[]): Promise { const model = this._getModel(modelUrl); @@ -432,7 +432,7 @@ export abstract class BaseEditorSimpleWorker { } const original = model.getValueInRange(range); - text = text!.replace(/\r\n|\n|\r/g, model.eol); + text = text.replace(/\r\n|\n|\r/g, model.eol); if (original === text) { // noop diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index 467590c7640..d42a7f7782e 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -21,6 +21,8 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { regExpFlags } from 'vs/base/common/strings'; import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { ILogService } from 'vs/platform/log/common/log'; +import { StopWatch } from 'vs/base/common/stopwatch'; /** * Stop syncing a model to the worker if it was not needed for 1 min. @@ -48,14 +50,16 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker private readonly _modelService: IModelService; private readonly _workerManager: WorkerManager; - + private readonly _logService: ILogService; constructor( @IModelService modelService: IModelService, - @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @ILogService logService: ILogService ) { super(); this._modelService = modelService; this._workerManager = this._register(new WorkerManager(this._modelService)); + this._logService = logService; // todo@joh make sure this happens only once this._register(modes.LinkProviderRegistry.register('*', { @@ -96,7 +100,10 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker if (!canSyncModel(this._modelService, resource)) { return Promise.resolve(edits); // File too large } - return this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits)); + const sw = StopWatch.create(true); + const result = this._workerManager.withWorker().then(client => client.computeMoreMinimalEdits(resource, edits)); + result.finally(() => this._logService.trace('FORMAT#computeMoreMinimalEdits', resource.toString(true), sw.elapsed())); + return result; } else { return Promise.resolve(undefined); @@ -241,7 +248,7 @@ class EditorModelManager extends Disposable { super.dispose(); } - public esureSyncedResources(resources: URI[]): void { + public ensureSyncedResources(resources: URI[]): void { for (const resource of resources) { let resourceStr = resource.toString(); @@ -380,7 +387,7 @@ export class EditorWorkerClient extends Disposable { protected _withSyncedResources(resources: URI[]): Promise { return this._getProxy().then((proxy) => { - this._getOrCreateModelManager(proxy).esureSyncedResources(resources); + this._getOrCreateModelManager(proxy).ensureSyncedResources(resources); return proxy; }); } diff --git a/src/vs/editor/common/services/getIconClasses.ts b/src/vs/editor/common/services/getIconClasses.ts index ca49de2528f..af148c324af 100644 --- a/src/vs/editor/common/services/getIconClasses.ts +++ b/src/vs/editor/common/services/getIconClasses.ts @@ -19,14 +19,11 @@ export function getIconClasses(modelService: IModelService, modeService: IModeSe // Get the path and name of the resource. For data-URIs, we need to parse specially let name: string | undefined; - let path: string | undefined; if (resource.scheme === Schemas.data) { const metadata = DataUri.parseMetaData(resource); name = metadata.get(DataUri.META_DATA_LABEL); - path = name; } else { name = cssEscape(basenameOrAuthority(resource).toLowerCase()); - path = resource.path.toLowerCase(); } // Folders @@ -47,46 +44,60 @@ export function getIconClasses(modelService: IModelService, modeService: IModeSe classes.push(`ext-file-icon`); // extra segment to increase file-ext score } - // Configured Language - let configuredLangId: string | null = getConfiguredLangId(modelService, modeService, resource); - configuredLangId = configuredLangId || (path ? modeService.getModeIdByFilepathOrFirstLine(path) : null); - if (configuredLangId) { - classes.push(`${cssEscape(configuredLangId)}-lang-file-icon`); + // Detected Mode + const detectedModeId = detectModeId(modelService, modeService, resource); + if (detectedModeId) { + classes.push(`${cssEscape(detectedModeId)}-lang-file-icon`); } } } return classes; } -export function getConfiguredLangId(modelService: IModelService, modeService: IModeService, resource: uri): string | null { - let configuredLangId: string | null = null; - if (resource) { - let modeId: string | null = null; +export function detectModeId(modelService: IModelService, modeService: IModeService, resource: uri): string | null { + if (!resource) { + return null; // we need a resource at least + } - // Data URI: check for encoded metadata - if (resource.scheme === Schemas.data) { - const metadata = DataUri.parseMetaData(resource); - const mime = metadata.get(DataUri.META_DATA_MIME); + let modeId: string | null = null; - if (mime) { - modeId = modeService.getModeId(mime); - } - } + // Data URI: check for encoded metadata + if (resource.scheme === Schemas.data) { + const metadata = DataUri.parseMetaData(resource); + const mime = metadata.get(DataUri.META_DATA_MIME); - // Any other URI: check for model if existing - else { - const model = modelService.getModel(resource); - if (model) { - modeId = model.getLanguageIdentifier().language; - } - } - - if (modeId && modeId !== PLAINTEXT_MODE_ID) { - configuredLangId = modeId; // only take if the mode is specific (aka no just plain text) + if (mime) { + modeId = modeService.getModeId(mime); } } - return configuredLangId; + // Any other URI: check for model if existing + else { + const model = modelService.getModel(resource); + if (model) { + modeId = model.getModeId(); + } + } + + // only take if the mode is specific (aka no just plain text) + if (modeId && modeId !== PLAINTEXT_MODE_ID) { + return modeId; + } + + // otherwise fallback to path based detection + let path: string | undefined; + if (resource.scheme === Schemas.data) { + const metadata = DataUri.parseMetaData(resource); + path = metadata.get(DataUri.META_DATA_LABEL); + } else { + path = resource.path.toLowerCase(); + } + + if (path) { + return modeService.getModeIdByFilepathOrFirstLine(path); + } + + return null; // finally - we do not know the mode id } export function cssEscape(val: string): string { diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index 442813334d5..958469e16f1 100644 --- a/src/vs/editor/common/services/resolverService.ts +++ b/src/vs/editor/common/services/resolverService.ts @@ -5,7 +5,7 @@ import { IDisposable, IReference } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { ITextModel } from 'vs/editor/common/model'; +import { ITextModel, ITextSnapshot } from 'vs/editor/common/model'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -46,6 +46,12 @@ export interface ITextEditorModel extends IEditorModel { */ readonly textEditorModel: ITextModel | null; + /** + * Creates a snapshot of the model's contents. + */ + createSnapshot(this: IResolvedTextEditorModel): ITextSnapshot; + createSnapshot(this: ITextEditorModel): ITextSnapshot | null; + isReadonly(): boolean; } diff --git a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts index 672b156378a..675fa71bd8c 100644 --- a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts @@ -31,7 +31,7 @@ class JumpToBracketAction extends EditorAction { id: 'editor.action.jumpToBracket', label: nls.localize('smartSelect.jumpBracket', "Go to Bracket"), alias: 'Go to Bracket', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKSLASH, @@ -55,7 +55,7 @@ class SelectToBracketAction extends EditorAction { id: 'editor.action.selectToBracket', label: nls.localize('smartSelect.selectToBracket', "Select to Bracket"), alias: 'Select to Bracket', - precondition: null + precondition: undefined }); } diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 990be3a2987..102f1b019dc 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -59,7 +59,7 @@ abstract class ExecCommandAction extends EditorAction { class ExecCommandCutAction extends ExecCommandAction { constructor() { - let kbOpts: ICommandKeybindingsOptions | null = { + let kbOpts: ICommandKeybindingsOptions | undefined = { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.KEY_X, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_X, secondary: [KeyMod.Shift | KeyCode.Delete] }, @@ -68,7 +68,7 @@ class ExecCommandCutAction extends ExecCommandAction { // Do not bind cut keybindings in the browser, // since browsers do that for us and it avoids security prompts if (!platform.isNative) { - kbOpts = null; + kbOpts = undefined; } super('cut', { id: 'editor.action.clipboardCutAction', @@ -107,7 +107,7 @@ class ExecCommandCutAction extends ExecCommandAction { class ExecCommandCopyAction extends ExecCommandAction { constructor() { - let kbOpts: ICommandKeybindingsOptions | null = { + let kbOpts: ICommandKeybindingsOptions | undefined = { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.KEY_C, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_C, secondary: [KeyMod.CtrlCmd | KeyCode.Insert] }, @@ -116,14 +116,14 @@ class ExecCommandCopyAction extends ExecCommandAction { // Do not bind copy keybindings in the browser, // since browsers do that for us and it avoids security prompts if (!platform.isNative) { - kbOpts = null; + kbOpts = undefined; } super('copy', { id: 'editor.action.clipboardCopyAction', label: nls.localize('actions.clipboard.copyLabel', "Copy"), alias: 'Copy', - precondition: null, + precondition: undefined, kbOpts: kbOpts, menuOpts: { group: CLIPBOARD_CONTEXT_MENU_GROUP, @@ -162,7 +162,7 @@ class ExecCommandCopyAction extends ExecCommandAction { class ExecCommandPasteAction extends ExecCommandAction { constructor() { - let kbOpts: ICommandKeybindingsOptions | null = { + let kbOpts: ICommandKeybindingsOptions | undefined = { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.KEY_V, win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.Shift | KeyCode.Insert] }, @@ -171,7 +171,7 @@ class ExecCommandPasteAction extends ExecCommandAction { // Do not bind paste keybindings in the browser, // since browsers do that for us and it avoids security prompts if (!platform.isNative) { - kbOpts = null; + kbOpts = undefined; } super('paste', { @@ -201,7 +201,7 @@ class ExecCommandCopyWithSyntaxHighlightingAction extends ExecCommandAction { id: 'editor.action.clipboardCopyWithSyntaxHighlightingAction', label: nls.localize('actions.clipboard.copyWithSyntaxHighlightingLabel', "Copy With Syntax Highlighting"), alias: 'Copy With Syntax Highlighting', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: 0, diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index 57fab087807..854af117f7d 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -34,8 +34,8 @@ export class CodeActionSet { public readonly actions: readonly CodeAction[]; - public constructor(actions: CodeAction[]) { - this.actions = mergeSort(actions, CodeActionSet.codeActionsComparator); + public constructor(actions: readonly CodeAction[]) { + this.actions = mergeSort([...actions], CodeActionSet.codeActionsComparator); } public get hasAutoFix() { diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index 28e7a707b03..47180269ef6 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -176,6 +176,7 @@ function showCodeActionsForEditorSelection( return; } + MessageController.get(editor).closeMessage(); const pos = editor.getPosition(); controller.triggerFromEditorSelection(filter, autoApply).then(codeActions => { if (!codeActions || !codeActions.actions.length) { diff --git a/src/vs/editor/contrib/codeAction/codeActionTrigger.ts b/src/vs/editor/contrib/codeAction/codeActionTrigger.ts index 15e90604ed6..f0fb3b3e6bf 100644 --- a/src/vs/editor/contrib/codeAction/codeActionTrigger.ts +++ b/src/vs/editor/contrib/codeAction/codeActionTrigger.ts @@ -20,8 +20,12 @@ export class CodeActionKind { public readonly value: string ) { } + public equals(other: CodeActionKind): boolean { + return this.value === other.value; + } + public contains(other: CodeActionKind): boolean { - return this.value === other.value || startsWith(other.value, this.value + CodeActionKind.sep); + return this.equals(other) || startsWith(other.value, this.value + CodeActionKind.sep); } public intersects(other: CodeActionKind): boolean { diff --git a/src/vs/editor/contrib/codelens/codeLensCache.ts b/src/vs/editor/contrib/codelens/codeLensCache.ts index 0119def5207..6027dec365d 100644 --- a/src/vs/editor/contrib/codelens/codeLensCache.ts +++ b/src/vs/editor/contrib/codelens/codeLensCache.ts @@ -34,7 +34,7 @@ class CacheItem { ) { } } -class CodeLensCache implements ICodeLensCache { +export class CodeLensCache implements ICodeLensCache { _serviceBrand: any; diff --git a/src/vs/editor/contrib/colorPicker/colorDetector.ts b/src/vs/editor/contrib/colorPicker/colorDetector.ts index 1de0bda0312..e20d0d532a3 100644 --- a/src/vs/editor/contrib/colorPicker/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/colorDetector.ts @@ -76,9 +76,9 @@ export class ColorDetector implements IEditorContribution { } const languageId = model.getLanguageIdentifier(); // handle deprecated settings. [languageId].colorDecorators.enable - let deprecatedConfig = this._configurationService.getValue(languageId.language); + const deprecatedConfig = this._configurationService.getValue<{}>(languageId.language); if (deprecatedConfig) { - let colorDecorators = deprecatedConfig['colorDecorators']; // deprecatedConfig.valueOf('.colorDecorators.enable'); + const colorDecorators = deprecatedConfig['colorDecorators']; // deprecatedConfig.valueOf('.colorDecorators.enable'); if (colorDecorators && colorDecorators['enable'] !== undefined && !colorDecorators['enable']) { return colorDecorators['enable']; } diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index 8fa07282fd8..3267cf0c4cd 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { ActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionViewItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { IAction } from 'vs/base/common/actions'; import { KeyCode, KeyMod, ResolvedKeybinding } from 'vs/base/common/keyCodes'; @@ -176,18 +176,18 @@ export class ContextMenuController implements IEditorContribution { getActions: () => actions, - getActionItem: (action) => { + getActionViewItem: (action) => { const keybinding = this._keybindingFor(action); if (keybinding) { - return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel(), isMenu: true }); + return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel(), isMenu: true }); } - const customActionItem = action; - if (typeof customActionItem.getActionItem === 'function') { - return customActionItem.getActionItem(); + const customActionViewItem = action; + if (typeof customActionViewItem.getActionViewItem === 'function') { + return customActionViewItem.getActionViewItem(); } - return new ActionItem(action, action, { icon: true, label: true, isMenu: true }); + return new ActionViewItem(action, action, { icon: true, label: true, isMenu: true }); }, getKeyBinding: (action): ResolvedKeybinding | undefined => { @@ -228,7 +228,7 @@ class ShowContextMenu extends EditorAction { id: 'editor.action.showContextMenu', label: nls.localize('action.showContextMenu.label', "Show Editor Context Menu"), alias: 'Show Editor Context Menu', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.Shift | KeyCode.F10, diff --git a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts index 4b37afc51aa..d543253b099 100644 --- a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts +++ b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts @@ -119,7 +119,7 @@ export class CursorUndo extends EditorAction { id: 'cursorUndo', label: nls.localize('cursor.undo', "Soft Undo"), alias: 'Soft Undo', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.KEY_U, diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index d51962cdfef..3b12e6e1c5d 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -422,7 +422,7 @@ export class StartFindAction extends EditorAction { id: FIND_IDS.StartFindAction, label: nls.localize('startFindAction', "Find"), alias: 'Find', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: null, primary: KeyMod.CtrlCmd | KeyCode.KEY_F, @@ -459,7 +459,7 @@ export class StartFindWithSelectionAction extends EditorAction { id: FIND_IDS.StartFindWithSelection, label: nls.localize('startFindWithSelectionAction', "Find With Selection"), alias: 'Find With Selection', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: null, primary: 0, @@ -513,7 +513,7 @@ export class NextMatchFindAction extends MatchFindAction { id: FIND_IDS.NextMatchFindAction, label: nls.localize('findNextMatchAction', "Find Next"), alias: 'Find Next', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyCode.F3, @@ -535,7 +535,7 @@ export class PreviousMatchFindAction extends MatchFindAction { id: FIND_IDS.PreviousMatchFindAction, label: nls.localize('findPreviousMatchAction', "Find Previous"), alias: 'Find Previous', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.Shift | KeyCode.F3, @@ -583,7 +583,7 @@ export class NextSelectionMatchFindAction extends SelectionMatchFindAction { id: FIND_IDS.NextSelectionMatchFindAction, label: nls.localize('nextSelectionMatchFindAction', "Find Next Selection"), alias: 'Find Next Selection', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.CtrlCmd | KeyCode.F3, @@ -604,7 +604,7 @@ export class PreviousSelectionMatchFindAction extends SelectionMatchFindAction { id: FIND_IDS.PreviousSelectionMatchFindAction, label: nls.localize('previousSelectionMatchFindAction', "Find Previous Selection"), alias: 'Find Previous Selection', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F3, @@ -625,7 +625,7 @@ export class StartFindReplaceAction extends EditorAction { id: FIND_IDS.StartFindReplaceAction, label: nls.localize('startReplace', "Replace"), alias: 'Replace', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: null, primary: KeyMod.CtrlCmd | KeyCode.KEY_H, @@ -704,7 +704,7 @@ registerEditorCommand(new FindCommand({ registerEditorCommand(new FindCommand({ id: FIND_IDS.ToggleCaseSensitiveCommand, - precondition: null, + precondition: undefined, handler: x => x.toggleCaseSensitive(), kbOpts: { weight: KeybindingWeight.EditorContrib + 5, @@ -718,7 +718,7 @@ registerEditorCommand(new FindCommand({ registerEditorCommand(new FindCommand({ id: FIND_IDS.ToggleWholeWordCommand, - precondition: null, + precondition: undefined, handler: x => x.toggleWholeWords(), kbOpts: { weight: KeybindingWeight.EditorContrib + 5, @@ -732,7 +732,7 @@ registerEditorCommand(new FindCommand({ registerEditorCommand(new FindCommand({ id: FIND_IDS.ToggleRegexCommand, - precondition: null, + precondition: undefined, handler: x => x.toggleRegex(), kbOpts: { weight: KeybindingWeight.EditorContrib + 5, @@ -746,7 +746,7 @@ registerEditorCommand(new FindCommand({ registerEditorCommand(new FindCommand({ id: FIND_IDS.ToggleSearchScopeCommand, - precondition: null, + precondition: undefined, handler: x => x.toggleSearchScope(), kbOpts: { weight: KeybindingWeight.EditorContrib + 5, diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 24d64b50225..389ef232a1b 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -503,7 +503,7 @@ class UnfoldAction extends FoldingAction { id: 'editor.unfold', label: nls.localize('unfoldAction.label', "Unfold"), alias: 'Unfold', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET, @@ -567,7 +567,7 @@ class UnFoldRecursivelyAction extends FoldingAction { id: 'editor.unfoldRecursively', label: nls.localize('unFoldRecursivelyAction.label', "Unfold Recursively"), alias: 'Unfold Recursively', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_CLOSE_SQUARE_BRACKET), @@ -588,7 +588,7 @@ class FoldAction extends FoldingAction { id: 'editor.fold', label: nls.localize('foldAction.label', "Fold"), alias: 'Fold', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET, @@ -652,7 +652,7 @@ class FoldRecursivelyAction extends FoldingAction { id: 'editor.foldRecursively', label: nls.localize('foldRecursivelyAction.label', "Fold Recursively"), alias: 'Fold Recursively', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_OPEN_SQUARE_BRACKET), @@ -674,7 +674,7 @@ class FoldAllBlockCommentsAction extends FoldingAction { id: 'editor.foldAllBlockComments', label: nls.localize('foldAllBlockComments.label', "Fold All Block Comments"), alias: 'Fold All Block Comments', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_SLASH), @@ -707,7 +707,7 @@ class FoldAllRegionsAction extends FoldingAction { id: 'editor.foldAllMarkerRegions', label: nls.localize('foldAllMarkerRegions.label', "Fold All Regions"), alias: 'Fold All Regions', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_8), @@ -740,7 +740,7 @@ class UnfoldAllRegionsAction extends FoldingAction { id: 'editor.unfoldAllMarkerRegions', label: nls.localize('unfoldAllMarkerRegions.label', "Unfold All Regions"), alias: 'Unfold All Regions', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_9), @@ -773,7 +773,7 @@ class FoldAllAction extends FoldingAction { id: 'editor.foldAll', label: nls.localize('foldAllAction.label', "Fold All"), alias: 'Fold All', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_0), @@ -794,7 +794,7 @@ class UnfoldAllAction extends FoldingAction { id: 'editor.unfoldAll', label: nls.localize('unfoldAllAction.label', "Unfold All"), alias: 'Unfold All', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_J), @@ -838,7 +838,7 @@ for (let i = 1; i <= 7; i++) { id: FoldLevelAction.ID(i), label: nls.localize('foldLevelAction.label', "Fold Level {0}", i), alias: `Fold Level ${i}`, - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | (KeyCode.KEY_0 + i)), diff --git a/src/vs/editor/contrib/fontZoom/fontZoom.ts b/src/vs/editor/contrib/fontZoom/fontZoom.ts index b0c9716132f..b7eefcb8c3f 100644 --- a/src/vs/editor/contrib/fontZoom/fontZoom.ts +++ b/src/vs/editor/contrib/fontZoom/fontZoom.ts @@ -15,7 +15,7 @@ class EditorFontZoomIn extends EditorAction { id: 'editor.action.fontZoomIn', label: nls.localize('EditorFontZoomIn.label', "Editor Font Zoom In"), alias: 'Editor Font Zoom In', - precondition: null + precondition: undefined }); } @@ -31,7 +31,7 @@ class EditorFontZoomOut extends EditorAction { id: 'editor.action.fontZoomOut', label: nls.localize('EditorFontZoomOut.label', "Editor Font Zoom Out"), alias: 'Editor Font Zoom Out', - precondition: null + precondition: undefined }); } @@ -47,7 +47,7 @@ class EditorFontZoomReset extends EditorAction { id: 'editor.action.fontZoomReset', label: nls.localize('EditorFontZoomReset.label', "Editor Font Zoom Reset"), alias: 'Editor Font Zoom Reset', - precondition: null + precondition: undefined }); } diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts index 737f6c9ebb9..2f36e372857 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts @@ -294,7 +294,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC private gotoDefinition(target: IMouseTarget, sideBySide: boolean): Promise { this.editor.setPosition(target.position!); - const action = new DefinitionAction(new DefinitionActionConfig(sideBySide, false, true, false), { alias: '', label: '', id: '', precondition: null }); + const action = new DefinitionAction(new DefinitionActionConfig(sideBySide, false, true, false), { alias: '', label: '', id: '', precondition: undefined }); return this.editor.invokeWithinContext(accessor => action.run(accessor, this.editor)); } diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 8c7f8bd5250..b977abf6cdf 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -251,7 +251,7 @@ class ShowHoverAction extends EditorAction { ] }, "Show Hover"), alias: 'Show Hover', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_I), diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index f66e00856f0..94d846e38a0 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -40,7 +40,6 @@ import { applyCodeAction, QuickFixAction } from 'vs/editor/contrib/codeAction/co import { Action } from 'vs/base/common/actions'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { withNullAsUndefined } from 'vs/base/common/types'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; const $ = dom.$; @@ -68,14 +67,13 @@ class ModesContentComputer implements IHoverComputer { private readonly _editor: ICodeEditor; private _result: HoverPart[]; - private _range: Range | null; + private _range?: Range; constructor( editor: ICodeEditor, private readonly _markerDecorationsService: IMarkerDecorationsService ) { this._editor = editor; - this._range = null; } setRange(range: Range): void { @@ -183,7 +181,7 @@ class ModesContentComputer implements IHoverComputer { private _getLoadingMessage(): HoverPart { return { - range: withNullAsUndefined(this._range), + range: this._range, contents: [new MarkdownString().appendText(nls.localize('modesContentHover.loading', "Loading..."))] }; } diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index 54edce87b07..051a5378cf0 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -253,7 +253,7 @@ export class IndentUsingTabs extends ChangeIndentationSizeAction { id: IndentUsingTabs.ID, label: nls.localize('indentUsingTabs', "Indent Using Tabs"), alias: 'Indent Using Tabs', - precondition: null + precondition: undefined }); } } @@ -267,7 +267,7 @@ export class IndentUsingSpaces extends ChangeIndentationSizeAction { id: IndentUsingSpaces.ID, label: nls.localize('indentUsingSpaces', "Indent Using Spaces"), alias: 'Indent Using Spaces', - precondition: null + precondition: undefined }); } } @@ -281,7 +281,7 @@ export class DetectIndentation extends EditorAction { id: DetectIndentation.ID, label: nls.localize('detectIndentation', "Detect Indentation from Content"), alias: 'Detect Indentation from Content', - precondition: null + precondition: undefined }); } diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index 3b94a6906ab..f44dbff1b0b 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -410,7 +410,7 @@ class OpenLinkAction extends EditorAction { id: 'editor.action.openLink', label: nls.localize('label', "Open Link"), alias: 'Open Link', - precondition: null + precondition: undefined }); } diff --git a/src/vs/editor/contrib/message/messageController.ts b/src/vs/editor/contrib/message/messageController.ts index e522a12f8dc..d6e6b5c4c33 100644 --- a/src/vs/editor/contrib/message/messageController.ts +++ b/src/vs/editor/contrib/message/messageController.ts @@ -35,7 +35,7 @@ export class MessageController extends Disposable implements editorCommon.IEdito private readonly _editor: ICodeEditor; private readonly _visible: IContextKey; - private _messageWidget: MessageWidget; + private _messageWidget?: MessageWidget; private _messageListeners: IDisposable[] = []; constructor( @@ -96,7 +96,9 @@ export class MessageController extends Disposable implements editorCommon.IEdito closeMessage(): void { this._visible.reset(); this._messageListeners = dispose(this._messageListeners); - this._messageListeners.push(MessageWidget.fadeOut(this._messageWidget)); + if (this._messageWidget) { + this._messageListeners.push(MessageWidget.fadeOut(this._messageWidget)); + } } private _onDidAttemptReadOnlyEdit(): void { diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 330d6811342..9a699df3229 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -34,7 +34,7 @@ export class InsertCursorAbove extends EditorAction { id: 'editor.action.insertCursorAbove', label: nls.localize('mutlicursor.insertAbove', "Add Cursor Above"), alias: 'Add Cursor Above', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.UpArrow, @@ -83,7 +83,7 @@ export class InsertCursorBelow extends EditorAction { id: 'editor.action.insertCursorBelow', label: nls.localize('mutlicursor.insertBelow', "Add Cursor Below"), alias: 'Add Cursor Below', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.DownArrow, @@ -132,7 +132,7 @@ class InsertCursorAtEndOfEachLineSelected extends EditorAction { id: 'editor.action.insertCursorAtEndOfEachLineSelected', label: nls.localize('mutlicursor.insertAtEndOfEachLineSelected', "Add Cursors to Line Ends"), alias: 'Add Cursors to Line Ends', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_I, @@ -184,7 +184,7 @@ class InsertCursorAtEndOfLineSelected extends EditorAction { id: 'editor.action.addCursorsToBottom', label: nls.localize('mutlicursor.addCursorsToBottom', "Add Cursors To Bottom"), alias: 'Add Cursors To Bottom', - precondition: null + precondition: undefined }); } @@ -214,7 +214,7 @@ class InsertCursorAtTopOfLineSelected extends EditorAction { id: 'editor.action.addCursorsToTop', label: nls.localize('mutlicursor.addCursorsToTop', "Add Cursors To Top"), alias: 'Add Cursors To Top', - precondition: null + precondition: undefined }); } @@ -650,7 +650,7 @@ export class AddSelectionToNextFindMatchAction extends MultiCursorSelectionContr id: 'editor.action.addSelectionToNextFindMatch', label: nls.localize('addSelectionToNextFindMatch', "Add Selection To Next Find Match"), alias: 'Add Selection To Next Find Match', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.CtrlCmd | KeyCode.KEY_D, @@ -675,7 +675,7 @@ export class AddSelectionToPreviousFindMatchAction extends MultiCursorSelectionC id: 'editor.action.addSelectionToPreviousFindMatch', label: nls.localize('addSelectionToPreviousFindMatch', "Add Selection To Previous Find Match"), alias: 'Add Selection To Previous Find Match', - precondition: null, + precondition: undefined, menubarOpts: { menuId: MenuId.MenubarSelectionMenu, group: '3_multi', @@ -695,7 +695,7 @@ export class MoveSelectionToNextFindMatchAction extends MultiCursorSelectionCont id: 'editor.action.moveSelectionToNextFindMatch', label: nls.localize('moveSelectionToNextFindMatch', "Move Last Selection To Next Find Match"), alias: 'Move Last Selection To Next Find Match', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_D), @@ -714,7 +714,7 @@ export class MoveSelectionToPreviousFindMatchAction extends MultiCursorSelection id: 'editor.action.moveSelectionToPreviousFindMatch', label: nls.localize('moveSelectionToPreviousFindMatch', "Move Last Selection To Previous Find Match"), alias: 'Move Last Selection To Previous Find Match', - precondition: null + precondition: undefined }); } protected _run(multiCursorController: MultiCursorSelectionController, findController: CommonFindController): void { @@ -728,7 +728,7 @@ export class SelectHighlightsAction extends MultiCursorSelectionControllerAction id: 'editor.action.selectHighlights', label: nls.localize('selectAllOccurrencesOfFindMatch', "Select All Occurrences of Find Match"), alias: 'Select All Occurrences of Find Match', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L, diff --git a/src/vs/editor/contrib/smartSelect/smartSelect.ts b/src/vs/editor/contrib/smartSelect/smartSelect.ts index f8edd2156ee..164723bb251 100644 --- a/src/vs/editor/contrib/smartSelect/smartSelect.ts +++ b/src/vs/editor/contrib/smartSelect/smartSelect.ts @@ -161,7 +161,7 @@ class GrowSelectionAction extends AbstractSmartSelect { id: 'editor.action.smartSelect.expand', label: nls.localize('smartSelect.expand', "Expand Selection"), alias: 'Expand Selection', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.RightArrow, @@ -187,7 +187,7 @@ class ShrinkSelectionAction extends AbstractSmartSelect { id: 'editor.action.smartSelect.shrink', label: nls.localize('smartSelect.shrink', "Shrink Selection"), alias: 'Shrink Selection', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.LeftArrow, diff --git a/src/vs/editor/contrib/snippet/snippetController2.ts b/src/vs/editor/contrib/snippet/snippetController2.ts index a6fc98d4760..daccfa9eec8 100644 --- a/src/vs/editor/contrib/snippet/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/snippetController2.ts @@ -19,7 +19,6 @@ import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from ' import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILogService } from 'vs/platform/log/common/log'; import { SnippetSession } from './snippetSession'; -import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; export class SnippetController2 implements IEditorContribution { @@ -114,26 +113,10 @@ export class SnippetController2 implements IEditorContribution { this._updateState(); - // we listen on model and selection changes. usually - // both events come in together and this is to prevent - // that we don't call _updateState twice. - let state: EditorState; - let dedupedUpdateState = () => { - if (!state || !state.validate(this._editor)) { - this._updateState(); - state = new EditorState(this._editor, CodeEditorStateFlag.Selection | CodeEditorStateFlag.Value); - } - }; this._snippetListener = [ - this._editor.onDidChangeModelContent(e => { - if (e.isFlush) { - this.cancel(); - } else { - setTimeout(dedupedUpdateState, 0); - } - }), - this._editor.onDidChangeCursorSelection(dedupedUpdateState), + this._editor.onDidChangeModelContent(e => e.isFlush && this.cancel()), this._editor.onDidChangeModel(() => this.cancel()), + this._editor.onDidChangeCursorSelection(() => this._updateState()) ]; } diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index 9d495ceb8a7..e96f4d80e8e 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -198,6 +198,10 @@ export class OneSnippet { let ranges: Range[] | undefined; for (const placeholder of placeholdersWithEqualIndex) { + if (placeholder.isFinalTabstop) { + // ignore those + break; + } if (!ranges) { ranges = []; @@ -571,12 +575,6 @@ export class SnippetSession { return false; } - if (allPossibleSelections.has(0)) { - // selection overlaps with a final tab stop which means - // we done - return false; - } - // add selections from 'this' snippet so that we know all // selections for this placeholder allPossibleSelections.forEach((array, index) => { diff --git a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts index bfb0cf61727..465a26111d1 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts @@ -11,7 +11,6 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { NullLogService } from 'vs/platform/log/common/log'; import { Handler } from 'vs/editor/common/editorCommon'; -import { timeout } from 'vs/base/common/async'; suite('SnippetController2', function () { @@ -134,11 +133,11 @@ suite('SnippetController2', function () { assertSelections(editor, new Selection(1, 1, 1, 7), new Selection(2, 5, 2, 11)); editor.trigger('test', 'cut', {}); - assertContextKeys(contextKeys, false, false, false); + assertContextKeys(contextKeys, true, false, true); assertSelections(editor, new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)); editor.trigger('test', 'type', { text: 'abc' }); - assertContextKeys(contextKeys, false, false, false); + assertContextKeys(contextKeys, true, false, true); ctrl.next(); assertContextKeys(contextKeys, false, false, false); @@ -160,9 +159,9 @@ suite('SnippetController2', function () { assertSelections(editor, new Selection(1, 4, 1, 4), new Selection(2, 8, 2, 8)); assertContextKeys(contextKeys, true, false, true); - // ctrl.next(); - // assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); - // assertContextKeys(contextKeys, true, true, true); + ctrl.next(); + assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); + assertContextKeys(contextKeys, true, true, true); ctrl.next(); assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); @@ -177,10 +176,10 @@ suite('SnippetController2', function () { ctrl.insert('farboo'); assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); - // assertContextKeys(contextKeys, true, false, true); + assertContextKeys(contextKeys, true, false, true); - // ctrl.next(); - // assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); + ctrl.next(); + assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); assertContextKeys(contextKeys, false, false, false); }); @@ -403,22 +402,12 @@ suite('SnippetController2', function () { assertSelections(editor, new Selection(1, 22, 1, 22)); }); - test('A little confusing visual effect of highlighting for snippet tabstop #43270', async function () { + test('User defined snippet tab stops ignored #72862', function () { const ctrl = new SnippetController2(editor, logService, contextKeys); model.setValue(''); editor.setSelection(new Selection(1, 1, 1, 1)); - ctrl.insert('background-color: ${1:fff};$0'); - assertSelections(editor, new Selection(1, 19, 1, 22)); - - editor.setSelection(new Selection(1, 22, 1, 22)); + ctrl.insert('export default $1'); assertContextKeys(contextKeys, true, false, true); - editor.trigger('', 'deleteRight', null); - - assert.equal(model.getValue(), 'background-color: fff'); - - await timeout(0); // this depends on re-scheduling of events... - - assertContextKeys(contextKeys, false, false, false); }); }); diff --git a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts index 924f9a6d230..0d2ec457b50 100644 --- a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts @@ -331,7 +331,7 @@ suite('SnippetSession', function () { // reset selection to placeholder session.next(); - assert.equal(session.isSelectionWithinPlaceholders(), false); + assert.equal(session.isSelectionWithinPlaceholders(), true); assert.equal(session.isAtLastPlaceholder, true); assertSelections(editor, new Selection(1, 13, 1, 13), new Selection(2, 17, 2, 17)); }); diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index 57853e2b59f..0b44a9c6a68 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -10,7 +10,7 @@ import { SelectionBasedVariableResolver, CompositeSnippetVariableResolver, Model import { SnippetParser, Variable, VariableResolver } from 'vs/editor/contrib/snippet/snippetParser'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { Workspace, toWorkspaceFolders, IWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { Workspace, toWorkspaceFolders, IWorkspace, IWorkspaceContextService, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; suite('Snippet Variables Resolver', function () { @@ -328,11 +328,12 @@ suite('Snippet Variables Resolver', function () { assertVariableResolve(resolver, 'WORKSPACE_NAME', undefined); // single folder workspace without config - workspace = new Workspace('', toWorkspaceFolders([{ path: '/folderName' }])); + workspace = new Workspace('', [toWorkspaceFolder(URI.file('/folderName'))]); assertVariableResolve(resolver, 'WORKSPACE_NAME', 'folderName'); // workspace with config - workspace = new Workspace('', toWorkspaceFolders([{ path: 'folderName' }]), URI.file('testWorkspace.code-workspace')); + const workspaceConfigPath = URI.file('testWorkspace.code-workspace'); + workspace = new Workspace('', toWorkspaceFolders([{ path: 'folderName' }], workspaceConfigPath), workspaceConfigPath); assertVariableResolve(resolver, 'WORKSPACE_NAME', 'testWorkspace'); }); }); \ No newline at end of file diff --git a/src/vs/editor/contrib/suggest/completionModel.ts b/src/vs/editor/contrib/suggest/completionModel.ts index 9d23376cffd..31a8b92e750 100644 --- a/src/vs/editor/contrib/suggest/completionModel.ts +++ b/src/vs/editor/contrib/suggest/completionModel.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { fuzzyScore, fuzzyScoreGracefulAggressive, anyScore, FuzzyScorer, FuzzyScore } from 'vs/base/common/filters'; +import { fuzzyScore, fuzzyScoreGracefulAggressive, FuzzyScorer, FuzzyScore } from 'vs/base/common/filters'; import { isDisposable } from 'vs/base/common/lifecycle'; import { CompletionList, CompletionItemProvider, CompletionItemKind } from 'vs/editor/common/modes'; import { CompletionItem } from './suggest'; import { InternalSuggestOptions, EDITOR_DEFAULTS } from 'vs/editor/common/config/editorOptions'; import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance'; import { CharCode } from 'vs/base/common/charCode'; +import { compareIgnoreCase } from 'vs/base/common/strings'; type StrictCompletionItem = Required; @@ -215,8 +216,15 @@ export class CompletionModel { if (!match) { continue; // NO match } - item.score = anyScore(word, wordLow, 0, item.completion.label, item.labelLow, 0); - item.score[0] = match[0]; // use score from filterText + if (compareIgnoreCase(item.completion.filterText, item.completion.label) === 0) { + // filterText and label are actually the same -> use good highlights + item.score = match; + } else { + // re-run the scorer on the label in the hope of a result BUT use the rank + // of the filterText-match + item.score = scoreFn(word, wordLow, wordPos, item.completion.label, item.labelLow, 0, false) || FuzzyScore.Default; + item.score[0] = match[0]; // use score from filterText + } } else { // by default match `word` against the `label` diff --git a/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts b/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts new file mode 100644 index 00000000000..ba0789bf893 --- /dev/null +++ b/src/vs/editor/contrib/suggest/suggestCommitCharacters.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ISelectedSuggestion, SuggestWidget } from './suggestWidget'; +import { CharacterSet } from 'vs/editor/common/core/characterClassifier'; + +export class CommitCharacterController { + + private _disposables: IDisposable[] = []; + + private _active?: { + readonly acceptCharacters: CharacterSet; + readonly item: ISelectedSuggestion; + }; + + constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (selected: ISelectedSuggestion) => any) { + + this._disposables.push(widget.onDidShow(() => this._onItem(widget.getFocusedItem()))); + this._disposables.push(widget.onDidFocus(this._onItem, this)); + this._disposables.push(widget.onDidHide(this.reset, this)); + + this._disposables.push(editor.onWillType(text => { + if (this._active) { + const ch = text.charCodeAt(text.length - 1); + if (this._active.acceptCharacters.has(ch) && editor.getConfiguration().contribInfo.acceptSuggestionOnCommitCharacter) { + accept(this._active.item); + } + } + })); + } + + private _onItem(selected: ISelectedSuggestion | undefined): void { + if (!selected || !isNonEmptyArray(selected.item.completion.commitCharacters)) { + this.reset(); + return; + } + + const acceptCharacters = new CharacterSet(); + for (const ch of selected.item.completion.commitCharacters) { + if (ch.length > 0) { + acceptCharacters.add(ch.charCodeAt(0)); + } + } + this._active = { acceptCharacters, item: selected }; + } + + reset(): void { + this._active = undefined; + } + + dispose() { + dispose(this._disposables); + } +} diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index c824573c90f..573d601f4cf 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -31,57 +31,9 @@ import { WordContextKey } from 'vs/editor/contrib/suggest/wordContextKey'; import { Event } from 'vs/base/common/event'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { IdleValue } from 'vs/base/common/async'; -import { CharacterSet } from 'vs/editor/common/core/characterClassifier'; import { isObject } from 'vs/base/common/types'; - -class AcceptOnCharacterOracle { - - private _disposables: IDisposable[] = []; - - private _active?: { - readonly acceptCharacters: CharacterSet; - readonly item: ISelectedSuggestion; - }; - - constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (selected: ISelectedSuggestion) => any) { - - this._disposables.push(widget.onDidShow(() => this._onItem(widget.getFocusedItem()))); - this._disposables.push(widget.onDidFocus(this._onItem, this)); - this._disposables.push(widget.onDidHide(this.reset, this)); - - this._disposables.push(editor.onWillType(text => { - if (this._active) { - const ch = text.charCodeAt(text.length - 1); - if (this._active.acceptCharacters.has(ch) && editor.getConfiguration().contribInfo.acceptSuggestionOnCommitCharacter) { - accept(this._active.item); - } - } - })); - } - - private _onItem(selected: ISelectedSuggestion | undefined): void { - if (!selected || !isNonEmptyArray(selected.item.completion.commitCharacters)) { - this.reset(); - return; - } - - const acceptCharacters = new CharacterSet(); - for (const ch of selected.item.completion.commitCharacters) { - if (ch.length > 0) { - acceptCharacters.add(ch.charCodeAt(0)); - } - } - this._active = { acceptCharacters, item: selected }; - } - - reset(): void { - this._active = undefined; - } - - dispose() { - dispose(this._disposables); - } -} +import { CommitCharacterController } from './suggestCommitCharacters'; +import { CancellationToken } from 'vs/base/common/cancellation'; export class SuggestController implements IEditorContribution { @@ -113,15 +65,19 @@ export class SuggestController implements IEditorContribution { const widget = this._instantiationService.createInstance(SuggestWidget, this._editor); this._toDispose.push(widget); - this._toDispose.push(widget.onDidSelect(item => this._onDidSelectItem(item, false, true), this)); + this._toDispose.push(widget.onDidSelect(item => this._insertSuggestion(item, false, true), this)); // Wire up logic to accept a suggestion on certain characters - const autoAcceptOracle = new AcceptOnCharacterOracle(this._editor, widget, item => this._onDidSelectItem(item, false, true)); + const commitCharacterController = new CommitCharacterController( + this._editor, + widget, + item => this._insertSuggestion(item, false, true, true) + ); this._toDispose.push( - autoAcceptOracle, + commitCharacterController, this._model.onDidSuggest(e => { if (e.completionModel.items.length === 0) { - autoAcceptOracle.reset(); + commitCharacterController.reset(); } }) ); @@ -210,12 +166,23 @@ export class SuggestController implements IEditorContribution { } } - protected _onDidSelectItem(event: ISelectedSuggestion | undefined, keepAlternativeSuggestions: boolean, undoStops: boolean): void { + protected _insertSuggestion(event: ISelectedSuggestion | undefined, keepAlternativeSuggestions: boolean, undoStops: boolean): Promise; + protected _insertSuggestion(event: ISelectedSuggestion | undefined, keepAlternativeSuggestions: boolean, undoStops: boolean, noResolve: true): void; + protected async _insertSuggestion(event: ISelectedSuggestion | undefined, keepAlternativeSuggestions: boolean, undoStops: boolean, noResolve?: true): Promise { if (!event || !event.item) { this._alternatives.getValue().reset(); this._model.cancel(); return; } + + if (!noResolve) { + // DEFAULT is to wait for the item to be resolve + // but the current implementation of commit characters + // doesn't allow to wait and that's why there is a + // sync (no resolve) case + await event.item.resolve(CancellationToken.None); + } + if (!this._editor.hasModel()) { return; } @@ -282,7 +249,7 @@ export class SuggestController implements IEditorContribution { if (modelVersionNow !== model.getAlternativeVersionId()) { model.undo(); } - this._onDidSelectItem(next, false, false); + this._insertSuggestion(next, false, false); break; } }); @@ -364,7 +331,7 @@ export class SuggestController implements IEditorContribution { return; } this._editor.pushUndoStop(); - this._onDidSelectItem({ index, item, model: completionModel }, true, false); + this._insertSuggestion({ index, item, model: completionModel }, true, false); }, undefined, listener); }); @@ -375,10 +342,8 @@ export class SuggestController implements IEditorContribution { } acceptSelectedSuggestion(keepAlternativeSuggestions?: boolean): void { - if (this._widget) { - const item = this._widget.getValue().getFocusedItem(); - this._onDidSelectItem(item, !!keepAlternativeSuggestions, true); - } + const item = this._widget.getValue().getFocusedItem(); + this._insertSuggestion(item, !!keepAlternativeSuggestions, true); } acceptNextSuggestion() { @@ -390,58 +355,40 @@ export class SuggestController implements IEditorContribution { } cancelSuggestWidget(): void { - if (this._widget) { - this._model.cancel(); - this._widget.getValue().hideWidget(); - } + this._model.cancel(); + this._widget.getValue().hideWidget(); } selectNextSuggestion(): void { - if (this._widget) { - this._widget.getValue().selectNext(); - } + this._widget.getValue().selectNext(); } selectNextPageSuggestion(): void { - if (this._widget) { - this._widget.getValue().selectNextPage(); - } + this._widget.getValue().selectNextPage(); } selectLastSuggestion(): void { - if (this._widget) { - this._widget.getValue().selectLast(); - } + this._widget.getValue().selectLast(); } selectPrevSuggestion(): void { - if (this._widget) { - this._widget.getValue().selectPrevious(); - } + this._widget.getValue().selectPrevious(); } selectPrevPageSuggestion(): void { - if (this._widget) { - this._widget.getValue().selectPreviousPage(); - } + this._widget.getValue().selectPreviousPage(); } selectFirstSuggestion(): void { - if (this._widget) { - this._widget.getValue().selectFirst(); - } + this._widget.getValue().selectFirst(); } toggleSuggestionDetails(): void { - if (this._widget) { - this._widget.getValue().toggleDetails(); - } + this._widget.getValue().toggleDetails(); } toggleSuggestionFocus(): void { - if (this._widget) { - this._widget.getValue().toggleDetailsFocus(); - } + this._widget.getValue().toggleDetailsFocus(); } } diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index ef68570326a..42991e0a2f8 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -95,7 +95,6 @@ export class SuggestModel implements IDisposable { private _quickSuggestDelay: number; private _triggerCharacterListener: IDisposable; private readonly _triggerQuickSuggest = new TimeoutTimer(); - private readonly _triggerRefilter = new TimeoutTimer(); private _state: State = State.Idle; private _requestToken?: CancellationTokenSource; @@ -161,7 +160,7 @@ export class SuggestModel implements IDisposable { } dispose(): void { - dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerCharacterListener, this._triggerQuickSuggest, this._triggerRefilter]); + dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this._triggerCharacterListener, this._triggerQuickSuggest]); this._toDispose = dispose(this._toDispose); dispose(this._completionModel); this.cancel(); @@ -221,7 +220,6 @@ export class SuggestModel implements IDisposable { cancel(retrigger: boolean = false): void { if (this._state !== State.Idle) { - this._triggerRefilter.cancel(); this._triggerQuickSuggest.cancel(); if (this._requestToken) { this._requestToken.cancel(); @@ -328,14 +326,15 @@ export class SuggestModel implements IDisposable { } private _refilterCompletionItems(): void { - if (this._state === State.Idle) { - return; - } - if (!this._editor.hasModel()) { - return; - } - // refine active suggestion - this._triggerRefilter.cancelAndSet(() => { + // Re-filter suggestions. This MUST run async because filtering/scoring + // uses the model content AND the cursor position. The latter is NOT + // updated when the document has changed (the event which drives this method) + // and therefore a little pause (next mirco task) is needed. See: + // https://stackoverflow.com/questions/25915634/difference-between-microtask-and-macrotask-within-an-event-loop-context#25933985 + Promise.resolve().then(() => { + if (this._state === State.Idle) { + return; + } if (!this._editor.hasModel()) { return; } @@ -343,7 +342,7 @@ export class SuggestModel implements IDisposable { const position = this._editor.getPosition(); const ctx = new LineContext(model, position, this._state === State.Auto, false); this._onNewContext(ctx); - }, 0); + }); } trigger(context: SuggestTriggerContext, retrigger: boolean = false, onlyFrom?: Set, existingItems?: CompletionItem[]): void { diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 054feaf901f..1b899d3946b 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -30,7 +30,6 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { TimeoutTimer, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { CompletionItemKind, completionKindToCssClass } from 'vs/editor/common/modes'; import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; @@ -545,10 +544,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate { - this.onDidSelectEmitter.fire({ item, index, model: completionModel }); - this.editor.focus(); - }); + this.onDidSelectEmitter.fire({ item, index, model: completionModel }); + this.editor.focus(); } private _getSuggestionAriaAlertLabel(item: CompletionItem): string { diff --git a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts index db6af2b0fbf..93f417533c3 100644 --- a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts @@ -671,8 +671,8 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { return withOracle(async (sugget, editor) => { class TestCtrl extends SuggestController { - _onDidSelectItem(item: ISelectedSuggestion) { - super._onDidSelectItem(item, false, true); + _insertSuggestion(item: ISelectedSuggestion) { + return super._insertSuggestion(item, false, true); } } const ctrl = editor.registerAndInstantiateContribution(TestCtrl); @@ -687,7 +687,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { const [first] = event.completionModel.items; assert.equal(first.completion.label, 'bar'); - ctrl._onDidSelectItem({ item: first, index: 0, model: event.completionModel }); + return ctrl._insertSuggestion({ item: first, index: 0, model: event.completionModel }); }); assert.equal( diff --git a/src/vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode.ts b/src/vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode.ts index 22dabb97eb2..37e74657713 100644 --- a/src/vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode.ts +++ b/src/vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode.ts @@ -20,7 +20,7 @@ export class ToggleTabFocusModeAction extends EditorAction { id: ToggleTabFocusModeAction.ID, label: nls.localize({ key: 'toggle.tabMovesFocus', comment: ['Turn on/off use of tab key for moving focus around VS Code'] }, "Toggle Tab Key Moves Focus"), alias: 'Toggle Tab Key Moves Focus', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: null, primary: KeyMod.CtrlCmd | KeyCode.KEY_M, diff --git a/src/vs/editor/contrib/tokenization/tokenization.ts b/src/vs/editor/contrib/tokenization/tokenization.ts index aeb5b996e1a..96e8d80e052 100644 --- a/src/vs/editor/contrib/tokenization/tokenization.ts +++ b/src/vs/editor/contrib/tokenization/tokenization.ts @@ -14,7 +14,7 @@ class ForceRetokenizeAction extends EditorAction { id: 'editor.action.forceRetokenize', label: nls.localize('forceRetokenize', "Developer: Force Retokenize"), alias: 'Developer: Force Retokenize', - precondition: null + precondition: undefined }); } diff --git a/src/vs/editor/contrib/wordOperations/wordOperations.ts b/src/vs/editor/contrib/wordOperations/wordOperations.ts index b39269c30a7..282a4e05ac1 100644 --- a/src/vs/editor/contrib/wordOperations/wordOperations.ts +++ b/src/vs/editor/contrib/wordOperations/wordOperations.ts @@ -98,7 +98,7 @@ export class CursorWordStartLeft extends WordLeftCommand { inSelectionMode: false, wordNavigationType: WordNavigationType.WordStart, id: 'cursorWordStartLeft', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.LeftArrow, @@ -115,7 +115,7 @@ export class CursorWordEndLeft extends WordLeftCommand { inSelectionMode: false, wordNavigationType: WordNavigationType.WordEnd, id: 'cursorWordEndLeft', - precondition: null + precondition: undefined }); } } @@ -126,7 +126,7 @@ export class CursorWordLeft extends WordLeftCommand { inSelectionMode: false, wordNavigationType: WordNavigationType.WordStartFast, id: 'cursorWordLeft', - precondition: null + precondition: undefined }); } } @@ -137,7 +137,7 @@ export class CursorWordStartLeftSelect extends WordLeftCommand { inSelectionMode: true, wordNavigationType: WordNavigationType.WordStart, id: 'cursorWordStartLeftSelect', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow, @@ -154,7 +154,7 @@ export class CursorWordEndLeftSelect extends WordLeftCommand { inSelectionMode: true, wordNavigationType: WordNavigationType.WordEnd, id: 'cursorWordEndLeftSelect', - precondition: null + precondition: undefined }); } } @@ -165,7 +165,7 @@ export class CursorWordLeftSelect extends WordLeftCommand { inSelectionMode: true, wordNavigationType: WordNavigationType.WordStart, id: 'cursorWordLeftSelect', - precondition: null + precondition: undefined }); } } @@ -176,7 +176,7 @@ export class CursorWordStartRight extends WordRightCommand { inSelectionMode: false, wordNavigationType: WordNavigationType.WordStart, id: 'cursorWordStartRight', - precondition: null + precondition: undefined }); } } @@ -187,7 +187,7 @@ export class CursorWordEndRight extends WordRightCommand { inSelectionMode: false, wordNavigationType: WordNavigationType.WordEnd, id: 'cursorWordEndRight', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyCode.RightArrow, @@ -204,7 +204,7 @@ export class CursorWordRight extends WordRightCommand { inSelectionMode: false, wordNavigationType: WordNavigationType.WordEnd, id: 'cursorWordRight', - precondition: null + precondition: undefined }); } } @@ -215,7 +215,7 @@ export class CursorWordStartRightSelect extends WordRightCommand { inSelectionMode: true, wordNavigationType: WordNavigationType.WordStart, id: 'cursorWordStartRightSelect', - precondition: null + precondition: undefined }); } } @@ -226,7 +226,7 @@ export class CursorWordEndRightSelect extends WordRightCommand { inSelectionMode: true, wordNavigationType: WordNavigationType.WordEnd, id: 'cursorWordEndRightSelect', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow, @@ -243,7 +243,7 @@ export class CursorWordRightSelect extends WordRightCommand { inSelectionMode: true, wordNavigationType: WordNavigationType.WordEnd, id: 'cursorWordRightSelect', - precondition: null + precondition: undefined }); } } diff --git a/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts index 0c20a58b790..5887bd84a21 100644 --- a/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts +++ b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts @@ -79,7 +79,7 @@ export class CursorWordPartLeft extends WordPartLeftCommand { inSelectionMode: false, wordNavigationType: WordNavigationType.WordStart, id: 'cursorWordPartLeft', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: 0, @@ -98,7 +98,7 @@ export class CursorWordPartLeftSelect extends WordPartLeftCommand { inSelectionMode: true, wordNavigationType: WordNavigationType.WordStart, id: 'cursorWordPartLeftSelect', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: 0, @@ -122,7 +122,7 @@ export class CursorWordPartRight extends WordPartRightCommand { inSelectionMode: false, wordNavigationType: WordNavigationType.WordEnd, id: 'cursorWordPartRight', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: 0, @@ -138,7 +138,7 @@ export class CursorWordPartRightSelect extends WordPartRightCommand { inSelectionMode: true, wordNavigationType: WordNavigationType.WordEnd, id: 'cursorWordPartRightSelect', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, primary: 0, diff --git a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts index 4bde524d545..73dfa8d3ac3 100644 --- a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts +++ b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts @@ -330,7 +330,7 @@ class ShowAccessibilityHelpAction extends EditorAction { id: 'editor.action.showAccessibilityHelp', label: AccessibilityHelpNLS.showAccessibilityHelpAction, alias: 'Show Accessibility Help', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: (browser.isIE ? KeyMod.CtrlCmd | KeyCode.F1 : KeyMod.Alt | KeyCode.F1), diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts index e60dd1925c8..47a2da412ef 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts @@ -84,7 +84,7 @@ class InspectTokens extends EditorAction { id: 'editor.action.inspectTokens', label: InspectTokensNLS.inspectTokensAction, alias: 'Developer: Inspect Tokens', - precondition: null + precondition: undefined }); } diff --git a/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts b/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts index 2b89cf276d9..5d2960a5497 100644 --- a/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts +++ b/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts @@ -149,7 +149,7 @@ export class GotoLineAction extends BaseEditorQuickOpenAction { id: 'editor.action.gotoLine', label: GoToLineNLS.gotoLineActionLabel, alias: 'Go to Line...', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.CtrlCmd | KeyCode.KEY_G, diff --git a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts index e50716e3c37..b5d2715c808 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts @@ -82,7 +82,7 @@ export class QuickCommandAction extends BaseEditorQuickOpenAction { id: 'editor.action.quickCommand', label: QuickCommandNLS.quickCommandActionLabel, alias: 'Command Palette', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: (browser.isIE ? KeyMod.Alt | KeyCode.F1 : KeyCode.F1), diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts index 92cc30a7b53..eeeaf6f32c8 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts @@ -26,12 +26,12 @@ let SCOPE_PREFIX = ':'; export class SymbolEntry extends QuickOpenEntryGroup { private readonly name: string; private readonly type: string; - private readonly description: string | null; + private readonly description: string | undefined; private readonly range: Range; private readonly editor: ICodeEditor; private readonly decorator: IDecorator; - constructor(name: string, type: string, description: string | null, range: Range, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator) { + constructor(name: string, type: string, description: string | undefined, range: Range, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator) { super(); this.name = name; @@ -55,7 +55,7 @@ export class SymbolEntry extends QuickOpenEntryGroup { return this.type; } - public getDescription(): string | null { + public getDescription(): string | undefined { return this.description; } @@ -169,7 +169,7 @@ export class QuickOutlineAction extends BaseEditorQuickOpenAction { }); } - private symbolEntry(name: string, type: string, description: string | null, range: IRange, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator): SymbolEntry { + private symbolEntry(name: string, type: string, description: string | undefined, range: IRange, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator): SymbolEntry { return new SymbolEntry(name, type, description, Range.lift(range), highlights, editor, decorator); } @@ -192,7 +192,7 @@ export class QuickOutlineAction extends BaseEditorQuickOpenAction { if (highlights) { // Show parent scope as description - let description: string | null = null; + let description: string | undefined = undefined; if (element.containerName) { description = element.containerName; } diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index b6468cddfc1..6bd70b0ac3e 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -19,7 +19,7 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { IPosition, Position as Pos } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { ITextModel } from 'vs/editor/common/model'; +import { ITextModel, ITextSnapshot } from 'vs/editor/common/model'; import { TextEdit, WorkspaceEdit, isResourceTextEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -67,6 +67,10 @@ export class SimpleModel implements IResolvedTextEditorModel { return this.model; } + public createSnapshot(): ITextSnapshot { + return this.model.createSnapshot(); + } + public isReadonly(): boolean { return false; } @@ -348,7 +352,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { if (!keybinding) { // This might be a removal keybinding item in user settings => accept it - result[resultLen++] = new ResolvedKeybindingItem(null, item.command, item.commandArgs, when, isDefault); + result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault); } else { const resolvedKeybindings = this.resolveKeybinding(keybinding); for (const resolvedKeybinding of resolvedKeybindings) { diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index a98abd9b34a..0f3d46ac1a2 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -48,6 +48,7 @@ import { ISuggestMemoryService, SuggestMemoryService } from 'vs/editor/contrib/s import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { ICodeLensCache, CodeLensCache } from 'vs/editor/contrib/codelens/codeLensCache'; export interface IEditorOverrideServices { [index: string]: any; @@ -145,8 +146,6 @@ export module StaticServices { export const markerDecorationsService = define(IMarkerDecorationsService, (o) => new MarkerDecorationsService(modelService.get(o), markerService.get(o))); - export const editorWorkerService = define(IEditorWorkerService, (o) => new EditorWorkerServiceImpl(modelService.get(o), resourceConfigurationService.get(o))); - export const standaloneThemeService = define(IStandaloneThemeService, () => new StandaloneThemeServiceImpl()); export const codeEditorService = define(ICodeEditorService, (o) => new StandaloneCodeEditorServiceImpl(standaloneThemeService.get(o))); @@ -157,8 +156,11 @@ export module StaticServices { export const logService = define(ILogService, () => new NullLogService()); + export const editorWorkerService = define(IEditorWorkerService, (o) => new EditorWorkerServiceImpl(modelService.get(o), resourceConfigurationService.get(o), logService.get(o))); + export const suggestMemoryService = define(ISuggestMemoryService, (o) => new SuggestMemoryService(storageService.get(o), configurationService.get(o))); + export const codeLensCacheService = define(ICodeLensCache, (o) => new CodeLensCache(storageService.get(o))); } export class DynamicStandaloneServices extends Disposable { diff --git a/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts b/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts index 9f6261900aa..22cb5063a94 100644 --- a/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts +++ b/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts @@ -17,7 +17,7 @@ class ToggleHighContrast extends EditorAction { id: 'editor.action.toggleHighContrast', label: ToggleHighContrastNLS.toggleHighContrast, alias: 'Toggle High Contrast Theme', - precondition: null + precondition: undefined }); this._originalThemeName = null; } diff --git a/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts b/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts index 626420509dd..e24401abda1 100644 --- a/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts +++ b/src/vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts @@ -7,14 +7,13 @@ import * as assert from 'assert'; import { WordCharacterClassifier } from 'vs/editor/common/controller/wordCharacterClassifier'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { DefaultEndOfLine } from 'vs/editor/common/model'; +import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model'; import { PieceTreeBase } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase'; import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer'; import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; import { NodeColor, SENTINEL, TreeNode } from 'vs/editor/common/model/pieceTreeTextBuffer/rbTreeBase'; import { TextModel } from 'vs/editor/common/model/textModel'; import { SearchData } from 'vs/editor/common/model/textModelSearch'; -import { ITextSnapshot } from 'vs/platform/files/common/files'; const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\r\n'; diff --git a/src/vs/platform/actions/browser/menuItemActionItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts similarity index 91% rename from src/vs/platform/actions/browser/menuItemActionItem.ts rename to src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 4151c09390a..d6c83d65768 100644 --- a/src/vs/platform/actions/browser/menuItemActionItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -5,7 +5,7 @@ import { addClasses, createCSSRule, removeClasses } from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; -import { ActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionViewItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; import { IdGenerator } from 'vs/base/common/idGenerator'; @@ -113,16 +113,16 @@ function fillInActions(groups: [string, Array = new Map(); @@ -230,13 +230,13 @@ export class MenuItemActionItem extends ActionItem { const iconPathMapKey = item.iconLocation.dark.toString(); - if (MenuItemActionItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { - iconClass = MenuItemActionItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; + if (MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) { + iconClass = MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey)!; } else { iconClass = ids.nextId(); createCSSRule(`.icon.${iconClass}`, `background-image: url("${(item.iconLocation.light || item.iconLocation.dark).toString()}")`); createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: url("${item.iconLocation.dark.toString()}")`); - MenuItemActionItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); + MenuEntryActionViewItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass); } addClasses(this.label, 'icon', iconClass); @@ -254,10 +254,10 @@ export class MenuItemActionItem extends ActionItem { } } -// Need to subclass MenuItemActionItem in order to respect +// Need to subclass MenuEntryActionViewItem in order to respect // the action context coming from any action bar, without breaking // existing users -export class ContextAwareMenuItemActionItem extends MenuItemActionItem { +export class ContextAwareMenuEntryActionViewItem extends MenuEntryActionViewItem { onClick(event: MouseEvent): void { event.preventDefault(); diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 1ffe3da98c2..8aacb21dfb2 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -18,21 +18,18 @@ export interface ILocalizedString { original: string; } -export interface IBaseCommandAction { +export interface ICommandAction { id: string; title: string | ILocalizedString; category?: string | ILocalizedString; -} - -export interface ICommandAction extends IBaseCommandAction { iconLocation?: { dark: URI; light?: URI; }; precondition?: ContextKeyExpr; toggled?: ContextKeyExpr; } -export interface ISerializableCommandAction extends IBaseCommandAction { - iconLocation?: { dark: UriComponents; light?: UriComponents; }; -} +type Serialized = { [K in keyof T]: T[K] extends URI ? UriComponents : Serialized }; + +export type ISerializableCommandAction = Serialized; export interface IMenuItem { command: ICommandAction; diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.ts b/src/vs/platform/contextview/browser/contextMenuHandler.ts index 3b7a0c51d5b..53ba4d21e04 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.ts +++ b/src/vs/platform/contextview/browser/contextMenuHandler.ts @@ -73,7 +73,7 @@ export class ContextMenuHandler { actionRunner.onDidBeforeRun(this.onActionRun, this, menuDisposables); actionRunner.onDidRun(this.onDidActionRun, this, menuDisposables); menu = new Menu(container, actions, { - actionItemProvider: delegate.getActionItem, + actionViewItemProvider: delegate.getActionViewItem, context: delegate.getActionsContext ? delegate.getActionsContext() : null, actionRunner, getKeyBinding: delegate.getKeyBinding ? delegate.getKeyBinding : action => this.keybindingService.lookupKeybinding(action.id) diff --git a/src/vs/platform/diagnostics/common/diagnosticsService.ts b/src/vs/platform/diagnostics/common/diagnosticsService.ts index d8d2ed70420..21cd8952e13 100644 --- a/src/vs/platform/diagnostics/common/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/common/diagnosticsService.ts @@ -22,7 +22,7 @@ export interface SystemInfo extends IMachineInfo { processArgs: string; gpuStatus: any; screenReader: string; - remoteData: IRemoteDiagnosticInfo[]; + remoteData: (IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]; load?: string; } diff --git a/src/vs/platform/dialogs/browser/dialogService.ts b/src/vs/platform/dialogs/browser/dialogService.ts index c2fa9b5d344..057ed24c053 100644 --- a/src/vs/platform/dialogs/browser/dialogService.ts +++ b/src/vs/platform/dialogs/browser/dialogService.ts @@ -13,6 +13,8 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachDialogStyler } from 'vs/platform/theme/common/styler'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { EventHelper } from 'vs/base/browser/dom'; export class DialogService implements IDialogService { _serviceBrand: any; @@ -76,7 +78,10 @@ export class DialogService implements IDialogService { { detail: options ? options.detail : undefined, cancelId: options ? options.cancelId : undefined, - type: this.getDialogType(severity) + type: this.getDialogType(severity), + keyEventProcessor: (event: StandardKeyboardEvent) => { + EventHelper.stop(event, true); + } }); dialogDisposables.push(dialog); diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index 24644613845..36246b59ad3 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -186,8 +186,8 @@ class WindowDriver implements IWindowDriver { const lines: string[] = []; - for (let i = 0; i < xterm._core.buffer.lines.length; i++) { - lines.push(xterm._core.buffer.translateBufferLineToString(i, true)); + for (let i = 0; i < xterm.buffer.length; i++) { + lines.push(xterm.buffer.getLine(i)!.translateToString(true)); } return lines; diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 48953aa64e7..160ea244b45 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -49,12 +49,21 @@ export interface IBaseResourceInput { * looking at the scheme of the resource(s). */ readonly forceFile?: boolean; + + /** + * Hint to indicate that this input should be treated as a + * untitled file. + * + * Without this hint, the editor service will make a guess by + * looking at the scheme of the resource(s). + */ + readonly forceUntitled?: boolean; } export interface IResourceInput extends IBaseResourceInput { /** - * The resource URL of the resource to open. + * The resource URI of the resource to open. */ resource: URI; @@ -62,6 +71,12 @@ export interface IResourceInput extends IBaseResourceInput { * The encoding of the text input if known. */ readonly encoding?: string; + + /** + * The identifier of the language mode of the text input + * if known to use when displaying the contents. + */ + readonly mode?: string; } export interface IEditorOptions { diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 58cb747afcd..f48e072b5e8 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -69,7 +69,6 @@ export interface ParsedArgs { 'driver'?: string; 'driver-verbose'?: boolean; remote?: string; - 'nodeless'?: boolean; // TODO@ben revisit electron5 nodeless support } export const IEnvironmentService = createDecorator('environmentService'); diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index b7a8de530f0..5c7a4050b8a 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -47,7 +47,7 @@ export const options: Option[] = [ { id: 'extensions-dir', type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, { id: 'list-extensions', type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") }, { id: 'show-versions', type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") }, - { id: 'install-extension', type: 'string', cat: 'e', args: 'extension-id', description: localize('installExtension', "Installs or updates the extension. Use `--force` argument to avoid prompts.") }, + { id: 'install-extension', type: 'string', cat: 'e', args: 'extension-id | path-to-vsix', description: localize('installExtension', "Installs or updates the extension. Use `--force` argument to avoid prompts.") }, { id: 'uninstall-extension', type: 'string', cat: 'e', args: 'extension-id', description: localize('uninstallExtension', "Uninstalls an extension.") }, { id: 'enable-proposed-api', type: 'string', cat: 'e', args: 'extension-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") }, @@ -95,7 +95,6 @@ export const options: Option[] = [ { id: 'trace-category-filter', type: 'string' }, { id: 'trace-options', type: 'string' }, { id: 'prof-code-loading', type: 'boolean' }, - { id: 'nodeless', type: 'boolean' }, // TODO@ben revisit electron5 nodeless support { id: '_', type: 'string' } ]; diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index c8970296151..7a945613a22 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -343,13 +343,6 @@ export class ExtensionManagementService extends Disposable implements IExtension return Promise.reject(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE)); } - if (this.remote) { - const manifest = await this.galleryService.getManifest(extension, CancellationToken.None); - if (manifest && isUIExtension(manifest, [], this.configurationService) && !isLanguagePackExtension(manifest)) { - return Promise.reject(new Error(nls.localize('notSupportedUIExtension', "Can't install extension {0} since UI Extensions are not supported", extension.identifier.id))); - } - } - return compatibleExtension; } diff --git a/src/vs/platform/extensions/node/extensionsUtil.ts b/src/vs/platform/extensions/node/extensionsUtil.ts index 972fe22f977..a84379d312e 100644 --- a/src/vs/platform/extensions/node/extensionsUtil.ts +++ b/src/vs/platform/extensions/node/extensionsUtil.ts @@ -11,16 +11,8 @@ import product from 'vs/platform/product/node/product'; export function isUIExtension(manifest: IExtensionManifest, uiContributions: string[], configurationService: IConfigurationService): boolean { const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); - const configuredUIExtensions = configurationService.getValue('_workbench.uiExtensions') || []; - if (configuredUIExtensions.length) { - if (configuredUIExtensions.indexOf(extensionId) !== -1) { - return true; - } - if (configuredUIExtensions.indexOf(`-${extensionId}`) !== -1) { - return false; - } - } - switch (manifest.extensionKind) { + const extensionKind = getExtensionKind(manifest, configurationService); + switch (extensionKind) { case 'ui': return true; case 'workspace': return false; default: { @@ -40,3 +32,14 @@ export function isUIExtension(manifest: IExtensionManifest, uiContributions: str } } } + +function getExtensionKind(manifest: IExtensionManifest, configurationService: IConfigurationService): string | undefined { + const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); + const configuredExtensionKinds = configurationService.getValue<{ [key: string]: string }>('remote.extensionKind') || {}; + for (const id of Object.keys(configuredExtensionKinds)) { + if (areSameExtensions({ id: extensionId }, { id })) { + return configuredExtensionKinds[id]; + } + } + return manifest.extensionKind; +} diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index e0301ff7301..8555f6f68f0 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -13,25 +13,14 @@ import { startsWithIgnoreCase } from 'vs/base/common/strings'; import { IDisposable } from 'vs/base/common/lifecycle'; import { isEqualOrParent, isEqual } from 'vs/base/common/resources'; import { isUndefinedOrNull } from 'vs/base/common/types'; -import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; +import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; export const IFileService = createDecorator('fileService'); -export interface IResourceEncodings { - getWriteEncoding(resource: URI, preferredEncoding?: string): IResourceEncoding; -} - -export interface IResourceEncoding { - encoding: string; - hasBOM: boolean; -} - export interface IFileService { _serviceBrand: ServiceIdentifier; - //#region File System Provider - /** * An event that is fired when a file system provider is added or removed */ @@ -63,13 +52,6 @@ export interface IFileService { */ hasCapability(resource: URI, capability: FileSystemProviderCapabilities): boolean; - //#endregion - - /** - * Helper to determine read/write encoding for resources. - */ - encoding: IResourceEncodings; - /** * Allows to listen for file changes. The event will fire for every file within the opened workspace * (if any) as well as all files that have been watched explicitly using the #watch() API. @@ -111,18 +93,14 @@ export interface IFileService { exists(resource: URI): Promise; /** - * Resolve the contents of a file identified by the resource. - * - * The returned object contains properties of the file and the full value as string. + * Read the contents of the provided resource unbuffered. */ - resolveContent(resource: URI, options?: IResolveContentOptions): Promise; + readFile(resource: URI, options?: IReadFileOptions): Promise; /** - * Resolve the contents of a file identified by the resource. - * - * The returned object contains properties of the file and the value as a readable stream. + * Read the contents of the provided resource buffered as stream. */ - resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise; + readFileStream(resource: URI, options?: IReadFileOptions): Promise; /** * Updates the content replacing its previous value. @@ -228,11 +206,11 @@ export const enum FileSystemProviderCapabilities { export interface IFileSystemProvider { readonly capabilities: FileSystemProviderCapabilities; - onDidChangeCapabilities: Event; + readonly onDidChangeCapabilities: Event; - onDidErrorOccur?: Event; // TODO@ben remove once file watchers are solid + readonly onDidErrorOccur?: Event; // TODO@ben remove once file watchers are solid - onDidChangeFile: Event; + readonly onDidChangeFile: Event; watch(resource: URI, opts: IWatchOptions): IDisposable; stat(resource: URI): Promise; @@ -310,7 +288,12 @@ export function markAsFileSystemProviderError(error: Error, code: FileSystemProv return error; } -export function toFileSystemProviderErrorCode(error: Error): FileSystemProviderErrorCode { +export function toFileSystemProviderErrorCode(error: Error | undefined | null): FileSystemProviderErrorCode { + + // Guard against abuse + if (!error) { + return FileSystemProviderErrorCode.Unknown; + } // FileSystemProviderError comes with the code if (error instanceof FileSystemProviderError) { @@ -337,6 +320,13 @@ export function toFileSystemProviderErrorCode(error: Error): FileSystemProviderE } export function toFileOperationResult(error: Error): FileOperationResult { + + // FileSystemProviderError comes with the result already + if (error instanceof FileOperationError) { + return error.fileOperationResult; + } + + // Otherwise try to find from code switch (toFileSystemProviderErrorCode(error)) { case FileSystemProviderErrorCode.FileNotFound: return FileOperationResult.FILE_NOT_FOUND; @@ -576,8 +566,7 @@ export interface IBaseStatWithMetadata extends IBaseStat { export interface IFileStat extends IBaseStat { /** - * The resource is a directory. if {{true}} - * {{encoding}} has no meaning. + * The resource is a directory */ isDirectory: boolean; @@ -608,147 +597,23 @@ export interface IResolveFileResultWithMetadata extends IResolveFileResult { stat?: IFileStatWithMetadata; } -/** - * Content and meta information of a file. - */ -export interface IContent extends IBaseStatWithMetadata { +export interface IFileContent extends IBaseStatWithMetadata { /** - * The content of a text file. + * The content of a file as buffer. */ - value: string; + value: VSBuffer; +} + +export interface IFileStreamContent extends IBaseStatWithMetadata { /** - * The encoding of the content if known. + * The content of a file as stream. */ - encoding: string; + value: VSBufferReadableStream; } -// this should eventually replace IContent such -// that we have a clear separation between content -// and metadata (TODO@Joh, TODO@Ben) -export interface IContentData { - encoding: string; - stream: IStringStream; -} - -/** - * A Stream emitting strings. - */ -export interface IStringStream { - on(event: 'data', callback: (chunk: string) => void): void; - on(event: 'error', callback: (err: any) => void): void; - on(event: 'end', callback: () => void): void; - on(event: string, callback: any): void; -} - -/** - * Text snapshot that works like an iterator. - * Will try to return chunks of roughly ~64KB size. - * Will return null when finished. - */ -export interface ITextSnapshot { - read(): string | null; -} - -/** - * Helper method to convert a snapshot into its full string form. - */ -export function snapshotToString(snapshot: ITextSnapshot): string { - const chunks: string[] = []; - - let chunk: string | null; - while (typeof (chunk = snapshot.read()) === 'string') { - chunks.push(chunk); - } - - return chunks.join(''); -} - -export function stringToSnapshot(value: string): ITextSnapshot { - let done = false; - - return { - read(): string | null { - if (!done) { - done = true; - - return value; - } - - return null; - } - }; -} - -export class TextSnapshotReadable implements VSBufferReadable { - private preambleHandled: boolean; - - constructor(private snapshot: ITextSnapshot, private preamble?: string) { } - - read(): VSBuffer | null { - let value = this.snapshot.read(); - - // Handle preamble if provided - if (!this.preambleHandled) { - this.preambleHandled = true; - - if (typeof this.preamble === 'string') { - if (typeof value === 'string') { - value = this.preamble + value; - } else { - value = this.preamble; - } - } - } - - if (typeof value === 'string') { - return VSBuffer.fromString(value); - } - - return null; - } -} - -export function toBufferOrReadable(value: string): VSBuffer; -export function toBufferOrReadable(value: ITextSnapshot): VSBufferReadable; -export function toBufferOrReadable(value: string | ITextSnapshot): VSBuffer | VSBufferReadable; -export function toBufferOrReadable(value: string | ITextSnapshot | undefined): VSBuffer | VSBufferReadable | undefined; -export function toBufferOrReadable(value: string | ITextSnapshot | undefined): VSBuffer | VSBufferReadable | undefined { - if (typeof value === 'undefined') { - return undefined; - } - - if (typeof value === 'string') { - return VSBuffer.fromString(value); - } - - return new TextSnapshotReadable(value); -} - -/** - * Streamable content and meta information of a file. - */ -export interface IStreamContent extends IBaseStatWithMetadata { - - /** - * The streamable content of a text file. - */ - value: IStringStream; - - /** - * The encoding of the content if known. - */ - encoding: string; -} - -export interface IResolveContentOptions { - - /** - * The optional acceptTextOnly parameter allows to fail this request early if the file - * contents are not textual. - */ - acceptTextOnly?: boolean; +export interface IReadFileOptions { /** * The optional etag parameter allows to return early from resolving the resource if @@ -758,22 +623,25 @@ export interface IResolveContentOptions { */ etag?: string; - /** - * The optional encoding parameter allows to specify the desired encoding when resolving - * the contents of the file. - */ - encoding?: string; - - /** - * The optional guessEncoding parameter allows to guess encoding from content of the file. - */ - autoGuessEncoding?: boolean; - /** * Is an integer specifying where to begin reading from in the file. If position is null, * data will be read from the current file position. */ position?: number; + + /** + * Is an integer specifying how many bytes to read from the file. By default, all bytes + * will be read. + */ + length?: number; + + /** + * If provided, the size of the file will be checked against the limits. + */ + limits?: { + size?: number; + memory?: number; + }; } export interface IWriteFileOptions { @@ -789,30 +657,6 @@ export interface IWriteFileOptions { etag?: string; } -export interface IWriteTextFileOptions extends IWriteFileOptions { - - /** - * The encoding to use when updating a file. - */ - encoding?: string; - - /** - * If set to true, will enforce the selected encoding and not perform any detection using BOMs. - */ - overwriteEncoding?: boolean; - - /** - * Whether to overwrite a file even if it is readonly. - */ - overwriteReadonly?: boolean; - - /** - * Wether to write to the file as elevated (admin) user. When setting this option a prompt will - * ask the user to authenticate as super user. - */ - writeElevated?: boolean; -} - export interface IResolveFileOptions { /** @@ -847,7 +691,7 @@ export interface ICreateFileOptions { } export class FileOperationError extends Error { - constructor(message: string, public fileOperationResult: FileOperationResult, public options?: IResolveContentOptions & IWriteTextFileOptions & ICreateFileOptions) { + constructor(message: string, public fileOperationResult: FileOperationResult, public options?: IReadFileOptions & IWriteFileOptions & ICreateFileOptions) { super(message); } @@ -857,7 +701,6 @@ export class FileOperationError extends Error { } export const enum FileOperationResult { - FILE_IS_BINARY, FILE_IS_DIRECTORY, FILE_NOT_FOUND, FILE_NOT_MODIFIED_SINCE, @@ -906,247 +749,6 @@ export interface IFilesConfiguration { }; } -export const SUPPORTED_ENCODINGS: { [encoding: string]: { labelLong: string; labelShort: string; order: number; encodeOnly?: boolean; alias?: string } } = { - utf8: { - labelLong: 'UTF-8', - labelShort: 'UTF-8', - order: 1, - alias: 'utf8bom' - }, - utf8bom: { - labelLong: 'UTF-8 with BOM', - labelShort: 'UTF-8 with BOM', - encodeOnly: true, - order: 2, - alias: 'utf8' - }, - utf16le: { - labelLong: 'UTF-16 LE', - labelShort: 'UTF-16 LE', - order: 3 - }, - utf16be: { - labelLong: 'UTF-16 BE', - labelShort: 'UTF-16 BE', - order: 4 - }, - windows1252: { - labelLong: 'Western (Windows 1252)', - labelShort: 'Windows 1252', - order: 5 - }, - iso88591: { - labelLong: 'Western (ISO 8859-1)', - labelShort: 'ISO 8859-1', - order: 6 - }, - iso88593: { - labelLong: 'Western (ISO 8859-3)', - labelShort: 'ISO 8859-3', - order: 7 - }, - iso885915: { - labelLong: 'Western (ISO 8859-15)', - labelShort: 'ISO 8859-15', - order: 8 - }, - macroman: { - labelLong: 'Western (Mac Roman)', - labelShort: 'Mac Roman', - order: 9 - }, - cp437: { - labelLong: 'DOS (CP 437)', - labelShort: 'CP437', - order: 10 - }, - windows1256: { - labelLong: 'Arabic (Windows 1256)', - labelShort: 'Windows 1256', - order: 11 - }, - iso88596: { - labelLong: 'Arabic (ISO 8859-6)', - labelShort: 'ISO 8859-6', - order: 12 - }, - windows1257: { - labelLong: 'Baltic (Windows 1257)', - labelShort: 'Windows 1257', - order: 13 - }, - iso88594: { - labelLong: 'Baltic (ISO 8859-4)', - labelShort: 'ISO 8859-4', - order: 14 - }, - iso885914: { - labelLong: 'Celtic (ISO 8859-14)', - labelShort: 'ISO 8859-14', - order: 15 - }, - windows1250: { - labelLong: 'Central European (Windows 1250)', - labelShort: 'Windows 1250', - order: 16 - }, - iso88592: { - labelLong: 'Central European (ISO 8859-2)', - labelShort: 'ISO 8859-2', - order: 17 - }, - cp852: { - labelLong: 'Central European (CP 852)', - labelShort: 'CP 852', - order: 18 - }, - windows1251: { - labelLong: 'Cyrillic (Windows 1251)', - labelShort: 'Windows 1251', - order: 19 - }, - cp866: { - labelLong: 'Cyrillic (CP 866)', - labelShort: 'CP 866', - order: 20 - }, - iso88595: { - labelLong: 'Cyrillic (ISO 8859-5)', - labelShort: 'ISO 8859-5', - order: 21 - }, - koi8r: { - labelLong: 'Cyrillic (KOI8-R)', - labelShort: 'KOI8-R', - order: 22 - }, - koi8u: { - labelLong: 'Cyrillic (KOI8-U)', - labelShort: 'KOI8-U', - order: 23 - }, - iso885913: { - labelLong: 'Estonian (ISO 8859-13)', - labelShort: 'ISO 8859-13', - order: 24 - }, - windows1253: { - labelLong: 'Greek (Windows 1253)', - labelShort: 'Windows 1253', - order: 25 - }, - iso88597: { - labelLong: 'Greek (ISO 8859-7)', - labelShort: 'ISO 8859-7', - order: 26 - }, - windows1255: { - labelLong: 'Hebrew (Windows 1255)', - labelShort: 'Windows 1255', - order: 27 - }, - iso88598: { - labelLong: 'Hebrew (ISO 8859-8)', - labelShort: 'ISO 8859-8', - order: 28 - }, - iso885910: { - labelLong: 'Nordic (ISO 8859-10)', - labelShort: 'ISO 8859-10', - order: 29 - }, - iso885916: { - labelLong: 'Romanian (ISO 8859-16)', - labelShort: 'ISO 8859-16', - order: 30 - }, - windows1254: { - labelLong: 'Turkish (Windows 1254)', - labelShort: 'Windows 1254', - order: 31 - }, - iso88599: { - labelLong: 'Turkish (ISO 8859-9)', - labelShort: 'ISO 8859-9', - order: 32 - }, - windows1258: { - labelLong: 'Vietnamese (Windows 1258)', - labelShort: 'Windows 1258', - order: 33 - }, - gbk: { - labelLong: 'Simplified Chinese (GBK)', - labelShort: 'GBK', - order: 34 - }, - gb18030: { - labelLong: 'Simplified Chinese (GB18030)', - labelShort: 'GB18030', - order: 35 - }, - cp950: { - labelLong: 'Traditional Chinese (Big5)', - labelShort: 'Big5', - order: 36 - }, - big5hkscs: { - labelLong: 'Traditional Chinese (Big5-HKSCS)', - labelShort: 'Big5-HKSCS', - order: 37 - }, - shiftjis: { - labelLong: 'Japanese (Shift JIS)', - labelShort: 'Shift JIS', - order: 38 - }, - eucjp: { - labelLong: 'Japanese (EUC-JP)', - labelShort: 'EUC-JP', - order: 39 - }, - euckr: { - labelLong: 'Korean (EUC-KR)', - labelShort: 'EUC-KR', - order: 40 - }, - windows874: { - labelLong: 'Thai (Windows 874)', - labelShort: 'Windows 874', - order: 41 - }, - iso885911: { - labelLong: 'Latin/Thai (ISO 8859-11)', - labelShort: 'ISO 8859-11', - order: 42 - }, - koi8ru: { - labelLong: 'Cyrillic (KOI8-RU)', - labelShort: 'KOI8-RU', - order: 43 - }, - koi8t: { - labelLong: 'Tajik (KOI8-T)', - labelShort: 'KOI8-T', - order: 44 - }, - gb2312: { - labelLong: 'Simplified Chinese (GB 2312)', - labelShort: 'GB 2312', - order: 45 - }, - cp865: { - labelLong: 'Nordic DOS (CP 865)', - labelShort: 'CP 865', - order: 46 - }, - cp850: { - labelLong: 'Western European DOS (CP 850)', - labelShort: 'CP 850', - order: 47 - } -}; - export enum FileKind { FILE, FOLDER, @@ -1156,29 +758,17 @@ export enum FileKind { export const MIN_MAX_MEMORY_SIZE_MB = 2048; export const FALLBACK_MAX_MEMORY_SIZE_MB = 4096; -export function etag(mtime: number, size: number): string; -export function etag(mtime: number | undefined, size: number | undefined): string | undefined; -export function etag(mtime: number | undefined, size: number | undefined): string | undefined { - if (typeof size !== 'number' || typeof mtime !== 'number') { +/** + * A hint to disable etag checking for reading/writing. + */ +export const ETAG_DISABLED = ''; + +export function etag(stat: { mtime: number, size: number }): string; +export function etag(stat: { mtime: number | undefined, size: number | undefined }): string | undefined; +export function etag(stat: { mtime: number | undefined, size: number | undefined }): string | undefined { + if (typeof stat.size !== 'number' || typeof stat.mtime !== 'number') { return undefined; } - return mtime.toString(29) + size.toString(31); + return stat.mtime.toString(29) + stat.size.toString(31); } - - -// TODO@ben remove traces of legacy file service -export const ILegacyFileService = createDecorator('legacyFileService'); -export interface ILegacyFileService extends IDisposable { - _serviceBrand: any; - - encoding: IResourceEncodings; - - onAfterOperation: Event; - - registerProvider(scheme: string, provider: IFileSystemProvider): IDisposable; - - resolveContent(resource: URI, options?: IResolveContentOptions): Promise; - - resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise; -} \ No newline at end of file diff --git a/src/vs/platform/files/node/fileConstants.ts b/src/vs/platform/files/node/fileConstants.ts deleted file mode 100644 index 629a67e8499..00000000000 --- a/src/vs/platform/files/node/fileConstants.ts +++ /dev/null @@ -1,15 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// See https://github.com/Microsoft/vscode/issues/30180 -const WIN32_MAX_FILE_SIZE = 300 * 1024 * 1024; // 300 MB -const GENERAL_MAX_FILE_SIZE = 16 * 1024 * 1024 * 1024; // 16 GB - -// See https://github.com/v8/v8/blob/5918a23a3d571b9625e5cce246bdd5b46ff7cd8b/src/heap/heap.cc#L149 -const WIN32_MAX_HEAP_SIZE = 700 * 1024 * 1024; // 700 MB -const GENERAL_MAX_HEAP_SIZE = 700 * 2 * 1024 * 1024; // 1400 MB - -export const MAX_FILE_SIZE = process.arch === 'ia32' ? WIN32_MAX_FILE_SIZE : GENERAL_MAX_FILE_SIZE; -export const MAX_HEAP_SIZE = process.arch === 'ia32' ? WIN32_MAX_HEAP_SIZE : GENERAL_MAX_HEAP_SIZE; \ No newline at end of file diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index 8a9db6d0aca..4429cc10574 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -299,11 +299,11 @@ export class HistoryMainService implements IHistoryMainService { description = nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(dirname(workspace), this.environmentService)); args = `--folder-uri "${workspace.toString()}"`; } else { - description = nls.localize('codeWorkspace', "Code Workspace"); + description = nls.localize('workspaceDesc', "{0} {1}", getBaseLabel(workspace.configPath), getPathLabel(dirname(workspace.configPath), this.environmentService)); args = `--file-uri "${workspace.configPath.toString()}"`; } - return { + return { type: 'task', title, description, diff --git a/src/vs/platform/instantiation/common/instantiation.ts b/src/vs/platform/instantiation/common/instantiation.ts index 608480b1aac..df147f419c3 100644 --- a/src/vs/platform/instantiation/common/instantiation.ts +++ b/src/vs/platform/instantiation/common/instantiation.ts @@ -125,7 +125,7 @@ function storeServiceDependency(id: Function, target: Function, index: number, o /** * A *only* valid way to create a {{ServiceIdentifier}}. */ -export function createDecorator(serviceId: string): { (...args: any[]): void; type: T; } { +export function createDecorator(serviceId: string): ServiceIdentifier { if (_util.serviceIds.has(serviceId)) { return _util.serviceIds.get(serviceId)!; diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index 76ab881a236..6b3e8d76c17 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -220,7 +220,7 @@ export class InstantiationService implements IInstantiationService { // Return a proxy object that's backed by an idle value. That // strategy is to instantiate services in our idle time or when actually // needed but not when injected into a consumer - const idle = new IdleValue(() => this._createInstance(ctor, args, _trace)); + const idle = new IdleValue(() => this._createInstance(ctor, args, _trace)); return new Proxy(Object.create(null), { get(_target: T, prop: PropertyKey): any { return idle.getValue()[prop]; diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index 4e9f67f367b..33e1efbebb2 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -17,7 +17,6 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { withNullAsUndefined } from 'vs/base/common/types'; interface CurrentChord { keypress: string; @@ -96,11 +95,11 @@ export abstract class AbstractKeybindingService extends Disposable implements IK } public lookupKeybinding(commandId: string): ResolvedKeybinding | undefined { - let result = this._getResolver().lookupPrimaryKeybinding(commandId); + const result = this._getResolver().lookupPrimaryKeybinding(commandId); if (!result) { return undefined; } - return withNullAsUndefined(result.resolvedKeybinding); + return result.resolvedKeybinding; } public dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean { diff --git a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts index c7cf8f7af1e..00b05e05213 100644 --- a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts +++ b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts @@ -10,7 +10,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; export class ResolvedKeybindingItem { _resolvedKeybindingItemBrand: void; - public readonly resolvedKeybinding: ResolvedKeybinding | null; + public readonly resolvedKeybinding: ResolvedKeybinding | undefined; public readonly keypressParts: string[]; public readonly bubble: boolean; public readonly command: string | null; @@ -18,7 +18,7 @@ export class ResolvedKeybindingItem { public readonly when: ContextKeyExpr | undefined; public readonly isDefault: boolean; - constructor(resolvedKeybinding: ResolvedKeybinding | null, command: string | null, commandArgs: any, when: ContextKeyExpr | undefined, isDefault: boolean) { + constructor(resolvedKeybinding: ResolvedKeybinding | undefined, command: string | null, commandArgs: any, when: ContextKeyExpr | undefined, isDefault: boolean) { this.resolvedKeybinding = resolvedKeybinding; this.keypressParts = resolvedKeybinding ? removeElementsAfterNulls(resolvedKeybinding.getDispatchParts()) : []; this.bubble = (command ? command.charCodeAt(0) === CharCode.Caret : false); diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index d2b97151250..72c2062202c 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -180,7 +180,7 @@ suite('AbstractKeybindingService', () => { }); function kbItem(keybinding: number, command: string, when?: ContextKeyExpr): ResolvedKeybindingItem { - const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS) : null); + const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS) : undefined); return new ResolvedKeybindingItem( resolvedKeybinding, command, diff --git a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts index 0174bcefd8b..a4488cffb17 100644 --- a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts @@ -21,7 +21,7 @@ function createContext(ctx: any) { suite('KeybindingResolver', () => { function kbItem(keybinding: number, command: string, commandArgs: any, when: ContextKeyExpr, isDefault: boolean): ResolvedKeybindingItem { - const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS) : null); + const resolvedKeybinding = (keybinding !== 0 ? new USLayoutResolvedKeybinding(createKeybinding(keybinding, OS)!, OS) : undefined); return new ResolvedKeybindingItem( resolvedKeybinding, command, diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 73704291d8e..d9d37956a76 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -266,7 +266,7 @@ export class WorkbenchList extends List { ...computeStyles(themeService.getTheme(), defaultListStyles), ...workbenchListOptions, horizontalScrolling - } as IListOptions + } ); this.disposables.push(workbenchListOptionsDisposable); @@ -348,7 +348,7 @@ export class WorkbenchPagedList extends PagedList { ...computeStyles(themeService.getTheme(), defaultListStyles), ...workbenchListOptions, horizontalScrolling - } as IListOptions + } ); this.disposables = [workbenchListOptionsDisposable]; @@ -689,7 +689,7 @@ export class TreeResourceNavigator2 extends Disposable { super(); this.options = { - ...{ + ...{ openOnSelection: true }, ...(options || {}) diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index c3830290c6f..f3e60f24455 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -6,6 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IAction } from 'vs/base/common/actions'; export const IProgressService = createDecorator('progressService'); @@ -42,6 +43,12 @@ export interface IProgressOptions { cancellable?: boolean; } +export interface IProgressNotificationOptions extends IProgressOptions { + location: ProgressLocation.Notification; + primaryActions?: IAction[]; + secondaryActions?: IAction[]; +} + export interface IProgressStep { message?: string; increment?: number; diff --git a/src/vs/platform/registry/common/platform.ts b/src/vs/platform/registry/common/platform.ts index 71c3909e02b..a81c001af82 100644 --- a/src/vs/platform/registry/common/platform.ts +++ b/src/vs/platform/registry/common/platform.ts @@ -54,4 +54,4 @@ class RegistryImpl implements IRegistry { } } -export const Registry = new RegistryImpl(); +export const Registry: IRegistry = new RegistryImpl(); diff --git a/src/vs/platform/remote/browser/browserWebSocketFactory.ts b/src/vs/platform/remote/browser/browserWebSocketFactory.ts new file mode 100644 index 00000000000..6d9ecbcf5a6 --- /dev/null +++ b/src/vs/platform/remote/browser/browserWebSocketFactory.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWebSocketFactory, IConnectCallback } from 'vs/platform/remote/common/remoteAgentConnection'; +import { ISocket } from 'vs/base/parts/ipc/common/ipc.net'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { onUnexpectedError } from 'vs/base/common/errors'; + +class BrowserSocket implements ISocket { + public readonly socket: WebSocket; + + constructor(socket: WebSocket) { + this.socket = socket; + } + + public dispose(): void { + this.socket.close(); + } + + public onData(_listener: (e: VSBuffer) => void): IDisposable { + const fileReader = new FileReader(); + const queue: Blob[] = []; + let isReading = false; + fileReader.onload = function (event) { + isReading = false; + const buff = (event.target).result; + + try { + _listener(VSBuffer.wrap(new Uint8Array(buff))); + } catch (err) { + onUnexpectedError(err); + } + + if (queue.length > 0) { + enqueue(queue.shift()!); + } + }; + const enqueue = (blob: Blob) => { + if (isReading) { + queue.push(blob); + return; + } + isReading = true; + fileReader.readAsArrayBuffer(blob); + }; + const listener = (e: MessageEvent) => { + enqueue(e.data); + }; + this.socket.addEventListener('message', listener); + return { + dispose: () => this.socket.removeEventListener('message', listener) + }; + } + + public onClose(listener: () => void): IDisposable { + this.socket.addEventListener('close', listener); + return { + dispose: () => this.socket.removeEventListener('close', listener) + }; + } + + public onEnd(listener: () => void): IDisposable { + return Disposable.None; + } + + public write(buffer: VSBuffer): void { + this.socket.send(buffer.buffer); + } + + public end(): void { + this.socket.close(); + } + +} + +export const browserWebSocketFactory = new class implements IWebSocketFactory { + connect(host: string, port: number, query: string, callback: IConnectCallback): void { + const errorListener = (err: any) => callback(err, undefined); + const socket = new WebSocket(`ws://${host}:${port}/?${query}&skipWebSocketFrames=false`); + socket.onopen = function (event) { + socket.removeEventListener('error', errorListener); + callback(undefined, new BrowserSocket(socket)); + }; + socket.addEventListener('error', errorListener); + } +}; diff --git a/src/vs/platform/remote/common/remoteAgentConnection.ts b/src/vs/platform/remote/common/remoteAgentConnection.ts index 4136f3de649..68278743fd3 100644 --- a/src/vs/platform/remote/common/remoteAgentConnection.ts +++ b/src/vs/platform/remote/common/remoteAgentConnection.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Client, PersistentProtocol, ISocket } from 'vs/base/parts/ipc/common/ipc.net'; +import { Client, PersistentProtocol, ISocket, ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net'; import { generateUuid } from 'vs/base/common/uuid'; import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; +import { RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; export const enum ConnectionType { Management = 1, @@ -148,6 +150,12 @@ export async function connectRemoteAgentTunnel(options: IConnectionOptions, tunn return protocol; } +function sleep(seconds: number): Promise { + return new Promise((resolve, reject) => { + setTimeout(resolve, seconds * 1000); + }); +} + export const enum PersistenConnectionEventType { ConnectionLost, ReconnectionWait, @@ -184,11 +192,99 @@ abstract class PersistentConnection extends Disposable { public readonly reconnectionToken: string; public readonly protocol: PersistentProtocol; + private _isReconnecting: boolean; + private _permanentFailure: boolean; + constructor(options: IConnectionOptions, reconnectionToken: string, protocol: PersistentProtocol) { super(); this._options = options; this.reconnectionToken = reconnectionToken; this.protocol = protocol; + this._isReconnecting = false; + this._permanentFailure = false; + + this._register(protocol.onSocketClose(() => this._beginReconnecting())); + this._register(protocol.onSocketTimeout(() => this._beginReconnecting())); + } + + private async _beginReconnecting(): Promise { + // Only have one reconnection loop active at a time. + if (this._isReconnecting) { + return; + } + try { + this._isReconnecting = true; + await this._runReconnectingLoop(); + } finally { + this._isReconnecting = false; + } + } + + private async _runReconnectingLoop(): Promise { + if (this._permanentFailure) { + // no more attempts! + return; + } + this._onDidStateChange.fire(new ConnectionLostEvent()); + const TIMES = [5, 5, 10, 10, 10, 10, 10, 30]; + const disconnectStartTime = Date.now(); + let attempt = -1; + do { + attempt++; + const waitTime = (attempt < TIMES.length ? TIMES[attempt] : TIMES[TIMES.length - 1]); + try { + this._onDidStateChange.fire(new ReconnectionWaitEvent(waitTime)); + await sleep(waitTime); + + // connection was lost, let's try to re-establish it + this._onDidStateChange.fire(new ReconnectionRunningEvent()); + const simpleOptions = await resolveConnectionOptions(this._options, this.reconnectionToken, this.protocol); + await connectWithTimeLimit(this._reconnect(simpleOptions), 30 * 1000 /*30s*/); + this._onDidStateChange.fire(new ConnectionGainEvent()); + + break; + } catch (err) { + if (err.code === 'VSCODE_CONNECTION_ERROR') { + console.error(`A permanent connection error occurred`); + console.error(err); + this._permanentFailure = true; + this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent()); + this.protocol.acceptDisconnect(); + break; + } + if (Date.now() - disconnectStartTime > ProtocolConstants.ReconnectionGraceTime) { + console.error(`Giving up after reconnection grace time has expired!`); + this._permanentFailure = true; + this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent()); + this.protocol.acceptDisconnect(); + break; + } + if (RemoteAuthorityResolverError.isTemporarilyNotAvailable(err)) { + console.warn(`A temporarily not available error occured while trying to reconnect:`); + console.warn(err); + // try again! + continue; + } + if ((err.code === 'ETIMEDOUT' || err.code === 'ENETUNREACH' || err.code === 'ECONNREFUSED' || err.code === 'ECONNRESET') && err.syscall === 'connect') { + console.warn(`A connect error occured while trying to reconnect:`); + console.warn(err); + // try again! + continue; + } + if (isPromiseCanceledError(err)) { + console.warn(`A cancel error occured while trying to reconnect:`); + console.warn(err); + // try again! + continue; + } + console.error(`An error occured while trying to reconnect:`); + console.error(err); + this._permanentFailure = true; + this._onDidStateChange.fire(new ReconnectionPermanentFailureEvent()); + this.protocol.acceptDisconnect(); + break; + } + } while (!this._permanentFailure); } protected abstract _reconnect(options: ISimpleConnectionOptions): Promise; @@ -227,6 +323,24 @@ export class ExtensionHostPersistentConnection extends PersistentConnection { } } +function connectWithTimeLimit(p: Promise, timeLimit: number): Promise { + return new Promise((resolve, reject) => { + let timeout = setTimeout(() => { + const err: any = new Error('Time limit reached'); + err.code = 'ETIMEDOUT'; + err.syscall = 'connect'; + reject(err); + }, timeLimit); + p.then(() => { + clearTimeout(timeout); + resolve(); + }, (err) => { + clearTimeout(timeout); + reject(err); + }); + }); +} + function getErrorFromMessage(msg: any): Error | null { if (msg && msg.type === 'error') { const error = new Error(`Connection error: ${msg.reason}`); diff --git a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts index d3b15083c23..fdc022b18d9 100644 --- a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts +++ b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts @@ -8,7 +8,7 @@ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle' import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IFileChange, IFileSystemProvider, IStat, IWatchOptions } from 'vs/platform/files/common/files'; +import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, IFileChange, IFileSystemProvider, IStat, IWatchOptions, FileOpenOptions } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { OperatingSystem } from 'vs/base/common/platform'; @@ -50,7 +50,7 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF setCaseSensitive(isCaseSensitive: boolean) { let capabilities = ( - FileSystemProviderCapabilities.FileReadWrite + FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.FileFolderCopy ); @@ -68,14 +68,28 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF return this.channel.call('stat', [resource]); } - async readFile(resource: URI): Promise { - const buff = await this.channel.call('readFile', [resource]); - - return buff.buffer; + open(resource: URI, opts: FileOpenOptions): Promise { + return this.channel.call('open', [resource, opts]); } - writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { - return this.channel.call('writeFile', [resource, VSBuffer.wrap(content), opts]); + close(fd: number): Promise { + return this.channel.call('close', [fd]); + } + + async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + const [bytes, bytesRead]: [VSBuffer, number] = await this.channel.call('read', [fd, pos, length]); + + // copy back the data that was written into the buffer on the remote + // side. we need to do this because buffers are not referenced by + // pointer, but only by value and as such cannot be directly written + // to from the other process. + data.set(bytes.buffer.slice(0, bytesRead), offset); + + return bytesRead; + } + + write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + return this.channel.call('write', [fd, pos, VSBuffer.wrap(data), offset, length]); } delete(resource: URI, opts: FileDeleteOptions): Promise { diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index 6c5063716e9..fac355a4917 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -13,6 +13,50 @@ export interface ResolvedAuthority { readonly port: number; } +export enum RemoteAuthorityResolverErrorCode { + Unknown = 'Unknown', + NotAvailable = 'NotAvailable', + TemporarilyNotAvailable = 'TemporarilyNotAvailable', +} + +export class RemoteAuthorityResolverError extends Error { + + public static isHandledNotAvailable(err: any): boolean { + if (err instanceof RemoteAuthorityResolverError) { + if (err._code === RemoteAuthorityResolverErrorCode.NotAvailable && err._detail === true) { + return true; + } + } + + return this.isTemporarilyNotAvailable(err); + } + + public static isTemporarilyNotAvailable(err: any): boolean { + if (err instanceof RemoteAuthorityResolverError) { + return err._code === RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable; + } + return false; + } + + public readonly _message: string | undefined; + public readonly _code: RemoteAuthorityResolverErrorCode; + public readonly _detail: any; + + constructor(message?: string, code: RemoteAuthorityResolverErrorCode = RemoteAuthorityResolverErrorCode.Unknown, detail?: any) { + super(message); + + this._message = message; + this._code = code; + this._detail = detail; + + // workaround when extending builtin objects and when compiling to ES5, see: + // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work + if (typeof (Object).setPrototypeOf === 'function') { + (Object).setPrototypeOf(this, RemoteAuthorityResolverError.prototype); + } + } +} + export interface IRemoteAuthorityResolverService { _serviceBrand: any; diff --git a/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts index f69fe2bb7f4..0a97af69780 100644 --- a/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts @@ -40,8 +40,10 @@ export class RemoteAuthorityResolverService implements IRemoteAuthorityResolverS } clearResolvedAuthority(authority: string): void { - this._resolveAuthorityRequests[authority].reject(errors.canceled()); - delete this._resolveAuthorityRequests[authority]; + if (this._resolveAuthorityRequests[authority]) { + this._resolveAuthorityRequests[authority].reject(errors.canceled()); + delete this._resolveAuthorityRequests[authority]; + } } setResolvedAuthority(resolvedAuthority: ResolvedAuthority) { diff --git a/src/vs/platform/telemetry/node/commonProperties.ts b/src/vs/platform/telemetry/node/commonProperties.ts index de2cbf7898b..43abe6892cc 100644 --- a/src/vs/platform/telemetry/node/commonProperties.ts +++ b/src/vs/platform/telemetry/node/commonProperties.ts @@ -8,7 +8,7 @@ import * as os from 'os'; import * as uuid from 'vs/base/common/uuid'; import { readFile } from 'vs/base/node/pfs'; -export function resolveCommonProperties(commit: string | undefined, version: string | undefined, machineId: string | undefined, installSourcePath: string): Promise<{ [name: string]: string | undefined; }> { +export function resolveCommonProperties(commit: string | undefined, version: string | undefined, machineId: string | undefined, installSourcePath: string, product?: string): Promise<{ [name: string]: string | undefined; }> { const result: { [name: string]: string | undefined; } = Object.create(null); // __GDPR__COMMON__ "common.machineId" : { "endPoint": "MacAddressHash", "classification": "EndUserPseudonymizedInformation", "purpose": "FeatureInsight" } result['common.machineId'] = machineId; @@ -26,6 +26,8 @@ export function resolveCommonProperties(commit: string | undefined, version: str result['common.nodePlatform'] = process.platform; // __GDPR__COMMON__ "common.nodeArch" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } result['common.nodeArch'] = process.arch; + // __GDPR__COMMON__ "common.product" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + result['common.product'] = product || 'desktop'; // dynamic properties which value differs on each call let seq = 0; diff --git a/src/vs/platform/telemetry/node/errorTelemetry.ts b/src/vs/platform/telemetry/node/errorTelemetry.ts index 9cf146e5f6e..60f1633ee58 100644 --- a/src/vs/platform/telemetry/node/errorTelemetry.ts +++ b/src/vs/platform/telemetry/node/errorTelemetry.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { onUnexpectedError } from 'vs/base/common/errors'; +import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import BaseErrorTelemetry from '../common/errorTelemetry'; export default class ErrorTelemetry extends BaseErrorTelemetry { protected installErrorListeners(): void { + setUnexpectedErrorHandler(err => console.error(err)); + // Print a console message when rejection isn't handled within N seconds. For details: // see https://nodejs.org/api/process.html#process_event_unhandledrejection // and https://nodejs.org/api/process.html#process_event_rejectionhandled diff --git a/src/vs/platform/update/node/update.config.contribution.ts b/src/vs/platform/update/node/update.config.contribution.ts index ed288dce05a..541ae043f97 100644 --- a/src/vs/platform/update/node/update.config.contribution.ts +++ b/src/vs/platform/update/node/update.config.contribution.ts @@ -44,6 +44,7 @@ configurationRegistry.registerConfiguration({ 'update.showReleaseNotes': { type: 'boolean', default: true, + scope: ConfigurationScope.APPLICATION, description: localize('showReleaseNotes', "Show Release Notes after an update. The Release Notes are fetched from a Microsoft online service."), tags: ['usesOnlineServices'] } diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 437bf99e163..f636cd1b8e3 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -355,7 +355,7 @@ export const enum ReadyState { export interface IPath extends IPathData { - // the file path to open within a Code instance + // the file path to open within the instance fileUri?: URI; } @@ -371,7 +371,7 @@ export interface IPathsToWaitForData { export interface IPathData { - // the file path to open within a Code instance + // the file path to open within the instance fileUri?: UriComponents; // the line number in the file path to open @@ -379,11 +379,15 @@ export interface IPathData { // the column number in the file path to open columnNumber?: number; + + // a hint that the file exists. if true, the + // file exists, if false it does not. with + // undefined the state is unknown. + exists?: boolean; } export interface IOpenFileRequest { - filesToOpen?: IPathData[]; - filesToCreate?: IPathData[]; + filesToOpenOrCreate?: IPathData[]; filesToDiff?: IPathData[]; filesToWait?: IPathsToWaitForData; termProgram?: string; @@ -427,8 +431,7 @@ export interface IWindowConfiguration extends ParsedArgs { perfWindowLoadTime?: number; perfEntries: ExportData; - filesToOpen?: IPath[]; - filesToCreate?: IPath[]; + filesToOpenOrCreate?: IPath[]; filesToDiff?: IPath[]; filesToWait?: IPathsToWaitFor; termProgram?: string; diff --git a/src/vs/platform/windows/electron-browser/windowsService.ts b/src/vs/platform/windows/electron-browser/windowsService.ts index 319d1584675..8c5ed69a4ef 100644 --- a/src/vs/platform/windows/electron-browser/windowsService.ts +++ b/src/vs/platform/windows/electron-browser/windowsService.ts @@ -74,9 +74,11 @@ export class WindowsService implements IWindowsService { return this.channel.call('closeWorkspace', windowId); } - enterWorkspace(windowId: number, path: URI): Promise { + enterWorkspace(windowId: number, path: URI): Promise { return this.channel.call('enterWorkspace', [windowId, path]).then((result: IEnterWorkspaceResult) => { - result.workspace = reviveWorkspaceIdentifier(result.workspace); + if (result) { + result.workspace = reviveWorkspaceIdentifier(result.workspace); + } return result; }); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 1fc3e5301ba..974b39fdf28 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -176,7 +176,7 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable async getRecentlyOpened(windowId: number): Promise { this.logService.trace('windowsService#getRecentlyOpened', windowId); - return this.withWindow(windowId, codeWindow => this.historyService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpen), () => this.historyService.getRecentlyOpened())!; + return this.withWindow(windowId, codeWindow => this.historyService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpenOrCreate), () => this.historyService.getRecentlyOpened())!; } async newWindowTab(): Promise { diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index 418d131224e..b5f3e64c135 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -4,14 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { isAbsolute } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { TernarySearchTree } from 'vs/base/common/map'; import { Event } from 'vs/base/common/event'; import { IWorkspaceIdentifier, IStoredWorkspaceFolder, isRawFileWorkspaceFolder, isRawUriWorkspaceFolder, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { coalesce, distinct } from 'vs/base/common/arrays'; -import { isLinux } from 'vs/base/common/platform'; export const IWorkspaceContextService = createDecorator('contextService'); @@ -225,17 +222,21 @@ export class WorkspaceFolder implements IWorkspaceFolder { } } -export function toWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], relativeTo?: URI): WorkspaceFolder[] { - let workspaceFolders = parseWorkspaceFolders(configuredFolders, relativeTo); - return ensureUnique(coalesce(workspaceFolders)) - .map(({ uri, raw, name }, index) => new WorkspaceFolder({ uri, name: name || resources.basenameOrAuthority(uri), index }, raw)); +export function toWorkspaceFolder(resource: URI): WorkspaceFolder { + return new WorkspaceFolder({ uri: resource, index: 0, name: resources.basenameOrAuthority(resource) }, { uri: resource.toString() }); } -function parseWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], relativeTo: URI | undefined): Array { - return configuredFolders.map((configuredFolder, index) => { +export function toWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], workspaceConfigFile: URI): WorkspaceFolder[] { + let result: WorkspaceFolder[] = []; + let seen: { [uri: string]: boolean } = Object.create(null); + + const relativeTo = resources.dirname(workspaceConfigFile); + for (let configuredFolder of configuredFolders) { let uri: URI | null = null; if (isRawFileWorkspaceFolder(configuredFolder)) { - uri = toUri(configuredFolder.path, relativeTo); + if (configuredFolder.path) { + uri = resources.resolvePath(relativeTo, configuredFolder.path); + } } else if (isRawUriWorkspaceFolder(configuredFolder)) { try { uri = URI.parse(configuredFolder.uri); @@ -248,25 +249,16 @@ function parseWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], rela // ignore } } - if (!uri) { - return undefined; - } - return new WorkspaceFolder({ uri, name: configuredFolder.name! /*is ensured in caller*/, index }, configuredFolder); - }); -} + if (uri) { + // remove duplicates + let comparisonKey = resources.getComparisonKey(uri); + if (!seen[comparisonKey]) { + seen[comparisonKey] = true; -function toUri(path: string, relativeTo: URI | undefined): URI | null { - if (path) { - if (isAbsolute(path)) { - return URI.file(path); - } - if (relativeTo) { - return resources.joinPath(relativeTo, path); + const name = configuredFolder.name || resources.basenameOrAuthority(uri); + result.push(new WorkspaceFolder({ uri, name, index: result.length }, configuredFolder)); + } } } - return null; -} - -function ensureUnique(folders: WorkspaceFolder[]): WorkspaceFolder[] { - return distinct(folders, folder => isLinux ? folder.uri.toString() : folder.uri.toString().toLowerCase()); -} + return result; +} \ No newline at end of file diff --git a/src/vs/platform/workspace/test/common/testWorkspace.ts b/src/vs/platform/workspace/test/common/testWorkspace.ts index 2378eca9c79..75762fed482 100644 --- a/src/vs/platform/workspace/test/common/testWorkspace.ts +++ b/src/vs/platform/workspace/test/common/testWorkspace.ts @@ -4,15 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; +import { Workspace, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { isWindows } from 'vs/base/common/platform'; const wsUri = URI.file(isWindows ? 'C:\\testWorkspace' : '/testWorkspace'); export const TestWorkspace = testWorkspace(wsUri); export function testWorkspace(resource: URI): Workspace { - return new Workspace( - resource.toString(), - toWorkspaceFolders([{ path: resource.fsPath }]) - ); + return new Workspace(resource.toString(), [toWorkspaceFolder(resource)]); } diff --git a/src/vs/platform/workspace/test/common/workspace.test.ts b/src/vs/platform/workspace/test/common/workspace.test.ts index b4dbde7ed97..63dff7e2bf5 100644 --- a/src/vs/platform/workspace/test/common/workspace.test.ts +++ b/src/vs/platform/workspace/test/common/workspace.test.ts @@ -4,15 +4,30 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import * as path from 'vs/base/common/path'; import { Workspace, toWorkspaceFolders, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; import { IRawFileWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; +import { isWindows } from 'vs/base/common/platform'; suite('Workspace', () => { + const fileFolder = isWindows ? 'c:\\src' : '/src'; + const abcFolder = isWindows ? 'c:\\abc' : '/abc'; + + const testFolderUri = URI.file(path.join(fileFolder, 'test')); + const mainFolderUri = URI.file(path.join(fileFolder, 'main')); + const test1FolderUri = URI.file(path.join(fileFolder, 'test1')); + const test2FolderUri = URI.file(path.join(fileFolder, 'test2')); + const test3FolderUri = URI.file(path.join(fileFolder, 'test3')); + const abcTest1FolderUri = URI.file(path.join(abcFolder, 'test1')); + const abcTest3FolderUri = URI.file(path.join(abcFolder, 'test3')); + + const workspaceConfigUri = URI.file(path.join(fileFolder, 'test.code-workspace')); + test('getFolder returns the folder with given uri', () => { - const expected = new WorkspaceFolder({ uri: URI.file('/src/test'), name: '', index: 2 }); - let testObject = new Workspace('', [new WorkspaceFolder({ uri: URI.file('/src/main'), name: '', index: 0 }), expected, new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 2 })]); + const expected = new WorkspaceFolder({ uri: testFolderUri, name: '', index: 2 }); + let testObject = new Workspace('', [new WorkspaceFolder({ uri: mainFolderUri, name: '', index: 0 }), expected, new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 2 })]); const actual = testObject.getFolder(expected.uri); @@ -20,172 +35,172 @@ suite('Workspace', () => { }); test('getFolder returns the folder if the uri is sub', () => { - const expected = new WorkspaceFolder({ uri: URI.file('/src/test'), name: '', index: 0 }); - let testObject = new Workspace('', [expected, new WorkspaceFolder({ uri: URI.file('/src/main'), name: '', index: 1 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 2 })]); + const expected = new WorkspaceFolder({ uri: testFolderUri, name: '', index: 0 }); + let testObject = new Workspace('', [expected, new WorkspaceFolder({ uri: mainFolderUri, name: '', index: 1 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 2 })]); - const actual = testObject.getFolder(URI.file('/src/test/a')); + const actual = testObject.getFolder(URI.file(path.join(fileFolder, 'test/a'))); assert.equal(actual, expected); }); test('getFolder returns the closest folder if the uri is sub', () => { - const expected = new WorkspaceFolder({ uri: URI.file('/src/test'), name: '', index: 2 }); - let testObject = new Workspace('', [new WorkspaceFolder({ uri: URI.file('/src/main'), name: '', index: 0 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 1 }), expected]); + const expected = new WorkspaceFolder({ uri: testFolderUri, name: '', index: 2 }); + let testObject = new Workspace('', [new WorkspaceFolder({ uri: mainFolderUri, name: '', index: 0 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 1 }), expected]); - const actual = testObject.getFolder(URI.file('/src/test/a')); + const actual = testObject.getFolder(URI.file(path.join(fileFolder, 'test/a'))); assert.equal(actual, expected); }); test('getFolder returns null if the uri is not sub', () => { - let testObject = new Workspace('', [new WorkspaceFolder({ uri: URI.file('/src/test'), name: '', index: 0 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 1 })]); + let testObject = new Workspace('', [new WorkspaceFolder({ uri: testFolderUri, name: '', index: 0 }), new WorkspaceFolder({ uri: URI.file('/src/code'), name: '', index: 1 })]); - const actual = testObject.getFolder(URI.file('/src/main/a')); + const actual = testObject.getFolder(URI.file(path.join(fileFolder, 'main/a'))); assert.equal(actual, undefined); }); test('toWorkspaceFolders with single absolute folder', () => { - const actual = toWorkspaceFolders([{ path: '/src/test' }]); + const actual = toWorkspaceFolders([{ path: '/src/test' }], workspaceConfigUri); assert.equal(actual.length, 1); - assert.equal(actual[0].uri.fsPath, URI.file('/src/test').fsPath); + assert.equal(actual[0].uri.fsPath, testFolderUri.fsPath); assert.equal((actual[0].raw).path, '/src/test'); assert.equal(actual[0].index, 0); assert.equal(actual[0].name, 'test'); }); test('toWorkspaceFolders with single relative folder', () => { - const actual = toWorkspaceFolders([{ path: './test' }], URI.file('src')); + const actual = toWorkspaceFolders([{ path: './test' }], workspaceConfigUri); assert.equal(actual.length, 1); - assert.equal(actual[0].uri.fsPath, URI.file('/src/test').fsPath); + assert.equal(actual[0].uri.fsPath, testFolderUri.fsPath); assert.equal((actual[0].raw).path, './test'); assert.equal(actual[0].index, 0); assert.equal(actual[0].name, 'test'); }); test('toWorkspaceFolders with single absolute folder with name', () => { - const actual = toWorkspaceFolders([{ path: '/src/test', name: 'hello' }]); + const actual = toWorkspaceFolders([{ path: '/src/test', name: 'hello' }], workspaceConfigUri); assert.equal(actual.length, 1); - assert.equal(actual[0].uri.fsPath, URI.file('/src/test').fsPath); + assert.equal(actual[0].uri.fsPath, testFolderUri.fsPath); assert.equal((actual[0].raw).path, '/src/test'); assert.equal(actual[0].index, 0); assert.equal(actual[0].name, 'hello'); }); test('toWorkspaceFolders with multiple unique absolute folders', () => { - const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test3' }, { path: '/src/test1' }]); + const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test3' }, { path: '/src/test1' }], workspaceConfigUri); assert.equal(actual.length, 3); - assert.equal(actual[0].uri.fsPath, URI.file('/src/test2').fsPath); + assert.equal(actual[0].uri.fsPath, test2FolderUri.fsPath); assert.equal((actual[0].raw).path, '/src/test2'); assert.equal(actual[0].index, 0); assert.equal(actual[0].name, 'test2'); - assert.equal(actual[1].uri.fsPath, URI.file('/src/test3').fsPath); + assert.equal(actual[1].uri.fsPath, test3FolderUri.fsPath); assert.equal((actual[1].raw).path, '/src/test3'); assert.equal(actual[1].index, 1); assert.equal(actual[1].name, 'test3'); - assert.equal(actual[2].uri.fsPath, URI.file('/src/test1').fsPath); + assert.equal(actual[2].uri.fsPath, test1FolderUri.fsPath); assert.equal((actual[2].raw).path, '/src/test1'); assert.equal(actual[2].index, 2); assert.equal(actual[2].name, 'test1'); }); test('toWorkspaceFolders with multiple unique absolute folders with names', () => { - const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test3', name: 'noName' }, { path: '/src/test1' }]); + const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test3', name: 'noName' }, { path: '/src/test1' }], workspaceConfigUri); assert.equal(actual.length, 3); - assert.equal(actual[0].uri.fsPath, URI.file('/src/test2').fsPath); + assert.equal(actual[0].uri.fsPath, test2FolderUri.fsPath); assert.equal((actual[0].raw).path, '/src/test2'); assert.equal(actual[0].index, 0); assert.equal(actual[0].name, 'test2'); - assert.equal(actual[1].uri.fsPath, URI.file('/src/test3').fsPath); + assert.equal(actual[1].uri.fsPath, test3FolderUri.fsPath); assert.equal((actual[1].raw).path, '/src/test3'); assert.equal(actual[1].index, 1); assert.equal(actual[1].name, 'noName'); - assert.equal(actual[2].uri.fsPath, URI.file('/src/test1').fsPath); + assert.equal(actual[2].uri.fsPath, test1FolderUri.fsPath); assert.equal((actual[2].raw).path, '/src/test1'); assert.equal(actual[2].index, 2); assert.equal(actual[2].name, 'test1'); }); test('toWorkspaceFolders with multiple unique absolute and relative folders', () => { - const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/abc/test3', name: 'noName' }, { path: './test1' }], URI.file('src')); + const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/abc/test3', name: 'noName' }, { path: './test1' }], workspaceConfigUri); assert.equal(actual.length, 3); - assert.equal(actual[0].uri.fsPath, URI.file('/src/test2').fsPath); + assert.equal(actual[0].uri.fsPath, test2FolderUri.fsPath); assert.equal((actual[0].raw).path, '/src/test2'); assert.equal(actual[0].index, 0); assert.equal(actual[0].name, 'test2'); - assert.equal(actual[1].uri.fsPath, URI.file('/abc/test3').fsPath); + assert.equal(actual[1].uri.fsPath, abcTest3FolderUri.fsPath); assert.equal((actual[1].raw).path, '/abc/test3'); assert.equal(actual[1].index, 1); assert.equal(actual[1].name, 'noName'); - assert.equal(actual[2].uri.fsPath, URI.file('/src/test1').fsPath); + assert.equal(actual[2].uri.fsPath, test1FolderUri.fsPath); assert.equal((actual[2].raw).path, './test1'); assert.equal(actual[2].index, 2); assert.equal(actual[2].name, 'test1'); }); test('toWorkspaceFolders with multiple absolute folders with duplicates', () => { - const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test2', name: 'noName' }, { path: '/src/test1' }]); + const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test2', name: 'noName' }, { path: '/src/test1' }], workspaceConfigUri); assert.equal(actual.length, 2); - assert.equal(actual[0].uri.fsPath, URI.file('/src/test2').fsPath); + assert.equal(actual[0].uri.fsPath, test2FolderUri.fsPath); assert.equal((actual[0].raw).path, '/src/test2'); assert.equal(actual[0].index, 0); assert.equal(actual[0].name, 'test2'); - assert.equal(actual[1].uri.fsPath, URI.file('/src/test1').fsPath); + assert.equal(actual[1].uri.fsPath, test1FolderUri.fsPath); assert.equal((actual[1].raw).path, '/src/test1'); assert.equal(actual[1].index, 1); assert.equal(actual[1].name, 'test1'); }); test('toWorkspaceFolders with multiple absolute and relative folders with duplicates', () => { - const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test3', name: 'noName' }, { path: './test3' }, { path: '/abc/test1' }], URI.file('src')); + const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '/src/test3', name: 'noName' }, { path: './test3' }, { path: '/abc/test1' }], workspaceConfigUri); assert.equal(actual.length, 3); - assert.equal(actual[0].uri.fsPath, URI.file('/src/test2').fsPath); + assert.equal(actual[0].uri.fsPath, test2FolderUri.fsPath); assert.equal((actual[0].raw).path, '/src/test2'); assert.equal(actual[0].index, 0); assert.equal(actual[0].name, 'test2'); - assert.equal(actual[1].uri.fsPath, URI.file('/src/test3').fsPath); + assert.equal(actual[1].uri.fsPath, test3FolderUri.fsPath); assert.equal((actual[1].raw).path, '/src/test3'); assert.equal(actual[1].index, 1); assert.equal(actual[1].name, 'noName'); - assert.equal(actual[2].uri.fsPath, URI.file('/abc/test1').fsPath); + assert.equal(actual[2].uri.fsPath, abcTest1FolderUri.fsPath); assert.equal((actual[2].raw).path, '/abc/test1'); assert.equal(actual[2].index, 2); assert.equal(actual[2].name, 'test1'); }); test('toWorkspaceFolders with multiple absolute and relative folders with invalid paths', () => { - const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '', name: 'noName' }, { path: './test3' }, { path: '/abc/test1' }], URI.file('src')); + const actual = toWorkspaceFolders([{ path: '/src/test2' }, { path: '', name: 'noName' }, { path: './test3' }, { path: '/abc/test1' }], workspaceConfigUri); assert.equal(actual.length, 3); - assert.equal(actual[0].uri.fsPath, URI.file('/src/test2').fsPath); + assert.equal(actual[0].uri.fsPath, test2FolderUri.fsPath); assert.equal((actual[0].raw).path, '/src/test2'); assert.equal(actual[0].index, 0); assert.equal(actual[0].name, 'test2'); - assert.equal(actual[1].uri.fsPath, URI.file('/src/test3').fsPath); + assert.equal(actual[1].uri.fsPath, test3FolderUri.fsPath); assert.equal((actual[1].raw).path, './test3'); assert.equal(actual[1].index, 1); assert.equal(actual[1].name, 'test3'); - assert.equal(actual[2].uri.fsPath, URI.file('/abc/test1').fsPath); + assert.equal(actual[2].uri.fsPath, abcTest1FolderUri.fsPath); assert.equal((actual[2].raw).path, '/abc/test1'); assert.equal(actual[2].index, 2); assert.equal(actual[2].name, 'test1'); diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index 380d16b2594..fa8fae57ec8 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -10,7 +10,7 @@ import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/works import { URI, UriComponents } from 'vs/base/common/uri'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; import { extname } from 'vs/base/common/path'; -import { dirname, resolvePath, isEqualAuthority, isEqualOrParent, relativePath } from 'vs/base/common/resources'; +import { dirname, resolvePath, isEqualAuthority, isEqualOrParent, relativePath, extname as resourceExtname } from 'vs/base/common/resources'; import * as jsonEdit from 'vs/base/common/jsonEdit'; import * as json from 'vs/base/common/json'; import { Schemas } from 'vs/base/common/network'; @@ -158,8 +158,10 @@ export function isSingleFolderWorkspaceInitializationPayload(obj: any): obj is I const WORKSPACE_SUFFIX = '.' + WORKSPACE_EXTENSION; -export function hasWorkspaceFileExtension(path: string) { - return extname(path) === WORKSPACE_SUFFIX; +export function hasWorkspaceFileExtension(path: string | URI) { + const ext = (typeof path === 'string') ? extname(path) : resourceExtname(path); + + return ext === WORKSPACE_SUFFIX; } const SLASH = '/'; diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index 5b35809d8ec..a7775611ea2 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -17,7 +17,7 @@ import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; -import { originalFSPath, dirname as resourcesDirname, isEqualOrParent, joinPath } from 'vs/base/common/resources'; +import { originalFSPath, isEqualOrParent, joinPath } from 'vs/base/common/resources'; export interface IStoredWorkspace { folders: IStoredWorkspaceFolder[]; @@ -61,7 +61,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain } private isWorkspacePath(uri: URI): boolean { - return this.isInsideWorkspacesHome(uri) || hasWorkspaceFileExtension(uri.path); + return this.isInsideWorkspacesHome(uri) || hasWorkspaceFileExtension(uri); } private doResolveWorkspace(path: URI, contents: string): IResolvedWorkspace | null { @@ -71,7 +71,7 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain return { id: workspaceIdentifier.id, configPath: workspaceIdentifier.configPath, - folders: toWorkspaceFolders(workspace.folders, resourcesDirname(path)), + folders: toWorkspaceFolders(workspace.folders, workspaceIdentifier.configPath), remoteAuthority: workspace.remoteAuthority }; } catch (error) { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index ca44a26a121..a4bf1796b7d 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5500,8 +5500,8 @@ declare module 'vscode' { /** * A type that filesystem providers should use to signal errors. * - * This class has factory methods for common error-cases, like `EntryNotFound` when - * a file or folder doesn't exist, use them like so: `throw vscode.FileSystemError.EntryNotFound(someUri);` + * This class has factory methods for common error-cases, like `FileNotFound` when + * a file or folder doesn't exist, use them like so: `throw vscode.FileSystemError.FileNotFound(someUri);` */ export class FileSystemError extends Error { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 9c603ea901e..f92a6f746cc 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -16,6 +16,24 @@ declare module 'vscode' { + //#region Joh - ExecutionContext + + export enum ExtensionExecutionContext { + Local = 1, + Remote = 2 + } + + export interface ExtensionContext { + /** + * Describes the context in which this extension is executed, e.g. + * a Node.js-context on the same machine or on a remote machine + */ + executionContext: ExtensionExecutionContext; + } + + //#endregion + + //#region Joh - call hierarchy export enum CallHierarchyDirection { @@ -72,6 +90,10 @@ declare module 'vscode' { //#region Alex - resolvers + export interface RemoteAuthorityResolverContext { + resolveAttempt: number; + } + export class ResolvedAuthority { readonly host: string; readonly port: number; @@ -79,8 +101,15 @@ declare module 'vscode' { constructor(host: string, port: number); } + export class RemoteAuthorityResolverError extends Error { + static NotAvailable(message?: string, handled?: boolean): RemoteAuthorityResolverError; + static TemporarilyNotAvailable(message?: string): RemoteAuthorityResolverError; + + constructor(message?: string); + } + export interface RemoteAuthorityResolver { - resolve(authority: string): ResolvedAuthority | Thenable; + resolve(authority: string, context: RemoteAuthorityResolverContext): ResolvedAuthority | Thenable; } export interface ResourceLabelFormatter { @@ -724,102 +753,10 @@ declare module 'vscode' { inDraftMode?: boolean; } - export enum CommentThreadCollapsibleState { - /** - * Determines an item is collapsed - */ - Collapsed = 0, - /** - * Determines an item is expanded - */ - Expanded = 1 - } - - /** - * A collection of comments representing a conversation at a particular range in a document. - */ - export interface CommentThread { - /** - * A unique identifier of the comment thread. - */ - threadId: string; - - /** - * The uri of the document the thread has been created on. - */ - resource: Uri; - - /** - * The range the comment thread is located within the document. The thread icon will be shown - * at the first line of the range. - */ - range: Range; - - /** - * The human-readable label describing the [Comment Thread](#CommentThread) - */ - label?: string; - - /** - * The ordered comments of the thread. - */ - comments: Comment[]; - - /** - * Optional accept input command - * - * `acceptInputCommand` is the default action rendered on Comment Widget, which is always placed rightmost. - * This command will be invoked when users the user accepts the value in the comment editor. - * This command will disabled when the comment editor is empty. - */ - acceptInputCommand?: Command; - - /** - * Optional additonal commands. - * - * `additionalCommands` are the secondary actions rendered on Comment Widget. - */ - additionalCommands?: Command[]; - - /** - * Whether the thread should be collapsed or expanded when opening the document. - * Defaults to Collapsed. - */ - collapsibleState?: CommentThreadCollapsibleState; - - /** - * The command to be executed when users try to delete the comment thread. Currently, this is only called - * when the user collapses a comment thread that has no comments in it. - */ - deleteCommand?: Command; - - /** - * Dispose this comment thread. - * Once disposed, the comment thread will be removed from visible text editors and Comments Panel. - */ - dispose?(): void; - } - /** * A comment is displayed within the editor or the Comments Panel, depending on how it is provided. */ export interface Comment { - /** - * The id of the comment - */ - readonly commentId: string; - - /** - * The text of the comment - */ - readonly body: MarkdownString; - - /** - * Optional label describing the [Comment](#Comment) - * Label will be rendered next to userName if exists. - */ - readonly label?: string; - /** * The display name of the user who created the comment */ @@ -830,6 +767,13 @@ declare module 'vscode' { */ readonly userIconPath?: Uri; + /** + * The id of the comment + * + * @deprecated Use Id instead + */ + readonly commentId: string; + /** * @deprecated Use userIconPath instead. The avatar src of the user who created the comment */ @@ -862,24 +806,14 @@ declare module 'vscode' { command?: Command; /** - * The command to be executed if the comment is selected in the Comments Panel + * Deprecated */ - readonly selectCommand?: Command; - - /** - * The command to be executed when users try to save the edits to the comment - */ - readonly editCommand?: Command; + isDraft?: boolean; /** * The command to be executed when users try to delete the comment */ - readonly deleteCommand?: Command; - - /** - * Deprecated - */ - isDraft?: boolean; + deleteCommand?: Command; /** * Proposed Comment Reaction @@ -914,6 +848,7 @@ declare module 'vscode' { /** * Comment Reactions + * Stay in proposed. */ interface CommentReaction { readonly label?: string; @@ -986,73 +921,49 @@ declare module 'vscode' { } /** - * The comment input box in Comment Widget. + * Stay in proposed */ - export interface CommentInputBox { - /** - * Setter and getter for the contents of the comment input box. - */ - value: string; - } - export interface CommentReactionProvider { availableReactions: CommentReaction[]; toggleReaction?(document: TextDocument, comment: Comment, reaction: CommentReaction): Promise; } - export interface CommentingRangeProvider { + export interface CommentThread { /** - * Provide a list of ranges which allow new comment threads creation or null for a given document + * The uri of the document the thread has been created on. */ - provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult; + readonly resource: Uri; /** - * The method `createEmptyCommentThread` is called when users attempt to create new comment thread from the gutter or command palette. - * Extensions still need to call `createCommentThread` inside this call when appropriate. + * Optional additonal commands. + * + * `additionalCommands` are the secondary actions rendered on Comment Widget. */ - createEmptyCommentThread(document: TextDocument, range: Range): ProviderResult; + additionalCommands?: Command[]; + + /** + * The command to be executed when users try to delete the comment thread. Currently, this is only called + * when the user collapses a comment thread that has no comments in it. + */ + deleteCommand?: Command; + } + + + export interface CommentController { + /** + * Optional reaction provider + * Stay in proposed. + */ + reactionProvider?: CommentReactionProvider; } export interface CommentController { /** - * The id of this comment controller. + * The active [comment thread](#CommentThread) or `undefined`. The `activeCommentThread` is the comment thread of + * the comment widget that currently has focus. It's `undefined` when the focus is not in any comment thread widget, or + * the comment widget created from [comment thread template](#CommentThreadTemplate). */ - readonly id: string; - - /** - * The human-readable label of this comment controller. - */ - readonly label: string; - - /** - * The active (focused) [comment input box](#CommentInputBox). - */ - readonly inputBox?: CommentInputBox; - - /** - * Create a [CommentThread](#CommentThread) - */ - createCommentThread(id: string, resource: Uri, range: Range, comments: Comment[]): CommentThread; - - /** - * Optional commenting range provider. - * Provide a list [ranges](#Range) which support commenting to any given resource uri. - */ - commentingRangeProvider?: CommentingRangeProvider; - - /** - * Optional reaction provider - */ - reactionProvider?: CommentReactionProvider; - - /** - * Dispose this comment controller. - */ - dispose(): void; - } - - namespace comment { - export function createCommentController(id: string, label: string): CommentController; + readonly activeCommentThread: CommentThread | undefined; } namespace workspace { @@ -1068,6 +979,277 @@ declare module 'vscode' { export function registerWorkspaceCommentProvider(provider: WorkspaceCommentProvider): Disposable; } + /** + * Collapsible state of a [comment thread](#CommentThread) + */ + export enum CommentThreadCollapsibleState { + /** + * Determines an item is collapsed + */ + Collapsed = 0, + + /** + * Determines an item is expanded + */ + Expanded = 1 + } + + /** + * A collection of [comments](#Comment) representing a conversation at a particular range in a document. + */ + export interface CommentThread { + /** + * A unique identifier of the comment thread. + */ + readonly id: string; + + /** + * The uri of the document the thread has been created on. + */ + readonly uri: Uri; + + /** + * The range the comment thread is located within the document. The thread icon will be shown + * at the first line of the range. + */ + readonly range: Range; + + /** + * The ordered comments of the thread. + */ + comments: Comment[]; + + /** + * Whether the thread should be collapsed or expanded when opening the document. + * Defaults to Collapsed. + */ + collapsibleState: CommentThreadCollapsibleState; + + /** + * The optional human-readable label describing the [Comment Thread](#CommentThread) + */ + label?: string; + + /** + * Optional accept input command + * + * `acceptInputCommand` is the default action rendered on Comment Widget, which is always placed rightmost. + * This command will be invoked when users the user accepts the value in the comment editor. + * This command will disabled when the comment editor is empty. + */ + acceptInputCommand?: Command; + + + /** + * Dispose this comment thread. + * + * Once disposed, this comment thread will be removed from visible editors and Comment Panel when approriate. + */ + dispose(): void; + } + + /** + * Author information of a [comment](#Comment) + */ + + export interface CommentAuthorInformation { + /** + * The display name of the author of the comment + */ + name: string; + + /** + * The optional icon path for the author + */ + iconPath?: Uri; + } + + /** + * Author information of a [comment](#Comment) + */ + + export interface CommentAuthorInformation { + /** + * The display name of the author of the comment + */ + name: string; + + /** + * The optional icon path for the author + */ + iconPath?: Uri; + } + + /** + * A comment is displayed within the editor or the Comments Panel, depending on how it is provided. + */ + export interface Comment { + /** + * The id of the comment + */ + id: string; + + /** + * The human-readable comment body + */ + body: MarkdownString; + + /** + * The author information of the comment + */ + author: CommentAuthorInformation; + + /** + * Optional label describing the [Comment](#Comment) + * Label will be rendered next to authorName if exists. + */ + label?: string; + + /** + * The command to be executed if the comment is selected in the Comments Panel + */ + selectCommand?: Command; + + /** + * The command to be executed when users try to save the edits to the comment + */ + editCommand?: Command; + } + + /** + * The comment input box in Comment Widget. + */ + export interface CommentInputBox { + /** + * Setter and getter for the contents of the comment input box + */ + value: string; + + /** + * The uri of the document comment input box has been created on + */ + resource: Uri; + + /** + * The range the comment input box is located within the document + */ + range: Range; + } + + /** + * Commenting range provider for a [comment controller](#CommentController). + */ + export interface CommentingRangeProvider { + /** + * Provide a list of ranges which allow new comment threads creation or null for a given document + */ + provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult; + } + + /** + * Comment thread template for new comment thread creation. + */ + export interface CommentThreadTemplate { + /** + * The human-readable label describing the [Comment Thread](#CommentThread) + */ + readonly label: string; + + /** + * Optional accept input command + * + * `acceptInputCommand` is the default action rendered on Comment Widget, which is always placed rightmost. + * This command will be invoked when users the user accepts the value in the comment editor. + * This command will disabled when the comment editor is empty. + */ + readonly acceptInputCommand?: Command; + + /** + * Optional additonal commands. + * + * `additionalCommands` are the secondary actions rendered on Comment Widget. + */ + readonly additionalCommands?: Command[]; + + /** + * The command to be executed when users try to delete the comment thread. Currently, this is only called + * when the user collapses a comment thread that has no comments in it. + */ + readonly deleteCommand?: Command; + } + + /** + * A comment controller is able to provide [comments](#CommentThread) support to the editor and + * provide users various ways to interact with comments. + */ + export interface CommentController { + /** + * The id of this comment controller. + */ + readonly id: string; + + /** + * The human-readable label of this comment controller. + */ + readonly label: string; + + /** + * The active [comment input box](#CommentInputBox) or `undefined`. The active `inputBox` is the input box of + * the comment thread widget that currently has focus. It's `undefined` when the focus is not in any CommentInputBox. + */ + readonly inputBox: CommentInputBox | undefined; + + /** + * Optional comment thread template information. + * + * The comment controller will use this information to create the comment widget when users attempt to create new comment thread + * from the gutter or command palette. + * + * When users run `CommentThreadTemplate.acceptInputCommand` or `CommentThreadTemplate.additionalCommands`, extensions should create + * the approriate [CommentThread](#CommentThread). + * + * If not provided, users won't be able to create new comment threads in the editor. + */ + template?: CommentThreadTemplate; + + /** + * Optional commenting range provider. Provide a list [ranges](#Range) which support commenting to any given resource uri. + * + * If not provided and `emptyCommentThreadFactory` exits, users can leave comments in any document opened in the editor. + */ + commentingRangeProvider?: CommentingRangeProvider; + + /** + * Create a [comment thread](#CommentThread). The comment thread will be displayed in visible text editors (if the resource matches) + * and Comments Panel once created. + * + * @param id An `id` for the comment thread. + * @param resource The uri of the document the thread has been created on. + * @param range The range the comment thread is located within the document. + * @param comments The ordered comments of the thread. + */ + createCommentThread(id: string, uri: Uri, range: Range, comments: Comment[]): CommentThread; + + /** + * Dispose this comment controller. + * + * Once disposed, all [comment threads](#CommentThread) created by this comment controller will also be removed from the editor + * and Comments Panel. + */ + dispose(): void; + } + + namespace comment { + /** + * Creates a new [comment controller](#CommentController) instance. + * + * @param id An `id` for the comment controller. + * @param label A human-readable string for the comment controller. + * @return An instance of [comment controller](#CommentController). + */ + export function createCommentController(id: string, label: string): CommentController; + } + //#endregion //#region Terminal @@ -1360,4 +1542,43 @@ declare module 'vscode' { group?: string; } //#endregion + + //#region Workspace URI Ben + + export namespace workspace { + + /** + * The location of the workspace file, for example: + * + * `file:///Users/name/Development/myProject.code-workspace` + * + * or + * + * `untitled:1555503116870` + * + * for a workspace that is untitled and not yet saved. + * + * Depending on the workspace that is opened, the value will be: + * * `undefined` when no workspace or a single folder is opened + * * the path of the workspace file as `Uri` otherwise. if the workspace + * is untitled, the returned URI will use the `untitled:` scheme + * + * The location can e.g. be used with the `vscode.openFolder` command to + * open the workspace again after it has been closed. + * + * **Example:** + * ```typescript + * vscode.commands.executeCommand('vscode.openFolder', uriOfWorkspace); + * ``` + * + * **Note:** it is not advised to use `workspace.workspaceFile` to write + * configuration data into the file. You can use `workspace.getConfiguration().update()` + * for that purpose which will work both when a single folder is opened as + * well as an untitled or saved workspace. + */ + export const workspaceFile: Uri | undefined; + } + + //#endregion + } diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 87d0106be0c..4b4896e6ece 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -184,14 +184,22 @@ export class MainThreadCommentThread implements modes.CommentThread2 { private _onDidChangeCollasibleState = new Emitter(); public onDidChangeCollasibleState = this._onDidChangeCollasibleState.event; + private _isDisposed: boolean; + + get isDisposed(): boolean { + return this._isDisposed; + } + constructor( public commentThreadHandle: number, - public controller: MainThreadCommentController, + public controllerHandle: number, public extensionId: string, public threadId: string, public resource: string, private _range: IRange - ) { } + ) { + this._isDisposed = false; + } batchUpdate( range: IRange, @@ -210,12 +218,21 @@ export class MainThreadCommentThread implements modes.CommentThread2 { this._collapsibleState = collapsibleState; } - dispose() { } + dispose() { + this._isDisposed = true; + this._onDidChangeAcceptInputCommand.dispose(); + this._onDidChangeAdditionalCommands.dispose(); + this._onDidChangeCollasibleState.dispose(); + this._onDidChangeComments.dispose(); + this._onDidChangeInput.dispose(); + this._onDidChangeLabel.dispose(); + this._onDidChangeRange.dispose(); + } toJSON(): any { return { $mid: 7, - commentControlHandle: this.controller.handle, + commentControlHandle: this.controllerHandle, commentThreadHandle: this.commentThreadHandle, }; } @@ -273,7 +290,7 @@ export class MainThreadCommentController { ): modes.CommentThread2 { let thread = new MainThreadCommentThread( commentThreadHandle, - this, + this.handle, '', threadId, URI.revive(resource).toString(), @@ -281,12 +298,18 @@ export class MainThreadCommentController { ); this._threads.set(commentThreadHandle, thread); - this._commentService.updateComments(this._uniqueId, { - added: [thread], - removed: [], - changed: [], - draftMode: modes.DraftMode.NotSupported - }); + + // As we create comment thread from template and then restore from the newly created maint thread comment thread, + // we postpone the update event to avoid duplication. + // This can be actually removed once we are on the new API. + setTimeout(() => { + this._commentService.updateComments(this._uniqueId, { + added: [thread], + removed: [], + changed: [], + draftMode: modes.DraftMode.NotSupported + }); + }, 0); return thread; } @@ -362,10 +385,23 @@ export class MainThreadCommentController { commentingRanges: commentingRanges ? { resource: resource, ranges: commentingRanges, newCommentThreadCallback: async (uri: UriComponents, range: IRange) => { - await this._proxy.$createNewCommentWidgetCallback(this.handle, uri, range, token); + let threadHandle = await this._proxy.$createNewCommentWidgetCallback(this.handle, uri, range, token); + + if (threadHandle !== undefined) { + return this.getKnownThread(threadHandle); + } + + return; } } : [], - draftMode: modes.DraftMode.NotSupported + draftMode: modes.DraftMode.NotSupported, + template: this._features.commentThreadTemplate ? { + controllerHandle: this.handle, + label: this._features.commentThreadTemplate.label, + acceptInputCommand: this._features.commentThreadTemplate.acceptInputCommand, + additionalCommands: this._features.commentThreadTemplate.additionalCommands, + deleteCommand: this._features.commentThreadTemplate.deleteCommand + } : undefined }; } @@ -391,6 +427,28 @@ export class MainThreadCommentController { return ret; } + getCommentThreadFromTemplate(resource: UriComponents, range: IRange): MainThreadCommentThread { + let thread = new MainThreadCommentThread( + -1, + this.handle, + '', + '', + URI.revive(resource).toString(), + range + ); + + let template = this._features.commentThreadTemplate; + + if (template) { + thread.acceptInputCommand = template.acceptInputCommand; + thread.additionalCommands = template.additionalCommands; + thread.deleteCommand = template.deleteCommand; + thread.label = template.label; + } + + return thread; + } + toJSON(): any { return { $mid: 6, @@ -426,7 +484,8 @@ export class MainThreadComments extends Disposable implements MainThreadComments this._activeCommentThreadDisposables = []; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostComments); this._disposables.push(this._commentService.onDidChangeActiveCommentThread(async thread => { - let controller = (thread as MainThreadCommentThread).controller; + let handle = (thread as MainThreadCommentThread).controllerHandle; + let controller = this._commentControllers.get(handle); if (!controller) { return; @@ -438,10 +497,11 @@ export class MainThreadComments extends Disposable implements MainThreadComments this._activeCommentThreadDisposables.push(this._activeCommentThread.onDidChangeInput(input => { // todo, dispose this._input = input; - this._proxy.$onCommentWidgetInputChange(controller.handle, this._input ? this._input.value : undefined); + this._proxy.$onCommentWidgetInputChange(handle, URI.parse(this._activeCommentThread!.resource), this._activeCommentThread!.range, this._input ? this._input.value : undefined); })); - await this._proxy.$onCommentWidgetInputChange(controller.handle, this._input ? this._input.value : undefined); + await this._proxy.$onActiveCommentThreadChange(controller.handle, controller.activeCommentThread.commentThreadHandle); + await this._proxy.$onCommentWidgetInputChange(controller.handle, URI.parse(this._activeCommentThread!.resource), this._activeCommentThread.range, this._input ? this._input.value : undefined); })); } diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 082c1f4114f..d49f770e9ef 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -5,7 +5,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { URI as uri } from 'vs/base/common/uri'; -import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, ITerminalSettings, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDebugAdapterTrackerFactory } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, ITerminalSettings, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory } from 'vs/workbench/contrib/debug/common/debug'; import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto @@ -26,7 +26,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb private _debugAdaptersHandleCounter = 1; private readonly _debugConfigurationProviders: Map; private readonly _debugAdapterDescriptorFactories: Map; - private readonly _debugAdapterTrackerFactories: Map; private readonly _sessions: Set; constructor( @@ -53,7 +52,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb this._debugAdapters = new Map(); this._debugConfigurationProviders = new Map(); this._debugAdapterDescriptorFactories = new Map(); - this._debugAdapterTrackerFactories = new Map(); this._sessions = new Set(); } @@ -207,24 +205,6 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } } - public $registerDebugAdapterTrackerFactory(debugType: string, handle: number) { - const factory = { - type: debugType, - }; - this._debugAdapterTrackerFactories.set(handle, factory); - this._toDispose.push(this.debugService.getConfigurationManager().registerDebugAdapterTrackerFactory(factory)); - - return Promise.resolve(undefined); - } - - public $unregisterDebugAdapterTrackerFactory(handle: number) { - const factory = this._debugAdapterTrackerFactories.get(handle); - if (factory) { - this._debugAdapterTrackerFactories.delete(handle); - this.debugService.getConfigurationManager().unregisterDebugAdapterTrackerFactory(factory); - } - } - private getSession(sessionId: DebugSessionUUID | undefined): IDebugSession | undefined { if (sessionId) { return this.debugService.getModel().getSession(sessionId, true); diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index c18e4fc1363..af99b8a7720 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -228,10 +228,10 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { }); } - private _doCreateUntitled(resource?: URI, modeId?: string, initialValue?: string): Promise { + private _doCreateUntitled(resource?: URI, mode?: string, initialValue?: string): Promise { return this._untitledEditorService.loadOrCreate({ resource, - modeId, + mode, initialValue, useResourcePath: Boolean(resource && resource.path) }).then(model => { diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index c7cd1af77e0..126112ed917 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -369,7 +369,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha sortText: data.e, filterText: data.f, preselect: data.g, - insertText: data.h || data.a, + insertText: typeof data.h === 'undefined' ? data.a : data.h, insertTextRules: data.i, range: data.j || defaultRange, commitCharacters: data.k, diff --git a/src/vs/workbench/api/browser/mainThreadProgress.ts b/src/vs/workbench/api/browser/mainThreadProgress.ts index 2c889ccc709..7c45dd4b1b5 100644 --- a/src/vs/workbench/api/browser/mainThreadProgress.ts +++ b/src/vs/workbench/api/browser/mainThreadProgress.ts @@ -3,9 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IProgress, IProgressService2, IProgressStep, IProgressOptions } from 'vs/platform/progress/common/progress'; +import { IProgress, IProgressService2, IProgressStep, ProgressLocation, IProgressOptions, IProgressNotificationOptions } from 'vs/platform/progress/common/progress'; import { MainThreadProgressShape, MainContext, IExtHostContext, ExtHostProgressShape, ExtHostContext } from '../common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; +import { Action } from 'vs/base/common/actions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { localize } from 'vs/nls'; + +class ManageExtensionAction extends Action { + constructor(id: ExtensionIdentifier, label: string, commandService: ICommandService) { + super(id.value, label, undefined, true, () => { + return commandService.executeCommand('_extensions.manage', id.value); + }); + } +} @extHostNamedCustomer(MainContext.MainThreadProgress) export class MainThreadProgress implements MainThreadProgressShape { @@ -16,7 +28,8 @@ export class MainThreadProgress implements MainThreadProgressShape { constructor( extHostContext: IExtHostContext, - @IProgressService2 progressService: IProgressService2 + @IProgressService2 progressService: IProgressService2, + @ICommandService private readonly _commandService: ICommandService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostProgress); this._progressService = progressService; @@ -27,9 +40,19 @@ export class MainThreadProgress implements MainThreadProgressShape { this._progress.clear(); } - $startProgress(handle: number, options: IProgressOptions): void { + $startProgress(handle: number, options: IProgressOptions, extension?: IExtensionDescription): void { const task = this._createTask(handle); + if (options.location === ProgressLocation.Notification && extension && !extension.isUnderDevelopment) { + const notificationOptions: IProgressNotificationOptions = { + ...options, + location: ProgressLocation.Notification, + secondaryActions: [new ManageExtensionAction(extension.identifier, localize('manageExtension', "Manage Extension"), this._commandService)] + }; + + options = notificationOptions; + } + this._progressService.withProgress(options, task, () => this._proxy.$acceptProgressCanceled(handle)); } diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 27ae35cfcd5..76bed28b12e 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -266,14 +266,18 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { const codeActionsOnSave = Object.keys(setting) .filter(x => setting[x]).map(x => new CodeActionKind(x)) .sort((a, b) => { - if (a.value === CodeActionKind.SourceFixAll.value) { - return -1; - } - if (b.value === CodeActionKind.SourceFixAll.value) { + if (CodeActionKind.SourceFixAll.contains(a)) { + if (CodeActionKind.SourceFixAll.contains(b)) { + return 0; + } return 1; } + if (CodeActionKind.SourceFixAll.contains(b)) { + return -1; + } return 0; }); + if (!codeActionsOnSave.length) { return undefined; } @@ -289,11 +293,8 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { reject(localize('codeActionsOnSave.didTimeout', "Aborted codeActionsOnSave after {0}ms", timeout)); }, timeout)), this.applyOnSaveActions(model, codeActionsOnSave, tokenSource.token) - ]).then(() => { + ]).finally(() => { tokenSource.cancel(); - }, (e) => { - tokenSource.cancel(); - return Promise.reject(e); }); } diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index ba53e31e444..5bf1387b85d 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -224,7 +224,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape cwd: request.shellLaunchConfig.cwd, env: request.shellLaunchConfig.env }; - this._proxy.$createProcess(request.proxy.terminalId, shellLaunchConfigDto, request.activeWorkspaceRootUri, request.cols, request.rows); + this._proxy.$createProcess(request.proxy.terminalId, shellLaunchConfigDto, request.activeWorkspaceRootUri, request.cols, request.rows, request.isWorkspaceShellAllowed); request.proxy.onInput(data => this._proxy.$acceptProcessInput(request.proxy.terminalId, data)); request.proxy.onResize(dimensions => this._proxy.$acceptProcessResize(request.proxy.terminalId, dimensions.cols, dimensions.rows)); request.proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(request.proxy.terminalId, immediate)); diff --git a/src/vs/workbench/api/browser/mainThreadWindow.ts b/src/vs/workbench/api/browser/mainThreadWindow.ts index 7f99842116d..4c5b038c231 100644 --- a/src/vs/workbench/api/browser/mainThreadWindow.ts +++ b/src/vs/workbench/api/browser/mainThreadWindow.ts @@ -8,7 +8,7 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ExtHostContext, ExtHostWindowShape, IExtHostContext, MainContext, MainThreadWindowShape } from '../common/extHost.protocol'; +import { ExtHostContext, ExtHostWindowShape, IExtHostContext, MainContext, MainThreadWindowShape, IOpenUriOptions } from '../common/extHost.protocol'; import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -45,24 +45,21 @@ export class MainThreadWindow implements MainThreadWindowShape { return this.windowService.isFocused(); } - async $openUri(uriComponent: UriComponents): Promise { - const uri = URI.revive(uriComponent); - if (!!this.environmentService.configuration.remoteAuthority) { + async $openUri(uriComponent: UriComponents, options: IOpenUriOptions): Promise { + let uri = URI.revive(uriComponent); + if (options.allowTunneling && !!this.environmentService.configuration.remoteAuthority) { if (uri.scheme === 'http' || uri.scheme === 'https') { const port = this.getLocalhostPort(uri); if (typeof port === 'number') { const tunnel = await this.getOrCreateTunnel(port); if (tunnel) { - const tunneledUrl = uri.toString().replace( - new RegExp(`^${uri.scheme}://localhost:${port}/`), - `${uri.scheme}://localhost:${tunnel.tunnelLocalPort}/`); - return this.windowsService.openExternal(tunneledUrl); + uri = uri.with({ authority: `localhost:${tunnel.tunnelLocalPort}` }); } } } } - return this.windowsService.openExternal(uri.toString(true)); + return this.windowsService.openExternal(encodeURI(uri.toString(true))); } private getLocalhostPort(uri: URI): number | undefined { diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 32b8b351311..8e91801b58f 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -22,6 +22,8 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape, IWorkspaceData, ITextSearchComplete } from '../common/extHost.protocol'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { isEqualOrParent } from 'vs/base/common/resources'; @extHostNamedCustomer(MainContext.MainThreadWorkspace) export class MainThreadWorkspace implements MainThreadWorkspaceShape { @@ -40,7 +42,8 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { @IStatusbarService private readonly _statusbarService: IStatusbarService, @IWindowService private readonly _windowService: IWindowService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @ILabelService private readonly _labelService: ILabelService + @ILabelService private readonly _labelService: ILabelService, + @IEnvironmentService private readonly _environmentService: IEnvironmentService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace); this._contextService.getCompleteWorkspace().then(workspace => this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace))); @@ -110,6 +113,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } return { configuration: workspace.configuration || undefined, + isUntitled: workspace.configuration ? isEqualOrParent(workspace.configuration, this._environmentService.untitledWorkspacesHome) : false, folders: workspace.folders, id: workspace.id, name: this._labelService.getWorkspaceLabel(workspace) diff --git a/src/vs/workbench/api/common/apiCommands.ts b/src/vs/workbench/api/common/apiCommands.ts index f9722bc8ee7..09ef07b2de7 100644 --- a/src/vs/workbench/api/common/apiCommands.ts +++ b/src/vs/workbench/api/common/apiCommands.ts @@ -15,6 +15,7 @@ import { IWindowsService, IOpenSettings, IURIToOpen } from 'vs/platform/windows/ import { IDownloadService } from 'vs/platform/download/common/download'; import { IWorkspacesService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { IRecent } from 'vs/platform/history/common/history'; +import { Schemas } from 'vs/base/common/network'; // ----------------------------------------------------------------- // The following commands are registered on both sides separately. @@ -35,6 +36,7 @@ function adjustHandler(handler: (executor: ICommandsExecutor, ...args: any[]) => interface IOpenFolderAPICommandOptions { forceNewWindow?: boolean; + forceReuseWindow?: boolean; noRecentEntry?: boolean; } @@ -49,9 +51,9 @@ export class OpenFolderAPICommand { if (!uri) { return executor.executeCommand('_files.pickFolderAndOpen', { forceNewWindow: arg.forceNewWindow }); } - const options: IOpenSettings = { forceNewWindow: arg.forceNewWindow, noRecentEntry: arg.noRecentEntry }; + const options: IOpenSettings = { forceNewWindow: arg.forceNewWindow, forceReuseWindow: arg.forceReuseWindow, noRecentEntry: arg.noRecentEntry }; uri = URI.revive(uri); - const uriToOpen: IURIToOpen = hasWorkspaceFileExtension(uri.path) ? { workspaceUri: uri } : { folderUri: uri }; + const uriToOpen: IURIToOpen = (hasWorkspaceFileExtension(uri) || uri.scheme === Schemas.untitled) ? { workspaceUri: uri } : { folderUri: uri }; return executor.executeCommand('_files.windowOpen', [uriToOpen], options); } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f75694bb5b2..9902a90c40c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -40,7 +40,7 @@ import { IRPCProtocol, createExtHostContextProxyIdentifier as createExtId, creat import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ResolvedAuthority, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; import * as callHierarchy from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; @@ -65,6 +65,7 @@ export interface IStaticWorkspaceData { id: string; name: string; configuration?: UriComponents | null; + isUntitled?: boolean | null; } export interface IWorkspaceData extends IStaticWorkspaceData { @@ -117,11 +118,19 @@ export interface MainThreadCommandsShape extends IDisposable { $getCommands(): Promise; } +export interface CommentThreadTemplate { + label: string; + acceptInputCommand?: modes.Command; + additionalCommands?: modes.Command[]; + deleteCommand?: modes.Command; +} + export interface CommentProviderFeatures { startDraftLabel?: string; deleteDraftLabel?: string; finishDraftLabel?: string; reactionGroup?: modes.CommentReaction[]; + commentThreadTemplate?: CommentThreadTemplate; } export interface MainThreadCommentsShape extends IDisposable { @@ -375,7 +384,7 @@ export interface MainThreadOutputServiceShape extends IDisposable { export interface MainThreadProgressShape extends IDisposable { - $startProgress(handle: number, options: IProgressOptions): void; + $startProgress(handle: number, options: IProgressOptions, extension?: IExtensionDescription): void; $progressReport(handle: number, message: IProgressStep): void; $progressEnd(handle: number): void; } @@ -674,10 +683,8 @@ export interface MainThreadDebugServiceShape extends IDisposable { $acceptDAExit(handle: number, code: number | undefined, signal: string | undefined): void; $registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, hasProvideDaMethod: boolean, handle: number): Promise; $registerDebugAdapterDescriptorFactory(type: string, handle: number): Promise; - $registerDebugAdapterTrackerFactory(type: string, handle: number): Promise; $unregisterDebugConfigurationProvider(handle: number): void; $unregisterDebugAdapterDescriptorFactory(handle: number): void; - $unregisterDebugAdapterTrackerFactory(handle: number): void; $startDebugging(folder: UriComponents | undefined, nameOrConfig: string | IDebugConfiguration, parentSessionID: string | undefined): Promise; $customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Promise; $appendDebugConsole(value: string): void; @@ -686,9 +693,13 @@ export interface MainThreadDebugServiceShape extends IDisposable { $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[]): Promise; } +export interface IOpenUriOptions { + readonly allowTunneling?: boolean; +} + export interface MainThreadWindowShape extends IDisposable { $getWindowVisibility(): Promise; - $openUri(uri: UriComponents): Promise; + $openUri(uri: UriComponents, options: IOpenUriOptions): Promise; } // -- extension host @@ -804,8 +815,24 @@ export interface ExtHostSearchShape { $clearCache(cacheKey: string): Promise; } +export interface IResolveAuthorityErrorResult { + type: 'error'; + error: { + message: string | undefined; + code: RemoteAuthorityResolverErrorCode; + detail: any; + }; +} + +export interface IResolveAuthorityOKResult { + type: 'ok'; + value: ResolvedAuthority; +} + +export type IResolveAuthorityResult = IResolveAuthorityErrorResult | IResolveAuthorityOKResult; + export interface ExtHostExtensionServiceShape { - $resolveAuthority(remoteAuthority: string): Promise; + $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise; $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise; $activateByEvent(activationEvent: string): Promise; $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise; @@ -1054,7 +1081,7 @@ export interface ExtHostTerminalServiceShape { $acceptTerminalRendererInput(id: number, data: string): void; $acceptTerminalTitleChange(id: number, name: string): void; $acceptTerminalDimensions(id: number, cols: number, rows: number): void; - $createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents, cols: number, rows: number): void; + $createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void; $acceptProcessInput(id: number, data: string): void; $acceptProcessResize(id: number, cols: number, rows: number): void; $acceptProcessShutdown(id: number, immediate: boolean): void; @@ -1181,7 +1208,8 @@ export interface ExtHostProgressShape { export interface ExtHostCommentsShape { $provideDocumentComments(handle: number, document: UriComponents): Promise; $createNewCommentThread(handle: number, document: UriComponents, range: IRange, text: string): Promise; - $onCommentWidgetInputChange(commentControllerHandle: number, input: string | undefined): Promise; + $onCommentWidgetInputChange(commentControllerHandle: number, document: UriComponents, range: IRange, input: string | undefined): Promise; + $onActiveCommentThreadChange(commentControllerHandle: number, threadHandle: number | undefined): Promise; $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise; $provideReactionGroup(commentControllerHandle: number): Promise; $toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise; diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 2c0e902a9a8..a36f7409934 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -88,17 +88,28 @@ export class ExtHostComments implements ExtHostCommentsShape { return commentController; } - $onCommentWidgetInputChange(commentControllerHandle: number, input: string): Promise { + $onCommentWidgetInputChange(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, input: string): Promise { const commentController = this._commentControllers.get(commentControllerHandle); if (!commentController) { return Promise.resolve(undefined); } - commentController.$onCommentWidgetInputChange(input); + commentController.$onCommentWidgetInputChange(uriComponents, range, input); return Promise.resolve(commentControllerHandle); } + $onActiveCommentThreadChange(commentControllerHandle: number, threadHandle: number): Promise { + const commentController = this._commentControllers.get(commentControllerHandle); + + if (!commentController) { + return Promise.resolve(undefined); + } + + commentController.$onActiveCommentThreadChange(threadHandle); + return Promise.resolve(threadHandle); + } + $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise { const commentController = this._commentControllers.get(commentControllerHandle); @@ -159,7 +170,6 @@ export class ExtHostComments implements ExtHostCommentsShape { const document = this._documents.getDocument(URI.revive(uriComponents)); return asPromise(() => { - // TODO, remove this once GH PR stable deprecates `emptyCommentThreadFactory`. if ((commentController as any).emptyCommentThreadFactory) { return (commentController as any).emptyCommentThreadFactory!.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range)); } @@ -367,18 +377,26 @@ export class ExtHostCommentThread implements vscode.CommentThread { private static _handlePool: number = 0; readonly handle = ExtHostCommentThread._handlePool++; get threadId(): string { - return this._threadId; + return this._id; + } + + get id(): string { + return this._id; } get resource(): vscode.Uri { - return this._resource; + return this._uri; + } + + get uri(): vscode.Uri { + return this._uri; } private _onDidUpdateCommentThread = new Emitter(); readonly onDidUpdateCommentThread = this._onDidUpdateCommentThread.event; set range(range: vscode.Range) { - if (range.isEqual(this._range)) { + if (!range.isEqual(this._range)) { this._range = range; this._onDidUpdateCommentThread.fire(); } @@ -451,24 +469,31 @@ export class ExtHostCommentThread implements vscode.CommentThread { private _localDisposables: types.Disposable[]; + private _isDiposed: boolean; + + public get isDisposed(): boolean { + return this._isDiposed; + } + constructor( private _proxy: MainThreadCommentsShape, private readonly _commandsConverter: CommandsConverter, private _commentController: ExtHostCommentController, - private _threadId: string, - private _resource: vscode.Uri, + private _id: string, + private _uri: vscode.Uri, private _range: vscode.Range, private _comments: vscode.Comment[] ) { this._proxy.$createCommentThread( this._commentController.handle, this.handle, - this._threadId, - this._resource, + this._id, + this._uri, extHostTypeConverter.Range.from(this._range) ); this._localDisposables = []; + this._isDiposed = false; this._localDisposables.push(this.onDidUpdateCommentThread(() => { this.eventuallyUpdateCommentThread(); @@ -491,8 +516,8 @@ export class ExtHostCommentThread implements vscode.CommentThread { this._proxy.$updateCommentThread( this._commentController.handle, this.handle, - this._threadId, - this._resource, + this._id, + this._uri, commentThreadRange, label, comments, @@ -519,17 +544,20 @@ export class ExtHostCommentThread implements vscode.CommentThread { this._commentController.handle, this.handle ); + this._isDiposed = true; } } export class ExtHostCommentInputBox implements vscode.CommentInputBox { - private _onDidChangeValue = new Emitter(); - - get onDidChangeValue(): Event { - return this._onDidChangeValue.event; + get resource(): vscode.Uri { + return this._resource; } - private _value: string = ''; + + get range(): vscode.Range { + return this._range; + } + get value(): string { return this._value; } @@ -540,16 +568,24 @@ export class ExtHostCommentInputBox implements vscode.CommentInputBox { this._proxy.$setInputValue(this.commentControllerHandle, newInput); } - constructor( - private _proxy: MainThreadCommentsShape, + private _onDidChangeValue = new Emitter(); - public commentControllerHandle: number, - input: string - ) { - this._value = input; + get onDidChangeValue(): Event { + return this._onDidChangeValue.event; } - setInput(input: string) { + constructor( + private _proxy: MainThreadCommentsShape, + public commentControllerHandle: number, + private _resource: vscode.Uri, + private _range: vscode.Range, + private _value: string + ) { + } + + setInput(resource: vscode.Uri, range: vscode.Range, input: string) { + this._resource = resource; + this._range = range; this._value = input; } } @@ -562,7 +598,17 @@ class ExtHostCommentController implements vscode.CommentController { return this._label; } - public inputBox?: ExtHostCommentInputBox; + public inputBox: ExtHostCommentInputBox | undefined; + private _activeCommentThread: ExtHostCommentThread | undefined; + + public get activeCommentThread(): ExtHostCommentThread | undefined { + if (this._activeCommentThread && this._activeCommentThread.isDisposed) { + this._activeCommentThread = undefined; + } + + return this._activeCommentThread; + } + public activeCommentingRange?: vscode.Range; public get handle(): number { @@ -570,7 +616,31 @@ class ExtHostCommentController implements vscode.CommentController { } private _threads: Map = new Map(); - commentingRangeProvider?: vscode.CommentingRangeProvider; + commentingRangeProvider?: vscode.CommentingRangeProvider & { createEmptyCommentThread: (document: vscode.TextDocument, range: types.Range) => Promise; }; + + private _template: vscode.CommentThreadTemplate | undefined; + + get template(): vscode.CommentThreadTemplate | undefined { + return this._template; + } + + set template(newTemplate: vscode.CommentThreadTemplate | undefined) { + this._template = newTemplate; + + if (newTemplate) { + const acceptInputCommand = newTemplate.acceptInputCommand ? this._commandsConverter.toInternal(newTemplate.acceptInputCommand) : undefined; + const additionalCommands = newTemplate.additionalCommands ? newTemplate.additionalCommands.map(x => this._commandsConverter.toInternal(x)) : []; + const deleteCommand = newTemplate.deleteCommand ? this._commandsConverter.toInternal(newTemplate.deleteCommand) : undefined; + this._proxy.$updateCommentControllerFeatures(this.handle, { + commentThreadTemplate: { + label: newTemplate.label, + acceptInputCommand, + additionalCommands, + deleteCommand + } + }); + } + } private _commentReactionProvider?: vscode.CommentReactionProvider; @@ -602,14 +672,18 @@ class ExtHostCommentController implements vscode.CommentController { return commentThread; } - $onCommentWidgetInputChange(input: string) { + $onCommentWidgetInputChange(uriComponents: UriComponents, range: IRange, input: string) { if (!this.inputBox) { - this.inputBox = new ExtHostCommentInputBox(this._proxy, this.handle, input); + this.inputBox = new ExtHostCommentInputBox(this._proxy, this.handle, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), input); } else { - this.inputBox.setInput(input); + this.inputBox.setInput(URI.revive(uriComponents), extHostTypeConverter.Range.to(range), input); } } + $onActiveCommentThreadChange(threadHandle: number) { + this._activeCommentThread = this.getCommentThread(threadHandle); + } + getCommentThread(handle: number) { return this._threads.get(handle); } @@ -635,22 +709,25 @@ function convertCommentInfo(owner: number, extensionId: ExtensionIdentifier, pro function convertToCommentThread(extensionId: ExtensionIdentifier, provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider, vscodeCommentThread: vscode.CommentThread, commandsConverter: CommandsConverter): modes.CommentThread { return { extensionId: extensionId.value, - threadId: vscodeCommentThread.threadId, + threadId: vscodeCommentThread.id, resource: vscodeCommentThread.resource.toString(), range: extHostTypeConverter.Range.from(vscodeCommentThread.range), - comments: vscodeCommentThread.comments.map(comment => convertToComment(provider, comment, commandsConverter)), + comments: vscodeCommentThread.comments.map(comment => convertToComment(provider, comment as vscode.Comment, commandsConverter)), collapsibleState: vscodeCommentThread.collapsibleState }; } function convertFromCommentThread(commentThread: modes.CommentThread): vscode.CommentThread { return { + id: commentThread.threadId!, threadId: commentThread.threadId!, + uri: URI.parse(commentThread.resource!), resource: URI.parse(commentThread.resource!), range: extHostTypeConverter.Range.to(commentThread.range), comments: commentThread.comments ? commentThread.comments.map(convertFromComment) : [], - collapsibleState: commentThread.collapsibleState - }; + collapsibleState: commentThread.collapsibleState, + dispose: () => { } + } as vscode.CommentThread; } function convertFromComment(comment: modes.Comment): vscode.Comment { @@ -664,8 +741,13 @@ function convertFromComment(comment: modes.Comment): vscode.Comment { } return { + id: comment.commentId, commentId: comment.commentId, body: extHostTypeConverter.MarkdownString.to(comment.body), + author: { + name: comment.userName, + iconPath: userIconPath + }, userName: comment.userName, userIconPath: userIconPath, canEdit: comment.canEdit, @@ -682,12 +764,13 @@ function convertFromComment(comment: modes.Comment): vscode.Comment { } function convertToModeComment(commentController: ExtHostCommentController, vscodeComment: vscode.Comment, commandsConverter: CommandsConverter): modes.Comment { - const iconPath = vscodeComment.userIconPath ? vscodeComment.userIconPath.toString() : vscodeComment.gravatar; + const iconPath = vscodeComment.author && vscodeComment.author.iconPath ? vscodeComment.author.iconPath.toString() : + (vscodeComment.userIconPath ? vscodeComment.userIconPath.toString() : vscodeComment.gravatar); return { - commentId: vscodeComment.commentId, + commentId: vscodeComment.id || vscodeComment.commentId, body: extHostTypeConverter.MarkdownString.from(vscodeComment.body), - userName: vscodeComment.userName, + userName: vscodeComment.author ? vscodeComment.author.name : vscodeComment.userName, userIconPath: iconPath, isDraft: vscodeComment.isDraft, selectCommand: vscodeComment.selectCommand ? commandsConverter.toInternal(vscodeComment.selectCommand) : undefined, diff --git a/src/vs/workbench/api/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index 3f152181961..86d49261456 100644 --- a/src/vs/workbench/api/common/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/common/extHostExtensionActivator.ts @@ -25,6 +25,7 @@ export interface IExtensionContext { globalStoragePath: string; asAbsolutePath(relativePath: string): string; readonly logPath: string; + executionContext: number; } /** diff --git a/src/vs/workbench/api/common/extHostProgress.ts b/src/vs/workbench/api/common/extHostProgress.ts index eb82d3d6a5c..afb0fb647f8 100644 --- a/src/vs/workbench/api/common/extHostProgress.ts +++ b/src/vs/workbench/api/common/extHostProgress.ts @@ -26,7 +26,7 @@ export class ExtHostProgress implements ExtHostProgressShape { const handle = this._handles++; const { title, location, cancellable } = options; const source = localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name); - this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }); + this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }, extension); return this._withProgress(handle, task, !!cancellable); } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 9a9c767fd6e..143da3168a6 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -13,6 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as vscode from 'vscode'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files'; +import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; function es5ClassCompat(target: Function): any { ///@ts-ignore @@ -445,6 +446,35 @@ export class ResolvedAuthority { } } +export class RemoteAuthorityResolverError extends Error { + + static NotAvailable(message?: string, handled?: boolean): RemoteAuthorityResolverError { + return new RemoteAuthorityResolverError(message, RemoteAuthorityResolverErrorCode.NotAvailable, handled); + } + + static TemporarilyNotAvailable(message?: string): RemoteAuthorityResolverError { + return new RemoteAuthorityResolverError(message, RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable); + } + + public readonly _message: string | undefined; + public readonly _code: RemoteAuthorityResolverErrorCode; + public readonly _detail: any; + + constructor(message?: string, code: RemoteAuthorityResolverErrorCode = RemoteAuthorityResolverErrorCode.Unknown, detail?: any) { + super(message); + + this._message = message; + this._code = code; + this._detail = detail; + + // workaround when extending builtin objects and when compiling to ES5, see: + // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work + if (typeof (Object).setPrototypeOf === 'function') { + (Object).setPrototypeOf(this, RemoteAuthorityResolverError.prototype); + } + } +} + export enum EndOfLine { LF = 1, CRLF = 2 @@ -2256,7 +2286,7 @@ export enum FoldingRangeKind { //#endregion - +//#region Comment export enum CommentThreadCollapsibleState { /** * Determines an item is collapsed @@ -2267,6 +2297,7 @@ export enum CommentThreadCollapsibleState { */ Expanded = 1 } +//#endregion @es5ClassCompat export class QuickInputButtons { @@ -2275,3 +2306,8 @@ export class QuickInputButtons { private constructor() { } } + +export enum ExtensionExecutionContext { + Local = 1, + Remote = 2 +} diff --git a/src/vs/workbench/api/common/extHostWindow.ts b/src/vs/workbench/api/common/extHostWindow.ts index 1cbfc06bd91..b4e764ced09 100644 --- a/src/vs/workbench/api/common/extHostWindow.ts +++ b/src/vs/workbench/api/common/extHostWindow.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { ExtHostWindowShape, MainContext, MainThreadWindowShape, IMainContext } from './extHost.protocol'; +import { ExtHostWindowShape, MainContext, MainThreadWindowShape, IMainContext, IOpenUriOptions } from './extHost.protocol'; import { WindowState } from 'vscode'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; @@ -38,7 +38,7 @@ export class ExtHostWindow implements ExtHostWindowShape { this._onDidChangeWindowState.fire(this._state); } - openUri(stringOrUri: string | URI): Promise { + openUri(stringOrUri: string | URI, options: IOpenUriOptions): Promise { if (typeof stringOrUri === 'string') { try { stringOrUri = URI.parse(stringOrUri); @@ -51,6 +51,6 @@ export class ExtHostWindow implements ExtHostWindowShape { } else if (stringOrUri.scheme === Schemas.command) { return Promise.reject(`Invalid scheme '${stringOrUri.scheme}'`); } - return this._proxy.$openUri(stringOrUri); + return this._proxy.$openUri(stringOrUri, options); } } diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 2e6eb740df1..bf8abf0eab9 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -10,7 +10,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { TernarySearchTree } from 'vs/base/common/map'; import { Counter } from 'vs/base/common/numbers'; import { isLinux } from 'vs/base/common/platform'; -import { basenameOrAuthority, dirname, isEqual, relativePath } from 'vs/base/common/resources'; +import { basenameOrAuthority, dirname, isEqual, relativePath, basename } from 'vs/base/common/resources'; import { compare } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; @@ -24,6 +24,7 @@ import * as vscode from 'vscode'; import { ExtHostWorkspaceShape, IWorkspaceData, MainThreadMessageServiceShape, MainThreadWorkspaceShape, IMainContext, MainContext, IStaticWorkspaceData } from './extHost.protocol'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Barrier } from 'vs/base/common/async'; +import { Schemas } from 'vs/base/common/network'; export interface IExtHostWorkspaceProvider { getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise; @@ -67,7 +68,7 @@ class ExtHostWorkspaceImpl extends Workspace { return { workspace: null, added: [], removed: [] }; } - const { id, name, folders } = data; + const { id, name, folders, configuration, isUntitled } = data; const newWorkspaceFolders: vscode.WorkspaceFolder[] = []; // If we have an existing workspace, we try to find the folders that match our @@ -95,7 +96,7 @@ class ExtHostWorkspaceImpl extends Workspace { // make sure to restore sort order based on index newWorkspaceFolders.sort((f1, f2) => f1.index < f2.index ? -1 : 1); - const workspace = new ExtHostWorkspaceImpl(id, name, newWorkspaceFolders); + const workspace = new ExtHostWorkspaceImpl(id, name, newWorkspaceFolders, configuration ? URI.revive(configuration) : null, !!isUntitled); const { added, removed } = delta(oldWorkspace ? oldWorkspace.workspaceFolders : [], workspace.workspaceFolders, compareWorkspaceFolderByUri); return { workspace, added, removed }; @@ -115,8 +116,8 @@ class ExtHostWorkspaceImpl extends Workspace { private readonly _workspaceFolders: vscode.WorkspaceFolder[] = []; private readonly _structure = TernarySearchTree.forPaths(); - constructor(id: string, private _name: string, folders: vscode.WorkspaceFolder[]) { - super(id, folders.map(f => new WorkspaceFolder(f))); + constructor(id: string, private _name: string, folders: vscode.WorkspaceFolder[], configuration: URI | null, private _isUntitled: boolean) { + super(id, folders.map(f => new WorkspaceFolder(f)), configuration); // setup the workspace folder data structure folders.forEach(folder => { @@ -129,6 +130,10 @@ class ExtHostWorkspaceImpl extends Workspace { return this._name; } + get isUntitled(): boolean { + return this._isUntitled; + } + get workspaceFolders(): vscode.WorkspaceFolder[] { return this._workspaceFolders.slice(0); } @@ -175,7 +180,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac this._proxy = mainContext.getProxy(MainContext.MainThreadWorkspace); this._messageService = mainContext.getProxy(MainContext.MainThreadMessageService); - this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, []) : undefined; + this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, [], data.configuration ? URI.revive(data.configuration) : null, !!data.isUntitled) : undefined; } $initializeWorkspace(data: IWorkspaceData): void { @@ -197,6 +202,20 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac return this._actualWorkspace ? this._actualWorkspace.name : undefined; } + get workspaceFile(): vscode.Uri | undefined { + if (this._actualWorkspace) { + if (this._actualWorkspace.configuration) { + if (this._actualWorkspace.isUntitled) { + return URI.from({ scheme: Schemas.untitled, path: basename(dirname(this._actualWorkspace.configuration)) }); // Untitled Worspace: return untitled URI + } + + return this._actualWorkspace.configuration; // Workspace: return the configuration location + } + } + + return undefined; + } + private get _actualWorkspace(): ExtHostWorkspaceImpl | undefined { return this._unconfirmedWorkspace || this._confirmedWorkspace; } @@ -365,7 +384,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac id: this._actualWorkspace.id, name: this._actualWorkspace.name, configuration: this._actualWorkspace.configuration, - folders + folders, + isUntitled: this._actualWorkspace.isUntitled } as IWorkspaceData, this._actualWorkspace).workspace || undefined; } } diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 77f99c0690e..2cc53f2ee5c 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -21,6 +21,7 @@ namespace schema { export interface IUserFriendlyMenuItem { command: string; alt?: string; + precondition?: string; when?: string; group?: string; } @@ -34,6 +35,7 @@ namespace schema { case 'explorer/context': return MenuId.ExplorerContext; case 'editor/title/context': return MenuId.EditorTitleContext; case 'debug/callstack/context': return MenuId.DebugCallStackContext; + case 'debug/toolbar': return MenuId.DebugToolBar; case 'debug/toolBar': return MenuId.DebugToolBar; case 'menuBar/file': return MenuId.MenubarFileMenu; case 'scm/title': return MenuId.SCMTitle; @@ -73,6 +75,10 @@ namespace schema { collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'alt')); return false; } + if (item.precondition && typeof item.precondition !== 'string') { + collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'precondition')); + return false; + } if (item.when && typeof item.when !== 'string') { collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when')); return false; @@ -97,6 +103,10 @@ namespace schema { description: localize('vscode.extension.contributes.menuItem.alt', 'Identifier of an alternative command to execute. The command must be declared in the \'commands\'-section'), type: 'string' }, + precondition: { + description: localize('vscode.extension.contributes.menuItem.precondition', 'Condition which must be true to enable this item'), + type: 'string' + }, when: { description: localize('vscode.extension.contributes.menuItem.when', 'Condition which must be true to show this item'), type: 'string' @@ -400,6 +410,14 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM } } + if (item.precondition) { + command.precondition = ContextKeyExpr.deserialize(item.precondition); + } + + if (alt && item.precondition) { + alt.precondition = command.precondition; + } + const registration = MenuRegistry.appendMenuItem(menu, { command, alt, diff --git a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts index bb965caaa16..09e372750d1 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts @@ -182,7 +182,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews public $setHtml(handle: WebviewPanelHandle | WebviewInsetHandle, value: string): void { if (typeof handle === 'number') { - this.getWebviewElement(handle).contents = value; + this.getWebviewElement(handle).html = value; } else { const webview = this.getWebview(handle); webview.html = value; diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index d7fb371498a..a2cfe7b4bde 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -113,7 +113,7 @@ export function createApiFactory( const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures)); const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostDocumentsAndEditors)); const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); - const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol, extHostConfiguration, extHostLogService)); + const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol, extHostConfiguration, extHostWorkspace, extHostDocumentsAndEditors, extHostLogService)); const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(rpcProtocol, extHostWorkspace, extensionService, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService, extHostCommands)); const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments)); @@ -253,7 +253,7 @@ export function createApiFactory( return extHostClipboard; }, openExternal(uri: URI) { - return extHostWindow.openUri(uri); + return extHostWindow.openUri(uri, { allowTunneling: !!initData.remoteAuthority }); } }); @@ -532,6 +532,12 @@ export function createApiFactory( set name(value) { throw errors.readonly(); }, + get workspaceFile() { + return extHostWorkspace.workspaceFile; + }, + set workspaceFile(value) { + throw errors.readonly(); + }, updateWorkspaceFolders: (index, deleteCount, ...workspaceFoldersToAdd) => { return extHostWorkspace.updateWorkspaceFolders(extension, index, deleteCount || 0, ...workspaceFoldersToAdd); }, @@ -784,6 +790,7 @@ export function createApiFactory( DocumentSymbol: extHostTypes.DocumentSymbol, EndOfLine: extHostTypes.EndOfLine, EventEmitter: Emitter, + ExtensionExecutionContext: extHostTypes.ExtensionExecutionContext, CustomExecution: extHostTypes.CustomExecution, FileChangeType: extHostTypes.FileChangeType, FileSystemError: extHostTypes.FileSystemError, @@ -805,6 +812,7 @@ export function createApiFactory( Range: extHostTypes.Range, RelativePattern: extHostTypes.RelativePattern, ResolvedAuthority: extHostTypes.ResolvedAuthority, + RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError, Selection: extHostTypes.Selection, SelectionRange: extHostTypes.SelectionRange, ShellExecution: extHostTypes.ShellExecution, diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts index 84f610f60fb..4a5811a8600 100644 --- a/src/vs/workbench/api/node/extHostCLIServer.ts +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -29,7 +29,7 @@ export interface StatusPipeArgs { export interface RunCommandPipeArgs { type: 'command'; command: string; - args: string[]; + args: any[]; } export class CLIServer { @@ -99,7 +99,9 @@ export class CLIServer { for (const s of folderURIs) { try { urisToOpen.push({ folderUri: URI.parse(s) }); - forceNewWindow = true; + if (!addMode && !forceReuseWindow) { + forceNewWindow = true; + } } catch (e) { // ignore } @@ -110,7 +112,9 @@ export class CLIServer { try { if (hasWorkspaceFileExtension(s)) { urisToOpen.push({ workspaceUri: URI.parse(s) }); - forceNewWindow = true; + if (!forceReuseWindow) { + forceNewWindow = true; + } } else { urisToOpen.push({ fileUri: URI.parse(s) }); } diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index f6019e90144..fd0d9cdcc85 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -307,11 +307,8 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { const handle = this._trackerFactoryHandleCounter++; this._trackerFactories.push({ type, handle, factory }); - this._debugServiceProxy.$registerDebugAdapterTrackerFactory(type, handle); - return new Disposable(() => { this._trackerFactories = this._trackerFactories.filter(p => p.factory !== factory); // remove - this._debugServiceProxy.$unregisterDebugAdapterTrackerFactory(handle); }); } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index c9f18be439b..316bc670997 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { createApiFactory, IExtensionApiFactory } from 'vs/workbench/api/node/extHost.api.impl'; import { NodeModuleRequireInterceptor, VSCodeNodeModuleFactory, KeytarNodeModuleFactory, OpenNodeModuleFactory } from 'vs/workbench/api/node/extHostRequireInterceptor'; -import { ExtHostExtensionServiceShape, IEnvironment, IInitData, IMainContext, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostExtensionServiceShape, IEnvironment, IInitData, IMainContext, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape, IResolveAuthorityResult } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionModule, HostExtension } from 'vs/workbench/api/common/extHostExtensionActivator'; import { ExtHostLogService } from 'vs/workbench/api/common/extHostLogService'; @@ -24,7 +24,6 @@ import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/c import { connectProxyResolver } from 'vs/workbench/services/extensions/node/proxyResolver'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; -import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; import * as vscode from 'vscode'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IWorkspace } from 'vs/platform/workspace/common/workspace'; @@ -34,6 +33,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { ISchemeTransformer } from 'vs/workbench/api/common/extHostLanguageFeatures'; import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; import { ExtensionStoragePaths } from 'vs/workbench/api/node/extHostStoragePaths'; +import { RemoteAuthorityResolverError, ExtensionExecutionContext } from 'vs/workbench/api/common/extHostTypes'; interface ITestRunner { run(testsRoot: string, clb: (error: Error, failures?: number) => void): void; @@ -62,6 +62,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { private readonly _mainThreadExtensionsProxy: MainThreadExtensionServiceShape; private readonly _almostReadyToRunExtensions: Barrier; + private readonly _readyToStartExtensionHost: Barrier; private readonly _readyToRunExtensions: Barrier; private readonly _registry: ExtensionDescriptionRegistry; private readonly _storage: ExtHostStorage; @@ -98,6 +99,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { this._mainThreadExtensionsProxy = this._extHostContext.getProxy(MainContext.MainThreadExtensionService); this._almostReadyToRunExtensions = new Barrier(); + this._readyToStartExtensionHost = new Barrier(); this._readyToRunExtensions = new Barrier(); this._registry = new ExtensionDescriptionRegistry(initData.extensions); this._storage = new ExtHostStorage(this._extHostContext); @@ -166,7 +168,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { this._almostReadyToRunExtensions.open(); await this._extHostWorkspace.waitForInitializeCall(); - this._readyToRunExtensions.open(); + this._readyToStartExtensionHost.open(); } catch (err) { errors.onUnexpectedError(err); } @@ -360,7 +362,8 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { storagePath: this._storagePath.workspaceValue(extensionDescription), globalStoragePath: this._storagePath.globalValue(extensionDescription), asAbsolutePath: (relativePath: string) => { return path.join(extensionDescription.extensionLocation.fsPath, relativePath); }, - logPath: that._extHostLogService.getLogDirectory(extensionDescription.identifier) + logPath: that._extHostLogService.getLogDirectory(extensionDescription.identifier), + executionContext: this._initData.remoteAuthority ? ExtensionExecutionContext.Remote : ExtensionExecutionContext.Local }); }); } @@ -576,7 +579,8 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } this._started = true; - return this._readyToRunExtensions.wait() + return this._readyToStartExtensionHost.wait() + .then(() => this._readyToRunExtensions.open()) .then(() => this._handleEagerExtensions()) .then(() => this._handleExtensionTests()) .then(() => { @@ -595,7 +599,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { // -- called by main thread - public async $resolveAuthority(remoteAuthority: string): Promise { + public async $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise { const authorityPlusIndex = remoteAuthority.indexOf('+'); if (authorityPlusIndex === -1) { throw new Error(`Not an authority that can be resolved!`); @@ -607,15 +611,32 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { const resolver = this._resolvers[authorityPrefix]; if (!resolver) { - throw new Error(`No resolver available for ${authorityPrefix}`); + throw new Error(`No remote extension installed to resolve ${authorityPrefix}.`); } - const result = await resolver.resolve(remoteAuthority); - return { - authority: remoteAuthority, - host: result.host, - port: result.port, - }; + try { + const result = await resolver.resolve(remoteAuthority, { resolveAttempt }); + return { + type: 'ok', + value: { + authority: remoteAuthority, + host: result.host, + port: result.port, + } + }; + } catch (err) { + if (err instanceof RemoteAuthorityResolverError) { + return { + type: 'error', + error: { + code: err._code, + message: err._message, + detail: err._detail + } + }; + } + throw err; + } } public $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise { @@ -675,7 +696,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { let buff = VSBuffer.alloc(size); let value = Math.random() % 256; for (let i = 0; i < size; i++) { - buff.writeUint8(value, i); + buff.writeUInt8(value, i); } return buff; } diff --git a/src/vs/workbench/api/node/extHostRequireInterceptor.ts b/src/vs/workbench/api/node/extHostRequireInterceptor.ts index 0093594850d..c73c005de92 100644 --- a/src/vs/workbench/api/node/extHostRequireInterceptor.ts +++ b/src/vs/workbench/api/node/extHostRequireInterceptor.ts @@ -202,9 +202,9 @@ export class OpenNodeModuleFactory implements INodeModuleFactory { return this.callOriginal(target, options); } if (uri.scheme === 'http' || uri.scheme === 'https') { - return mainThreadWindow.$openUri(uri); + return mainThreadWindow.$openUri(uri, { allowTunneling: true }); } else if (uri.scheme === 'mailto') { - return mainThreadWindow.$openUri(uri); + return mainThreadWindow.$openUri(uri, {}); } return this.callOriginal(target, options); }; diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index f6393364e83..7bf6960304c 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -13,10 +13,14 @@ import { Event, Emitter } from 'vs/base/common/event'; import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { ILogService } from 'vs/platform/log/common/log'; -import { EXT_HOST_CREATION_DELAY, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; +import { EXT_HOST_CREATION_DELAY, IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess'; import { timeout } from 'vs/base/common/async'; -import { sanitizeProcessEnvironment } from 'vs/base/common/processes'; +import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; +import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal'; const RENDERER_NO_PROCESS_ID = -1; @@ -288,6 +292,8 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { constructor( mainContext: IMainContext, private _extHostConfiguration: ExtHostConfiguration, + private _extHostWorkspace: ExtHostWorkspace, + private _extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, private _logService: ILogService, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadTerminalService); @@ -436,7 +442,17 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { } } - public async $createProcess(id: number, shellLaunchConfigDto: ShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number): Promise { + private _apiInspectConfigToPlain( + config: { key: string; defaultValue?: T; globalValue?: T; workspaceValue?: T, workspaceFolderValue?: T } | undefined + ): { user: T | undefined, value: T | undefined, default: T | undefined } { + return { + user: config ? config.globalValue : undefined, + value: config ? config.workspaceValue : undefined, + default: config ? config.defaultValue : undefined, + }; + } + + public async $createProcess(id: number, shellLaunchConfigDto: ShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise { const shellLaunchConfig: IShellLaunchConfig = { name: shellLaunchConfigDto.name, executable: shellLaunchConfigDto.executable, @@ -445,63 +461,57 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { env: shellLaunchConfigDto.env }; - // TODO: This function duplicates a lot of TerminalProcessManager.createProcess, ideally - // they would be merged into a single implementation. + // Merge in shell and args from settings + const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); const configProvider = await this._extHostConfiguration.getConfigProvider(); - const terminalConfig = configProvider.getConfiguration('terminal.integrated'); - if (!shellLaunchConfig.executable) { - // TODO: This duplicates some of TerminalConfigHelper.mergeDefaultShellPathAndArgs and should be merged - // this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig); - - const platformKey = platform.isWindows ? 'windows' : platform.isMacintosh ? 'osx' : 'linux'; - const shellConfigValue: string | undefined = terminalConfig.get(`shell.${platformKey}`); - const shellArgsConfigValue: string | undefined = terminalConfig.get(`shellArgs.${platformKey}`); - - shellLaunchConfig.executable = shellConfigValue; - shellLaunchConfig.args = shellArgsConfigValue; + const fetchSetting = (key: string) => { + const setting = configProvider + .getConfiguration(key.substr(0, key.lastIndexOf('.'))) + .inspect(key.substr(key.lastIndexOf('.') + 1)); + return this._apiInspectConfigToPlain(setting); + }; + terminalEnvironment.mergeDefaultShellPathAndArgs(shellLaunchConfig, fetchSetting, isWorkspaceShellAllowed || false, getDefaultShell(platform.platform)); } - // TODO: @daniel + // Get the initial cwd + const terminalConfig = configProvider.getConfiguration('terminal.integrated'); const activeWorkspaceRootUri = URI.revive(activeWorkspaceRootUriComponents); const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, os.homedir(), activeWorkspaceRootUri, terminalConfig.cwd); - // TODO: Pull in and resolve config settings - // // Resolve env vars from config and shell - // const lastActiveWorkspaceRoot = this._workspaceContextService.getWorkspaceFolder(lastActiveWorkspaceRootUri); - const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); - // const envFromConfig = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...terminalConfig.env[platformKey] }, lastActiveWorkspaceRoot); - const envFromConfig = { ...terminalConfig.env[platformKey] }; - // const envFromShell = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...shellLaunchConfig.env }, lastActiveWorkspaceRoot); - - // Merge process env with the env from config - const env = { ...process.env }; - Object.keys(env).filter(k => env[k] === undefined).forEach(k => { - delete env[k]; - }); - const castedEnv = env as platform.IProcessEnvironment; - terminalEnvironment.mergeEnvironments(castedEnv, envFromConfig); - terminalEnvironment.mergeEnvironments(castedEnv, shellLaunchConfig.env); - - // Sanitize the environment, removing any undesirable VS Code and Electron environment - // variables - sanitizeProcessEnvironment(castedEnv, 'VSCODE_IPC_HOOK_CLI'); - - // Continue env initialization, merging in the env from the launch - // config and adding keys that are needed to create the process - terminalEnvironment.addTerminalEnvironmentKeys(castedEnv, pkg.version, platform.locale, terminalConfig.get('setLocaleVariables') as boolean); + // Get the environment + const apiLastActiveWorkspace = await this._extHostWorkspace.getWorkspaceFolder(activeWorkspaceRootUri); + const lastActiveWorkspace = apiLastActiveWorkspace ? { + uri: apiLastActiveWorkspace.uri, + name: apiLastActiveWorkspace.name, + index: apiLastActiveWorkspace.index, + toResource: () => { + throw new Error('Not implemented'); + } + } as IWorkspaceFolder : null; + const envFromConfig = this._apiInspectConfigToPlain(configProvider.getConfiguration('terminal.integrated').inspect(`env.${platformKey}`)); + const workspaceFolders = await this._extHostWorkspace.getWorkspaceFolders2(); + const variableResolver = workspaceFolders ? new ExtHostVariableResolverService(workspaceFolders, this._extHostDocumentsAndEditors, configProvider) : undefined; + const env = terminalEnvironment.createTerminalEnvironment( + shellLaunchConfig, + lastActiveWorkspace, + envFromConfig, + variableResolver, + isWorkspaceShellAllowed, + pkg.version, + terminalConfig.get('setLocaleVariables', false) + ); // Fork the process and listen for messages - this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, castedEnv); - const p = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, castedEnv, terminalConfig.get('windowsEnableConpty') as boolean); + this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, env); + const p = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, terminalConfig.get('windowsEnableConpty') as boolean, this._logService); p.onProcessIdReady(pid => this._proxy.$sendProcessPid(id, pid)); p.onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title)); p.onProcessData(data => this._proxy.$sendProcessData(id, data)); - p.onProcessExit((exitCode) => this._onProcessExit(id, exitCode)); + p.onProcessExit(exitCode => this._onProcessExit(id, exitCode)); this._terminalProcesses[id] = p; } - public $acceptProcessInput(id: number, data: string): void { this._terminalProcesses[id].input(data); } @@ -542,7 +552,6 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { // Send exit event to main side this._proxy.$sendProcessExit(id, exitCode); - } private _getTerminalByIdEventually(id: number, retries: number = 5): Promise { diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 8a7236e8d0f..5b42967e77f 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -19,7 +19,7 @@ import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { MenuBarVisibility } from 'vs/platform/windows/common/windows'; import { isWindows, isLinux } from 'vs/base/common/platform'; -import { IsMacContext } from 'vs/workbench/common/contextkeys'; +import { IsMacContext } from 'vs/workbench/browser/contextkeys'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { InEditorZenModeContext } from 'vs/workbench/common/editor'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index fbea24f3bce..801b038b879 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions'; -import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Component } from 'vs/workbench/common/component'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IComposite, ICompositeControl } from 'vs/workbench/common/composite'; @@ -170,12 +170,12 @@ export abstract class Composite extends Component implements IComposite { } /** - * For any of the actions returned by this composite, provide an IActionItem in + * For any of the actions returned by this composite, provide an IActionViewItem in * cases where the implementor of the composite wants to override the presentation * of an action. Returns undefined to indicate that the action is not rendered through * an action item. */ - getActionItem(action: IAction): IActionItem | undefined { + getActionViewItem(action: IAction): IActionViewItem | undefined { return undefined; } diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index f696004578e..a7846a7d257 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -5,11 +5,10 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext } from 'vs/workbench/common/editor'; -import { IsMacContext, IsLinuxContext, IsWindowsContext, HasMacNativeTabsContext, IsDevelopmentContext, SupportsWorkspacesContext, WorkbenchStateContext, WorkspaceFolderCountContext, RemoteAuthorityContext } from 'vs/workbench/common/contextkeys'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -19,6 +18,25 @@ import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/ import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; + +export const IsMacContext = new RawContextKey('isMac', isMacintosh); +export const IsLinuxContext = new RawContextKey('isLinux', isLinux); +export const IsWindowsContext = new RawContextKey('isWindows', isWindows); + +export const RemoteAuthorityContext = new RawContextKey('remoteAuthority', ''); + +export const HasMacNativeTabsContext = new RawContextKey('hasMacNativeTabs', false); + +export const SupportsWorkspacesContext = new RawContextKey('supportsWorkspaces', true); + +export const IsDevelopmentContext = new RawContextKey('isDevelopment', false); + +export const WorkbenchStateContext = new RawContextKey('workbenchState', undefined); + +export const WorkspaceFolderCountContext = new RawContextKey('workspaceFolderCount', 0); + +export const RemoteFileDialogContext = new RawContextKey('remoteFileDialogVisible', false); export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 74a04554f7a..870076ae23d 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -30,6 +30,7 @@ import { addDisposableListener, EventType } from 'vs/base/browser/dom'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IRecentFile } from 'vs/platform/history/common/history'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; +import { withNullAsUndefined } from 'vs/base/common/types'; export interface IDraggedResource { resource: URI; @@ -81,7 +82,12 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array { - resources.push({ resource: URI.parse(draggedEditor.resource), backupResource: draggedEditor.backupResource ? URI.parse(draggedEditor.backupResource) : undefined, viewState: draggedEditor.viewState, isExternal: false }); + resources.push({ + resource: URI.parse(draggedEditor.resource), + backupResource: draggedEditor.backupResource ? URI.parse(draggedEditor.backupResource) : undefined, + viewState: withNullAsUndefined(draggedEditor.viewState), + isExternal: false + }); }); } catch (error) { // Invalid transfer @@ -240,7 +246,7 @@ export class ResourcesDropHandler { return this.backupFileService.resolveBackupContent(droppedDirtyEditor.backupResource!).then(content => { // Set the contents of to the resource to the target - return this.backupFileService.backupResource(droppedDirtyEditor.resource, content!.create(this.getDefaultEOL()).createSnapshot(true)); + return this.backupFileService.backupResource(droppedDirtyEditor.resource, content.value.create(this.getDefaultEOL()).createSnapshot(true)); }).then(() => false, () => false /* ignore any error */); } @@ -260,7 +266,7 @@ export class ResourcesDropHandler { return Promise.all(fileOnDiskResources.map(fileOnDiskResource => { // Check for Workspace - if (hasWorkspaceFileExtension(fileOnDiskResource.fsPath)) { + if (hasWorkspaceFileExtension(fileOnDiskResource)) { urisToOpen.push({ workspaceUri: fileOnDiskResource }); return undefined; diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index 5778b3ff65e..f65ab63ac54 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -77,7 +77,7 @@ export class EditorDescriptor implements IEditorDescriptor { } describes(obj: unknown): boolean { - return obj instanceof BaseEditor && (obj).getId() === this.id; + return obj instanceof BaseEditor && obj.getId() === this.id; } } @@ -108,7 +108,7 @@ class EditorRegistry implements IEditorRegistry { const matchingDescriptors: EditorDescriptor[] = []; for (const editor of this.editors) { - const inputDescriptors = []>editor[INPUT_DESCRIPTORS_PROPERTY]; + const inputDescriptors: SyncDescriptor[] = editor[INPUT_DESCRIPTORS_PROPERTY]; for (const inputDescriptor of inputDescriptors) { const inputClass = inputDescriptor.ctor; diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index d4cc5b6851f..daafe77c133 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -21,7 +21,7 @@ import { ITextModel } from 'vs/editor/common/model'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; import { ILabelService } from 'vs/platform/label/common/label'; -import { getIconClasses, getConfiguredLangId } from 'vs/editor/common/services/getIconClasses'; +import { getIconClasses, detectModeId } from 'vs/editor/common/services/getIconClasses'; import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { withNullAsUndefined } from 'vs/base/common/types'; @@ -121,7 +121,16 @@ export class ResourceLabels extends Disposable { return; // ignore transitions in files from no mode to specific mode because this happens each time a model is created } - this._widgets.forEach(widget => widget.notifyModelModeChanged(e)); + this._widgets.forEach(widget => widget.notifyModelModeChanged(e.model)); + })); + + // notify when model is added + this._register(this.modelService.onModelAdded(model => { + if (!model.uri) { + return; // we need the resource to compare + } + + this._widgets.forEach(widget => widget.notifyModelAdded(model)); })); // notify when file decoration changes @@ -228,7 +237,7 @@ class ResourceLabelWidget extends IconLabel { private label?: IResourceLabelProps; private options?: IResourceLabelOptions; private computedIconClasses?: string[]; - private lastKnownConfiguredLangId?: string; + private lastKnownDetectedModeId?: string; private computedPathLabel?: string; private needsRedraw?: Redraw; @@ -258,13 +267,21 @@ class ResourceLabelWidget extends IconLabel { } } - notifyModelModeChanged(e: { model: ITextModel; oldModeId: string; }): void { + notifyModelModeChanged(model: ITextModel): void { + this.handleModelEvent(model); + } + + notifyModelAdded(model: ITextModel): void { + this.handleModelEvent(model); + } + + private handleModelEvent(model: ITextModel): void { if (!this.label || !this.label.resource) { return; // only update if label exists } - if (e.model.uri.toString() === this.label.resource.toString()) { - if (this.lastKnownConfiguredLangId !== e.model.getLanguageIdentifier().language) { + if (model.uri.toString() === this.label.resource.toString()) { + if (this.lastKnownDetectedModeId !== model.getModeId()) { this.render(true); // update if the language id of the model has changed from our last known state } } @@ -333,7 +350,7 @@ class ResourceLabelWidget extends IconLabel { setEditor(editor: IEditorInput, options?: IResourceLabelOptions): void { this.setResource({ - resource: withNullAsUndefined(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })), + resource: toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }), name: withNullAsUndefined(editor.getName()), description: withNullAsUndefined(editor.getDescription()) }, options); @@ -367,7 +384,7 @@ class ResourceLabelWidget extends IconLabel { clear(): void { this.label = undefined; this.options = undefined; - this.lastKnownConfiguredLangId = undefined; + this.lastKnownDetectedModeId = undefined; this.computedIconClasses = undefined; this.computedPathLabel = undefined; @@ -388,10 +405,10 @@ class ResourceLabelWidget extends IconLabel { } if (this.label) { - const configuredLangId = this.label.resource ? withNullAsUndefined(getConfiguredLangId(this.modelService, this.modeService, this.label.resource)) : undefined; - if (this.lastKnownConfiguredLangId !== configuredLangId) { + const detectedModeId = this.label.resource ? withNullAsUndefined(detectModeId(this.modelService, this.modeService, this.label.resource)) : undefined; + if (this.lastKnownDetectedModeId !== detectedModeId) { clearIconCache = true; - this.lastKnownConfiguredLangId = configuredLangId; + this.lastKnownDetectedModeId = detectedModeId; } } @@ -465,7 +482,7 @@ class ResourceLabelWidget extends IconLabel { this.label = undefined; this.options = undefined; - this.lastKnownConfiguredLangId = undefined; + this.lastKnownDetectedModeId = undefined; this.computedIconClasses = undefined; this.computedPathLabel = undefined; } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index ac14f2c388c..313743ea6e3 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -10,8 +10,7 @@ import { onDidChangeFullscreen, isFullscreen, getZoomFactor } from 'vs/base/brow import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { IUntitledResourceInput, IResourceDiffInput } from 'vs/workbench/common/editor'; +import { pathsToEditors } from 'vs/workbench/common/editor'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; @@ -24,7 +23,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IInstantiationService, ServicesAccessor, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { LifecyclePhase, StartupKind, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWindowService, IPath, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { IWindowService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -34,7 +33,7 @@ import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; -import { coalesce } from 'vs/base/common/arrays'; +import { IFileService } from 'vs/platform/files/common/files'; enum Settings { MENUBAR_VISIBLE = 'window.menuBarVisibility', @@ -182,7 +181,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.registerLayoutListeners(); // State - this.initLayoutState(accessor.get(ILifecycleService)); + this.initLayoutState(accessor.get(ILifecycleService), accessor.get(IFileService)); } private registerLayoutListeners(): void { @@ -319,7 +318,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } - private initLayoutState(lifecycleService: ILifecycleService): void { + private initLayoutState(lifecycleService: ILifecycleService, fileService: IFileService): void { // Fullscreen this.state.fullscreen = isFullscreen(); @@ -358,7 +357,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.editor.restoreCentered = this.storageService.getBoolean(Storage.CENTERED_LAYOUT_ENABLED, StorageScope.WORKSPACE, false); // Editors to open - this.state.editor.editorsToOpen = this.resolveEditorsToOpen(); + this.state.editor.editorsToOpen = this.resolveEditorsToOpen(fileService); // Panel visibility this.state.panel.hidden = this.storageService.getBoolean(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE, true); @@ -389,7 +388,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.zenMode.restore = this.storageService.getBoolean(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE, false) && this.configurationService.getValue(Settings.ZEN_MODE_RESTORE); } - private resolveEditorsToOpen(): Promise | IResourceEditor[] { + private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditor[] { const configuration = this.environmentService.configuration; const hasInitialFilesToOpen = this.hasInitialFilesToOpen(); @@ -400,21 +399,19 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (hasInitialFilesToOpen) { // Files to diff is exclusive - const filesToDiff = this.toInputs(configuration.filesToDiff, false); - if (filesToDiff && filesToDiff.length === 2) { - return [{ - leftResource: filesToDiff[0].resource, - rightResource: filesToDiff[1].resource, - options: { pinned: true }, - forceFile: true - }]; - } + return pathsToEditors(configuration.filesToDiff, fileService).then(filesToDiff => { + if (filesToDiff && filesToDiff.length === 2) { + return [{ + leftResource: filesToDiff[0].resource, + rightResource: filesToDiff[1].resource, + options: { pinned: true }, + forceFile: true + }]; + } - const filesToCreate = this.toInputs(configuration.filesToCreate, true); - const filesToOpen = this.toInputs(configuration.filesToOpen, false); - - // Otherwise: Open/Create files - return [...filesToOpen, ...filesToCreate]; + // Otherwise: Open/Create files + return pathsToEditors(configuration.filesToOpenOrCreate, fileService); + }); } // Empty workbench @@ -428,7 +425,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return []; // do not open any empty untitled file if we have backups to restore } - return [{}]; + return [Object.create(null)]; // open empty untitled file }); } @@ -439,38 +436,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const configuration = this.environmentService.configuration; return !!( - (configuration.filesToCreate && configuration.filesToCreate.length > 0) || - (configuration.filesToOpen && configuration.filesToOpen.length > 0) || - (configuration.filesToDiff && configuration.filesToDiff.length > 0)); - } - - private toInputs(paths: IPath[] | undefined, isNew: boolean): Array { - if (!paths || !paths.length) { - return []; - } - - return coalesce(paths.map(p => { - const resource = p.fileUri; - if (!resource) { - return undefined; - } - - let input: IResourceInput | IUntitledResourceInput; - if (isNew) { - input = { filePath: resource.fsPath, options: { pinned: true } }; - } else { - input = { resource, options: { pinned: true }, forceFile: true }; - } - - if (!isNew && typeof p.lineNumber === 'number') { - input.options!.selection = { - startLineNumber: p.lineNumber, - startColumn: p.columnNumber || 1 - }; - } - - return input; - })); + (configuration.filesToOpenOrCreate && configuration.filesToOpenOrCreate.length > 0) || + (configuration.filesToDiff && configuration.filesToDiff.length > 0) + ); } private updatePanelPosition() { diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 488552dc37c..a467be6485b 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -39,6 +39,10 @@ body { user-select: none; } +body.web { + position: fixed; /* prevent bounce effect */ +} + @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .monaco-workbench { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 0b90d082300..4e4f5523191 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -18,7 +18,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { ActivityAction, ActivityActionItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; +import { ActivityAction, ActivityActionViewItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { IActivity, IGlobalActivity } from 'vs/workbench/common/activity'; @@ -110,7 +110,7 @@ export class GlobalActivityAction extends ActivityAction { } } -export class GlobalActivityActionItem extends ActivityActionItem { +export class GlobalActivityActionViewItem extends ActivityActionViewItem { constructor( action: GlobalActivityAction, diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 731f56fb415..2b4586e1c88 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -10,7 +10,7 @@ import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/acti import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/common/activity'; import { Registry } from 'vs/platform/registry/common/platform'; import { Part } from 'vs/workbench/browser/part'; -import { GlobalActivityActionItem, GlobalActivityAction, ViewletActivityAction, ToggleViewletAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewletActivityAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; +import { GlobalActivityActionViewItem, GlobalActivityAction, ViewletActivityAction, ToggleViewletAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewletActivityAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; @@ -76,7 +76,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @IViewsService private readonly viewsService: IViewsService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); @@ -87,7 +87,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } - this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, this.cachedViewlets.map(v => ({ id: v.id, name: undefined, visible: v.visible, order: v.order, pinned: v.pinned })), { + this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, this.cachedViewlets.map(v => ({ id: v.id, name: undefined, visible: v.visible, order: v.order, pinned: v.pinned })), { icon: true, orientation: ActionsOrientation.VERTICAL, openComposite: (compositeId: string) => this.viewletService.openViewlet(compositeId, true), @@ -150,7 +150,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { if (viewContainer && viewContainer.hideIfEmpty) { const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); if (viewDescriptors && viewDescriptors.activeViewDescriptors.length === 0) { - this.removeComposite(viewletDescriptor.id, true); // Update the composite bar by hiding + this.hideComposite(viewletDescriptor.id); // Update the composite bar by hiding } } } @@ -218,7 +218,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { } private getActivitybarItemColors(theme: ITheme): ICompositeBarColors { - return { + return { activeForegroundColor: theme.getColor(ACTIVITY_BAR_FOREGROUND), inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_INACTIVE_FOREGROUND), badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), @@ -236,7 +236,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { .map(a => new GlobalActivityAction(a)); this.globalActionBar = this._register(new ActionBar(container, { - actionItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionItem, a, (theme: ITheme) => this.getActivitybarItemColors(theme)), + actionViewItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionViewItem, a, (theme: ITheme) => this.getActivitybarItemColors(theme)), orientation: ActionsOrientation.VERTICAL, ariaLabel: nls.localize('globalActions', "Global Actions"), animated: false @@ -309,14 +309,14 @@ export class ActivitybarPart extends Part implements IActivityBarService { disposable.dispose(); } this.viewletDisposables.delete(viewletId); - this.removeComposite(viewletId, true); + this.hideComposite(viewletId); } private onDidChangeActiveViews(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection): void { if (viewDescriptors.activeViewDescriptors.length) { this.compositeBar.addComposite(viewlet); } else { - this.removeComposite(viewlet.id, true); + this.hideComposite(viewlet.id); } } @@ -334,18 +334,13 @@ export class ActivitybarPart extends Part implements IActivityBarService { const viewlets = this.viewletService.getViewlets(); for (const { id } of this.cachedViewlets) { if (viewlets.every(viewlet => viewlet.id !== id)) { - this.removeComposite(id, false); + this.hideComposite(id); } } } - private removeComposite(compositeId: string, hide: boolean): void { - if (hide) { - this.compositeBar.hideComposite(compositeId); - } else { - this.compositeBar.removeComposite(compositeId); - } - + private hideComposite(compositeId: string): void { + this.compositeBar.hideComposite(compositeId); const compositeActions = this.compositeActions[compositeId]; if (compositeActions) { compositeActions.activityAction.dispose(); @@ -385,7 +380,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { // Layout composite bar let availableHeight = contentAreaSize.height; if (this.globalActionBar) { - availableHeight -= (this.globalActionBar.items.length * ActivitybarPart.ACTION_HEIGHT); // adjust height for global actions showing + availableHeight -= (this.globalActionBar.viewItems.length * ActivitybarPart.ACTION_HEIGHT); // adjust height for global actions showing } this.compositeBar.layout(new Dimension(width, availableHeight)); } @@ -441,7 +436,9 @@ export class ActivitybarPart extends Part implements IActivityBarService { } } } - state.push({ id: compositeItem.id, iconUrl: viewlet.iconUrl && viewlet.iconUrl.scheme === Schemas.file ? viewlet.iconUrl : undefined, views, pinned: compositeItem && compositeItem.pinned, order: compositeItem ? compositeItem.order : undefined, visible: compositeItem && compositeItem.visible }); + state.push({ id: compositeItem.id, iconUrl: viewlet.iconUrl && viewlet.iconUrl.scheme === Schemas.file ? viewlet.iconUrl : undefined, views, pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible }); + } else { + state.push({ id: compositeItem.id, pinned: compositeItem.pinned, order: compositeItem.order, visible: false }); } } @@ -449,9 +446,9 @@ export class ActivitybarPart extends Part implements IActivityBarService { } private getCachedViewlets(): ICachedViewlet[] { - const storedStates = >JSON.parse(this.cachedViewletsValue); - const cachedViewlets = storedStates.map(c => { - const serialized: ICachedViewlet = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true, iconUrl: undefined, views: undefined } : c; + const storedStates: Array = JSON.parse(this.cachedViewletsValue); + const cachedViewlets = storedStates.map(c => { + const serialized: ICachedViewlet = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true, iconUrl: undefined, views: undefined } : c; serialized.visible = isUndefinedOrNull(serialized.visible) ? true : serialized.visible; return serialized; }); @@ -469,7 +466,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private loadOldCachedViewlets(): ICachedViewlet[] { const previousState = this.storageService.get('workbench.activity.placeholderViewlets', StorageScope.GLOBAL, '[]'); - const result = (JSON.parse(previousState)); + const result: ICachedViewlet[] = JSON.parse(previousState); this.storageService.remove('workbench.activity.placeholderViewlets', StorageScope.GLOBAL); return result; diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index ac4cacb55f0..8e9b5bd0a4f 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -11,7 +11,7 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { CompositeActionItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionItem, ActivityAction, ICompositeBar, ICompositeBarColors, DraggedCompositeIdentifier } from 'vs/workbench/browser/parts/compositeBarActions'; +import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors, DraggedCompositeIdentifier } from 'vs/workbench/browser/parts/compositeBarActions'; import { Dimension, $, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -50,7 +50,7 @@ export class CompositeBar extends Widget implements ICompositeBar { private compositeSwitcherBar: ActionBar; private compositeOverflowAction: CompositeOverflowActivityAction | null; - private compositeOverflowActionItem: CompositeOverflowActivityActionItem | undefined; + private compositeOverflowActionViewItem: CompositeOverflowActivityActionViewItem | undefined; private model: CompositeBarModel; private visibleComposites: string[]; @@ -93,12 +93,12 @@ export class CompositeBar extends Widget implements ICompositeBar { create(parent: HTMLElement): HTMLElement { const actionBarDiv = parent.appendChild($('.composite-bar')); this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, { - actionItemProvider: (action: Action) => { + actionViewItemProvider: (action: Action) => { if (action instanceof CompositeOverflowActivityAction) { - return this.compositeOverflowActionItem; + return this.compositeOverflowActionViewItem; } const item = this.model.findItem(action.id); - return item && this.instantiationService.createInstance(CompositeActionItem, action, item.pinnedAction, () => this.getContextMenuActions(), this.options.colors, this.options.icon, this); + return item && this.instantiationService.createInstance(CompositeActionViewItem, action, item.pinnedAction, () => this.getContextMenuActions(), this.options.colors, this.options.icon, this); }, orientation: this.options.orientation, ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"), @@ -278,13 +278,13 @@ export class CompositeBar extends Widget implements ICompositeBar { } else { if (this.dimension && this.dimension.height !== 0 && this.dimension.width !== 0) { // Compute sizes only if visible. Otherwise the size measurment would be computed wrongly. - const currentItemsLength = this.compositeSwitcherBar.items.length; + const currentItemsLength = this.compositeSwitcherBar.viewItems.length; this.compositeSwitcherBar.push(items.map(composite => composite.activityAction)); items.map((composite, index) => this.compositeSizeInBar.set(composite.id, this.options.orientation === ActionsOrientation.VERTICAL ? this.compositeSwitcherBar.getHeight(currentItemsLength + index) : this.compositeSwitcherBar.getWidth(currentItemsLength + index) )); - items.forEach(() => this.compositeSwitcherBar.pull(this.compositeSwitcherBar.items.length - 1)); + items.forEach(() => this.compositeSwitcherBar.pull(this.compositeSwitcherBar.viewItems.length - 1)); } } } @@ -343,10 +343,10 @@ export class CompositeBar extends Widget implements ICompositeBar { this.compositeOverflowAction.dispose(); this.compositeOverflowAction = null; - if (this.compositeOverflowActionItem) { - this.compositeOverflowActionItem.dispose(); + if (this.compositeOverflowActionViewItem) { + this.compositeOverflowActionViewItem.dispose(); } - this.compositeOverflowActionItem = undefined; + this.compositeOverflowActionViewItem = undefined; } // Pull out composites that overflow or got hidden @@ -357,9 +357,9 @@ export class CompositeBar extends Widget implements ICompositeBar { } }); compositesToRemove.reverse().forEach(index => { - const actionItem = this.compositeSwitcherBar.items[index]; + const actionViewItem = this.compositeSwitcherBar.viewItems[index]; this.compositeSwitcherBar.pull(index); - actionItem.dispose(); + actionViewItem.dispose(); this.visibleComposites.splice(index, 1); }); @@ -368,9 +368,9 @@ export class CompositeBar extends Widget implements ICompositeBar { const currentIndex = this.visibleComposites.indexOf(compositeId); if (newIndex !== currentIndex) { if (currentIndex !== -1) { - const actionItem = this.compositeSwitcherBar.items[currentIndex]; + const actionViewItem = this.compositeSwitcherBar.viewItems[currentIndex]; this.compositeSwitcherBar.pull(currentIndex); - actionItem.dispose(); + actionViewItem.dispose(); this.visibleComposites.splice(currentIndex, 1); } @@ -382,12 +382,12 @@ export class CompositeBar extends Widget implements ICompositeBar { // Add overflow action as needed if ((visibleCompositesChange && overflows) || this.compositeSwitcherBar.length() === 0) { this.compositeOverflowAction = this.instantiationService.createInstance(CompositeOverflowActivityAction, () => { - if (this.compositeOverflowActionItem) { - this.compositeOverflowActionItem.showMenu(); + if (this.compositeOverflowActionViewItem) { + this.compositeOverflowActionViewItem.showMenu(); } }); - this.compositeOverflowActionItem = this.instantiationService.createInstance( - CompositeOverflowActivityActionItem, + this.compositeOverflowActionViewItem = this.instantiationService.createInstance( + CompositeOverflowActivityActionViewItem, this.compositeOverflowAction, () => this.getOverflowingComposites(), () => this.model.activeItem ? this.model.activeItem.id : undefined, diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index f6763258e5e..59e89ca7df4 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; -import { BaseActionItem, IBaseActionItemOptions, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { BaseActionViewItem, IBaseActionViewItemOptions, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { dispose, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -118,16 +118,16 @@ export interface ICompositeBarColors { dragAndDropBackground?: Color; } -export interface IActivityActionItemOptions extends IBaseActionItemOptions { +export interface IActivityActionViewItemOptions extends IBaseActionViewItemOptions { icon?: boolean; colors: (theme: ITheme) => ICompositeBarColors; } -export class ActivityActionItem extends BaseActionItem { +export class ActivityActionViewItem extends BaseActionViewItem { protected container: HTMLElement; protected label: HTMLElement; protected badge: HTMLElement; - protected options: IActivityActionItemOptions; + protected options: IActivityActionViewItemOptions; private badgeContent: HTMLElement; private badgeDisposable: IDisposable = Disposable.None; @@ -135,7 +135,7 @@ export class ActivityActionItem extends BaseActionItem { constructor( action: ActivityAction, - options: IActivityActionItemOptions, + options: IActivityActionViewItemOptions, @IThemeService protected themeService: IThemeService ) { super(null, action, options); @@ -347,7 +347,7 @@ export class CompositeOverflowActivityAction extends ActivityAction { } } -export class CompositeOverflowActivityActionItem extends ActivityActionItem { +export class CompositeOverflowActivityActionViewItem extends ActivityActionViewItem { private actions: Action[]; constructor( @@ -428,7 +428,7 @@ export class DraggedCompositeIdentifier { } } -export class CompositeActionItem extends ActivityActionItem { +export class CompositeActionViewItem extends ActivityActionViewItem { private static manageExtensionAction: ManageExtensionAction; @@ -451,8 +451,8 @@ export class CompositeActionItem extends ActivityActionItem { this.compositeTransfer = LocalSelectionTransfer.getInstance(); - if (!CompositeActionItem.manageExtensionAction) { - CompositeActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); + if (!CompositeActionViewItem.manageExtensionAction) { + CompositeActionViewItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction); } this._register(compositeActivityAction.onDidChangeActivity(() => { this.compositeActivity = null; this.updateActivity(); }, this)); @@ -569,7 +569,7 @@ export class CompositeActionItem extends ActivityActionItem { const actions: Action[] = [this.toggleCompositePinnedAction]; if ((this.compositeActivityAction.activity).extensionId) { actions.push(new Separator()); - actions.push(CompositeActionItem.manageExtensionAction); + actions.push(CompositeActionViewItem.manageExtensionAction); } const isPinned = this.compositeBar.isPinned(this.activity.id); diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 064d003ebde..adbbceb087c 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -11,7 +11,7 @@ import * as strings from 'vs/base/common/strings'; import { Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { prepareActions } from 'vs/workbench/browser/actions'; import { IAction } from 'vs/base/common/actions'; @@ -399,7 +399,7 @@ export abstract class CompositePart extends Part { // Toolbar this.toolBar = this._register(new ToolBar(titleActionsContainer, this.contextMenuService, { - actionItemProvider: action => this.actionItemProvider(action), + actionViewItemProvider: action => this.actionViewItemProvider(action), orientation: ActionsOrientation.HORIZONTAL, getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment() @@ -432,11 +432,11 @@ export abstract class CompositePart extends Part { this.titleLabel.updateStyles(); } - protected actionItemProvider(action: IAction): IActionItem | undefined { + protected actionViewItemProvider(action: IAction): IActionViewItem | undefined { // Check Active Composite if (this.activeComposite) { - return this.activeComposite.getActionItem(action); + return this.activeComposite.getActionViewItem(action); } return undefined; diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index 94d361655aa..76839dbde39 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -15,10 +15,11 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ResourceViewerContext, ResourceViewer } from 'vs/workbench/browser/parts/editor/resourceViewer'; import { URI } from 'vs/base/common/uri'; import { Dimension, size, clearNode } from 'vs/base/browser/dom'; -import { IFileService } from 'vs/platform/files/common/files'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { CancellationToken } from 'vs/base/common/cancellation'; import { dispose } from 'vs/base/common/lifecycle'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export interface IOpenCallbacks { openInternal: (input: EditorInput, options: EditorOptions) => Promise; @@ -47,7 +48,8 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { callbacks: IOpenCallbacks, telemetryService: ITelemetryService, themeService: IThemeService, - @IFileService private readonly _fileService: IFileService, + @ITextFileService private readonly textFileService: ITextFileService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IStorageService storageService: IStorageService ) { super(id, telemetryService, themeService, storageService); @@ -89,12 +91,14 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { // Render Input this.resourceViewerContext = ResourceViewer.show( { name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() }, - this._fileService, + this.textFileService, this.binaryContainer, this.scrollbar, - resource => this.handleOpenInternalCallback(input, options), - resource => this.callbacks.openExternal(resource), - meta => this.handleMetadataChanged(meta) + { + openInternalClb: _ => this.handleOpenInternalCallback(input, options), + openExternalClb: this.environmentService.configuration.remoteAuthority ? undefined : resource => this.callbacks.openExternal(resource), + metadataClb: meta => this.handleMetadataChanged(meta) + } ); return undefined; diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 5b01084e576..5cd799b070a 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -47,6 +47,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { onDidChangeZoomLevel } from 'vs/base/browser/browser'; import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; +import { ILabelService } from 'vs/platform/label/common/label'; class Item extends BreadcrumbsItem { @@ -69,7 +70,7 @@ class Item extends BreadcrumbsItem { return false; } if (this.element instanceof FileElement && other.element instanceof FileElement) { - return isEqual(this.element.uri, other.element.uri); + return isEqual(this.element.uri, other.element.uri, false); } if (this.element instanceof TreeElement && other.element instanceof TreeElement) { return this.element.id === other.element.id; @@ -167,6 +168,7 @@ export class BreadcrumbsControl { @IConfigurationService private readonly _configurationService: IConfigurationService, @IFileService private readonly _fileService: IFileService, @ITelemetryService private readonly _telemetryService: ITelemetryService, + @ILabelService private readonly _labelService: ILabelService, @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService, ) { this.domNode = document.createElement('div'); @@ -238,16 +240,18 @@ export class BreadcrumbsControl { this._ckBreadcrumbsVisible.set(true); this._ckBreadcrumbsPossible.set(true); - let editor = this._getActiveCodeEditor(); - let model = new EditorBreadcrumbsModel(input.getResource()!, editor, this._workspaceService, this._configurationService); + const uri = input.getResource()!; + const editor = this._getActiveCodeEditor(); + const model = new EditorBreadcrumbsModel(uri, editor, this._workspaceService, this._configurationService); dom.toggleClass(this.domNode, 'relative-path', model.isRelative()); + dom.toggleClass(this.domNode, 'backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\'); - let updateBreadcrumbs = () => { - let items = model.getElements().map(element => new Item(element, this._options, this._instantiationService)); + const updateBreadcrumbs = () => { + const items = model.getElements().map(element => new Item(element, this._options, this._instantiationService)); this._widget.setItems(items); this._widget.reveal(items[items.length - 1]); }; - let listener = model.onDidUpdate(updateBreadcrumbs); + const listener = model.onDidUpdate(updateBreadcrumbs); updateBreadcrumbs(); this._breadcrumbsDisposables = [model, listener]; diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 9b038dbf6a0..d2147f4c505 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -388,21 +388,14 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { const labels = this._instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER /* TODO@Jo visibility propagation */); this._disposables.push(labels); - return this._instantiationService.createInstance( - WorkbenchAsyncDataTree, - container, - new FileVirtualDelegate(), - [this._instantiationService.createInstance(FileRenderer, labels)], - this._instantiationService.createInstance(FileDataSource), - { - filterOnType: true, - multipleSelectionSupport: false, - sorter: new FileSorter(), - filter: this._instantiationService.createInstance(FileFilter), - identityProvider: new FileIdentityProvider(), - keyboardNavigationLabelProvider: new FileNavigationLabelProvider() - } - ) as WorkbenchAsyncDataTree; + return this._instantiationService.createInstance(WorkbenchAsyncDataTree, container, new FileVirtualDelegate(), [this._instantiationService.createInstance(FileRenderer, labels)], this._instantiationService.createInstance(FileDataSource), { + filterOnType: true, + multipleSelectionSupport: false, + sorter: new FileSorter(), + filter: this._instantiationService.createInstance(FileFilter), + identityProvider: new FileIdentityProvider(), + keyboardNavigationLabelProvider: new FileNavigationLabelProvider() + }) as WorkbenchAsyncDataTree; } _setInput(element: BreadcrumbElement): Promise { diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 99e6640c4ba..a8abe040f01 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -47,7 +47,6 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { isMacintosh } from 'vs/base/common/platform'; import { AllEditorsPicker, ActiveEditorGroupPicker } from 'vs/workbench/browser/parts/editor/editorPicker'; -import { Schemas } from 'vs/base/common/network'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { ZoomStatusbarItem } from 'vs/workbench/browser/parts/editor/resourceViewer'; @@ -105,7 +104,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( interface ISerializedUntitledEditorInput { resource: string; resourceJSON: object; - modeId: string | null; + modeId: string | undefined; encoding: string; } @@ -132,7 +131,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory { const serialized: ISerializedUntitledEditorInput = { resource: resource.toString(), // Keep for backwards compatibility resourceJSON: resource.toJSON(), - modeId: untitledEditorInput.getModeId(), + modeId: untitledEditorInput.getMode(), encoding: untitledEditorInput.getEncoding() }; @@ -143,11 +142,10 @@ class UntitledEditorInputFactory implements IEditorInputFactory { return instantiationService.invokeFunction(accessor => { const deserialized: ISerializedUntitledEditorInput = JSON.parse(serializedEditorInput); const resource = !!deserialized.resourceJSON ? URI.revive(deserialized.resourceJSON) : URI.parse(deserialized.resource); - const filePath = resource.scheme === Schemas.untitled ? undefined : resource.scheme === Schemas.file ? resource.fsPath : resource.path; - const language = deserialized.modeId; + const mode = deserialized.modeId; const encoding = deserialized.encoding; - return accessor.get(IEditorService).createInput({ resource, filePath, language, encoding }) as UntitledEditorInput; + return accessor.get(IEditorService).createInput({ resource, mode, encoding, forceUntitled: true }) as UntitledEditorInput; }); } } diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 1ae07b4a378..1d6d3e15151 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -449,7 +449,7 @@ export function toEditorQuickOpenEntry(element: any): IEditorQuickOpenEntry | nu // QuickOpenEntryGroup if (element instanceof QuickOpenEntryGroup) { - const group = element; + const group = element; if (group.getEntry()) { element = group.getEntry(); } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 710296c74e6..fa7d3c0dd69 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -40,14 +40,15 @@ import { CLOSE_EDITOR_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor import { NoTabsTitleControl } from 'vs/workbench/browser/parts/editor/noTabsTitleControl'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { isErrorWithActions, IErrorWithActions } from 'vs/base/common/errorsWithActions'; import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; import { withNullAsUndefined } from 'vs/base/common/types'; import { hash } from 'vs/base/common/hash'; import { guessMimeTypes } from 'vs/base/common/mime'; -import { extname } from 'vs/base/common/path'; +import { extname } from 'vs/base/common/resources'; +import { Schemas } from 'vs/base/common/network'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -520,8 +521,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const descriptor = editor.getTelemetryDescriptor(); const resource = editor.getResource(); - if (resource && resource.fsPath) { - descriptor['resource'] = { mimeType: guessMimeTypes(resource.fsPath).join(', '), scheme: resource.scheme, ext: extname(resource.fsPath), path: hash(resource.fsPath) }; + const path = resource ? resource.scheme === Schemas.file ? resource.fsPath : resource.path : undefined; + if (resource && path) { + descriptor['resource'] = { mimeType: guessMimeTypes(path).join(', '), scheme: resource.scheme, ext: extname(resource), path: hash(path) }; /* __GDPR__FRAGMENT__ "EditorTelemetryDescriptor" : { diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts index edb8142f7de..6b30c32d092 100644 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ b/src/vs/workbench/browser/parts/editor/editorPicker.ts @@ -18,6 +18,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { EditorInput, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { withNullAsUndefined } from 'vs/base/common/types'; export class EditorPickerEntry extends QuickOpenEntryGroup { @@ -32,13 +33,13 @@ export class EditorPickerEntry extends QuickOpenEntryGroup { getLabelOptions(): IIconLabelValueOptions { return { - extraClasses: getIconClasses(this.modelService, this.modeService, this.getResource() || undefined), + extraClasses: getIconClasses(this.modelService, this.modeService, this.getResource()), italic: !this._group.isPinned(this.editor) }; } getLabel() { - return this.editor.getName(); + return withNullAsUndefined(this.editor.getName()); } getIcon(): string { @@ -58,7 +59,7 @@ export class EditorPickerEntry extends QuickOpenEntryGroup { } getDescription() { - return this.editor.getDescription(); + return withNullAsUndefined(this.editor.getDescription()); } run(mode: Mode, context: IEntryRunContext): boolean { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index dbeebced34a..48bbdff9a7f 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -14,11 +14,11 @@ import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { Action } from 'vs/base/common/actions'; import { Language } from 'vs/base/common/platform'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; -import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor } from 'vs/workbench/common/editor'; +import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor'; import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IEditorAction } from 'vs/editor/common/editorCommon'; -import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model'; +import { EndOfLineSequence } from 'vs/editor/common/model'; import { IModelLanguageChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { TrimTrailingWhitespaceAction } from 'vs/editor/contrib/linesOperations/linesOperations'; import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction } from 'vs/editor/contrib/indentation/indentation'; @@ -26,7 +26,7 @@ import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/bina import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { SUPPORTED_ENCODINGS, IFileService, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; +import { IFileService, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -35,7 +35,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; @@ -59,7 +59,15 @@ class SideBySideEditorEncodingSupport implements IEncodingSupport { } setEncoding(encoding: string, mode: EncodingMode): void { - [this.master, this.details].forEach(s => s.setEncoding(encoding, mode)); + [this.master, this.details].forEach(editor => editor.setEncoding(encoding, mode)); + } +} + +class SideBySideEditorModeSupport implements IModeSupport { + constructor(private master: IModeSupport, private details: IModeSupport) { } + + setMode(mode: string): void { + [this.master, this.details].forEach(editor => editor.setMode(mode)); } } @@ -83,7 +91,7 @@ function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport | nu } // File or Resource Editor - let encodingSupport = input as IFileEditorInput; + const encodingSupport = input as IFileEditorInput; if (areFunctions(encodingSupport.setEncoding, encodingSupport.getEncoding)) { return encodingSupport; } @@ -92,14 +100,41 @@ function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport | nu return null; } +function toEditorWithModeSupport(input: IEditorInput): IModeSupport | null { + + // Untitled Editor + if (input instanceof UntitledEditorInput) { + return input; + } + + // Side by Side (diff) Editor + if (input instanceof SideBySideEditorInput) { + const masterModeSupport = toEditorWithModeSupport(input.master); + const detailsModeSupport = toEditorWithModeSupport(input.details); + + if (masterModeSupport && detailsModeSupport) { + return new SideBySideEditorModeSupport(masterModeSupport, detailsModeSupport); + } + + return masterModeSupport; + } + + // File or Resource Editor + const modeSupport = input as IFileEditorInput; + if (typeof modeSupport.setMode === 'function') { + return modeSupport; + } + + // Unsupported for any other editor + return null; +} + interface IEditorSelectionStatus { selections?: Selection[]; charactersSelected?: number; } class StateChange { - _stateChangeBrand: void; - indentation: boolean = false; selectionStatus: boolean = false; mode: boolean = false; @@ -120,7 +155,7 @@ class StateChange { this.metadata = this.metadata || other.metadata; } - public hasChanges(): boolean { + hasChanges(): boolean { return this.indentation || this.selectionStatus || this.mode @@ -179,42 +214,49 @@ class State { change.selectionStatus = true; } } + if ('indentation' in update) { if (this._indentation !== update.indentation) { this._indentation = update.indentation; change.indentation = true; } } + if ('mode' in update) { if (this._mode !== update.mode) { this._mode = update.mode; change.mode = true; } } + if ('encoding' in update) { if (this._encoding !== update.encoding) { this._encoding = update.encoding; change.encoding = true; } } + if ('EOL' in update) { if (this._EOL !== update.EOL) { this._EOL = update.EOL; change.EOL = true; } } + if ('tabFocusMode' in update) { if (this._tabFocusMode !== update.tabFocusMode) { this._tabFocusMode = update.tabFocusMode; change.tabFocusMode = true; } } + if ('screenReaderMode' in update) { if (this._screenReaderMode !== update.screenReaderMode) { this._screenReaderMode = update.screenReaderMode; change.screenReaderMode = true; } } + if ('metadata' in update) { if (this._metadata !== update.metadata) { this._metadata = update.metadata; @@ -236,7 +278,6 @@ const nlsTabFocusMode = nls.localize('tabFocusModeEnabled', "Tab Moves Focus"); const nlsScreenReaderDetected = nls.localize('screenReaderDetected', "Screen Reader Optimized"); const nlsScreenReaderDetectedTitle = nls.localize('screenReaderDetectedExtra', "If you are not using a Screen Reader, please change the setting `editor.accessibilitySupport` to \"off\"."); - class StatusBarItem { private _showing = true; @@ -248,15 +289,15 @@ class StatusBarItem { this.element.title = title; } - public set textContent(value: string) { + set textContent(value: string) { this.element.textContent = value; } - public set onclick(value: () => void) { + set onclick(value: () => void) { this.element.onclick = value; } - public setVisible(shouldShow: boolean): void { + setVisible(shouldShow: boolean): void { if (shouldShow !== this._showing) { this._showing = shouldShow; this.element.style.display = shouldShow ? '' : 'none'; @@ -264,7 +305,6 @@ class StatusBarItem { } } - export class EditorStatus implements IStatusbarItem { private state: State; private element: HTMLElement; @@ -661,7 +701,7 @@ export class EditorStatus implements IStatusbarItem { this.updateState(update); } - private _promptedScreenReader: boolean = false; + private promptedScreenReader: boolean = false; private onScreenReaderModeChange(editorWidget: ICodeEditor | undefined): void { let screenReaderMode = false; @@ -673,8 +713,8 @@ export class EditorStatus implements IStatusbarItem { const screenReaderConfiguration = this.configurationService.getValue('editor').accessibilitySupport; if (screenReaderConfiguration === 'auto') { // show explanation - if (!this._promptedScreenReader) { - this._promptedScreenReader = true; + if (!this.promptedScreenReader) { + this.promptedScreenReader = true; setTimeout(() => { this.onScreenReaderModeClick(); }, 100); @@ -771,7 +811,7 @@ export class EditorStatus implements IStatusbarItem { if (activeControl) { const activeResource = toResource(activeControl.input, { supportSideBySide: SideBySideEditor.MASTER }); if (activeResource && activeResource.toString() === resource.toString()) { - return this.onEncodingChange(activeControl); // only update if the encoding changed for the active resource + return this.onEncodingChange(activeControl); // only update if the encoding changed for the active resource } } } @@ -884,7 +924,7 @@ export class ChangeModeAction extends Action { } } - return { + return { label: lang, iconClasses: getIconClasses(this.modelService, this.modeService, fakeResource), description @@ -948,42 +988,27 @@ export class ChangeModeAction extends Action { // Change mode for active editor const activeEditor = this.editorService.activeEditor; - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - const models: ITextModel[] = []; - if (isCodeEditor(activeTextEditorWidget)) { - const codeEditorModel = activeTextEditorWidget.getModel(); - if (codeEditorModel) { - models.push(codeEditorModel); - } - } else if (isDiffEditor(activeTextEditorWidget)) { - const diffEditorModel = activeTextEditorWidget.getModel(); - if (diffEditorModel) { - if (diffEditorModel.original) { - models.push(diffEditorModel.original); - } - if (diffEditorModel.modified) { - models.push(diffEditorModel.modified); - } - } - } + if (activeEditor) { + const modeSupport = toEditorWithModeSupport(activeEditor); + if (modeSupport) { - // Find mode - let languageSelection: ILanguageSelection | undefined; - if (pick === autoDetectMode) { - if (textModel) { - const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); - if (resource) { - languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1)); + // Find mode + let languageSelection: ILanguageSelection | undefined; + if (pick === autoDetectMode) { + if (textModel) { + const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); + if (resource) { + languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1)); + } + } + } else { + languageSelection = this.modeService.createByLanguageName(pick.label); } - } - } else { - languageSelection = this.modeService.createByLanguageName(pick.label); - } - // Change mode - if (typeof languageSelection !== 'undefined') { - for (const textModel of models) { - this.modelService.setMode(textModel, languageSelection); + // Change mode + if (typeof languageSelection !== 'undefined') { + modeSupport.setMode(languageSelection.languageIdentifier.language); + } } } }); @@ -996,9 +1021,9 @@ export class ChangeModeAction extends Action { const languages = this.modeService.getRegisteredLanguageNames(); const picks: IQuickPickItem[] = languages.sort().map((lang, index) => { - const id = this.modeService.getModeIdForLanguageName(lang.toLowerCase()); + const id = withNullAsUndefined(this.modeService.getModeIdForLanguageName(lang.toLowerCase())); - return { + return { id, label: lang, description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : undefined @@ -1008,7 +1033,7 @@ export class ChangeModeAction extends Action { setTimeout(() => { this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || base) }).then(language => { if (language) { - const fileAssociationsConfig = this.configurationService.inspect(FILES_ASSOCIATIONS_CONFIG); + const fileAssociationsConfig = this.configurationService.inspect<{}>(FILES_ASSOCIATIONS_CONFIG); let associationKey: string; if (extension && base[0] !== '.') { @@ -1144,7 +1169,8 @@ export class ChangeEncodingAction extends Action { @IEditorService private readonly editorService: IEditorService, @IQuickInputService private readonly quickInputService: IQuickInputService, @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService, - @IFileService private readonly fileService: IFileService + @IFileService private readonly fileService: IFileService, + @ITextFileService private readonly textFileService: ITextFileService ) { super(actionId, actionLabel); } @@ -1158,6 +1184,7 @@ export class ChangeEncodingAction extends Action { if (!activeControl) { return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); } + const encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeControl.input); if (!encodingSupport) { return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]); @@ -1195,7 +1222,7 @@ export class ChangeEncodingAction extends Action { return Promise.resolve(null); // encoding detection only possible for resources the file service can handle } - return this.fileService.resolveContent(resource, { autoGuessEncoding: true, acceptTextOnly: true }).then(content => content.encoding, err => null); + return this.textFileService.read(resource, { autoGuessEncoding: true, acceptTextOnly: true }).then(content => content.encoding, err => null); }) .then((guessedEncoding: string) => { const isReopenWithEncoding = (action === reopenWithEncodingPick); @@ -1248,10 +1275,12 @@ export class ChangeEncodingAction extends Action { if (!encoding) { return; } + const activeControl = this.editorService.activeControl; if (!activeControl) { return; } + const encodingSupport = toEditorWithEncodingSupport(activeControl.input); if (typeof encoding.id !== 'undefined' && encodingSupport && encodingSupport.getEncoding() !== encoding.id) { encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding diff --git a/src/vs/workbench/browser/parts/editor/editorWidgets.ts b/src/vs/workbench/browser/parts/editor/editorWidgets.ts index 43ef52ce114..de5aacf5354 100644 --- a/src/vs/workbench/browser/parts/editor/editorWidgets.ts +++ b/src/vs/workbench/browser/parts/editor/editorWidgets.ts @@ -139,7 +139,7 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit return false; // we need a model } - if (!hasWorkspaceFileExtension(model.uri.fsPath)) { + if (!hasWorkspaceFileExtension(model.uri)) { return false; // we need a workspace file } diff --git a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css index 85e4d64ebb1..edce36d6ec8 100644 --- a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css @@ -54,7 +54,7 @@ background-image: none; } -.monaco-workbench.windows .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before { +.monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control.backslash-path .monaco-breadcrumb-item::before { content: '\\'; } diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 38036067d68..885211a42be 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -149,6 +149,12 @@ export class NoTabsTitleControl extends TitleControl { this.ifEditorIsActive(editor, () => this.redraw()); } + updateEditorLabels(): void { + if (this.group.activeEditor) { + this.updateEditorLabel(this.group.activeEditor); // we only have the active one to update + } + } + updateEditorDirty(editor: IEditorInput): void { this.ifEditorIsActive(editor, () => { if (editor.isDirty()) { diff --git a/src/vs/workbench/browser/parts/editor/resourceViewer.ts b/src/vs/workbench/browser/parts/editor/resourceViewer.ts index 84a8cf568fd..999c8064c6b 100644 --- a/src/vs/workbench/browser/parts/editor/resourceViewer.ts +++ b/src/vs/workbench/browser/parts/editor/resourceViewer.ts @@ -21,7 +21,7 @@ import { Action } from 'vs/base/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { memoize } from 'vs/base/common/decorators'; import * as platform from 'vs/base/common/platform'; -import { IFileService } from 'vs/platform/files/common/files'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; export interface IResourceDescriptor { readonly resource: URI; @@ -62,6 +62,12 @@ export interface ResourceViewerContext extends IDisposable { layout?(dimension: DOM.Dimension): void; } +interface ResourceViewerDelegate { + openInternalClb(uri: URI): void; + openExternalClb?(uri: URI): void; + metadataClb(meta: string): void; +} + /** * Helper to actually render the given resource into the provided container. Will adjust scrollbar (if provided) automatically based on loading * progress of the binary resource. @@ -72,12 +78,10 @@ export class ResourceViewer { static show( descriptor: IResourceDescriptor, - fileService: IFileService, + textFileService: ITextFileService, container: HTMLElement, scrollbar: DomScrollableElement, - openInternalClb: (uri: URI) => void, - openExternalClb: (uri: URI) => void, - metadataClb: (meta: string) => void + delegate: ResourceViewerDelegate ): ResourceViewerContext { // Ensure CSS class @@ -85,17 +89,17 @@ export class ResourceViewer { // Images if (ResourceViewer.isImageResource(descriptor)) { - return ImageView.create(container, descriptor, fileService, scrollbar, openExternalClb, metadataClb); + return ImageView.create(container, descriptor, textFileService, scrollbar, delegate); } // Large Files if (descriptor.size > ResourceViewer.MAX_OPEN_INTERNAL_SIZE) { - return FileTooLargeFileView.create(container, descriptor, scrollbar, metadataClb); + return FileTooLargeFileView.create(container, descriptor, scrollbar, delegate); } // Seemingly Binary Files else { - return FileSeemsBinaryFileView.create(container, descriptor, scrollbar, openInternalClb, metadataClb); + return FileSeemsBinaryFileView.create(container, descriptor, scrollbar, delegate); } } @@ -114,16 +118,15 @@ class ImageView { static create( container: HTMLElement, descriptor: IResourceDescriptor, - fileService: IFileService, + textFileService: ITextFileService, scrollbar: DomScrollableElement, - openExternalClb: (uri: URI) => void, - metadataClb: (meta: string) => void + delegate: ResourceViewerDelegate ): ResourceViewerContext { if (ImageView.shouldShowImageInline(descriptor)) { - return InlineImageView.create(container, descriptor, fileService, scrollbar, metadataClb); + return InlineImageView.create(container, descriptor, textFileService, scrollbar, delegate); } - return LargeImageView.create(container, descriptor, openExternalClb, metadataClb); + return LargeImageView.create(container, descriptor, delegate); } private static shouldShowImageInline(descriptor: IResourceDescriptor): boolean { @@ -150,11 +153,10 @@ class LargeImageView { static create( container: HTMLElement, descriptor: IResourceDescriptor, - openExternalClb: (uri: URI) => void, - metadataClb: (meta: string) => void + delegate: ResourceViewerDelegate ) { const size = BinarySize.formatSize(descriptor.size); - metadataClb(size); + delegate.metadataClb(size); DOM.clearNode(container); @@ -164,12 +166,13 @@ class LargeImageView { label.textContent = nls.localize('largeImageError', "The image is not displayed in the editor because it is too large ({0}).", size); container.appendChild(label); - if (descriptor.resource.scheme !== Schemas.data) { + const openExternal = delegate.openExternalClb; + if (descriptor.resource.scheme === Schemas.file && openExternal) { const link = DOM.append(label, DOM.$('a.embedded-link')); link.setAttribute('role', 'button'); link.textContent = nls.localize('resourceOpenExternalButton', "Open image using external program?"); - disposables.push(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => openExternalClb(descriptor.resource))); + disposables.push(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => openExternal(descriptor.resource))); } return combinedDisposable(disposables); @@ -181,10 +184,10 @@ class FileTooLargeFileView { container: HTMLElement, descriptor: IResourceDescriptor, scrollbar: DomScrollableElement, - metadataClb: (meta: string) => void + delegate: ResourceViewerDelegate ) { const size = BinarySize.formatSize(descriptor.size); - metadataClb(size); + delegate.metadataClb(size); DOM.clearNode(container); @@ -203,10 +206,9 @@ class FileSeemsBinaryFileView { container: HTMLElement, descriptor: IResourceDescriptor, scrollbar: DomScrollableElement, - openInternalClb: (uri: URI) => void, - metadataClb: (meta: string) => void + delegate: ResourceViewerDelegate ) { - metadataClb(typeof descriptor.size === 'number' ? BinarySize.formatSize(descriptor.size) : ''); + delegate.metadataClb(typeof descriptor.size === 'number' ? BinarySize.formatSize(descriptor.size) : ''); DOM.clearNode(container); @@ -221,7 +223,7 @@ class FileSeemsBinaryFileView { link.setAttribute('role', 'button'); link.textContent = nls.localize('openAsText', "Do you want to open it anyway?"); - disposables.push(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => openInternalClb(descriptor.resource))); + disposables.push(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => delegate.openInternalClb(descriptor.resource))); } scrollbar.scanDomNode(); @@ -357,9 +359,9 @@ class InlineImageView { static create( container: HTMLElement, descriptor: IResourceDescriptor, - fileService: IFileService, + textFileService: ITextFileService, scrollbar: DomScrollableElement, - metadataClb: (meta: string) => void + delegate: ResourceViewerDelegate ) { const disposables: IDisposable[] = []; @@ -368,7 +370,7 @@ class InlineImageView { dispose: () => combinedDisposable(disposables).dispose() }; - const cacheKey = descriptor.resource.toString(); + const cacheKey = `${descriptor.resource.toString()}:${descriptor.etag}`; let ctrlPressed = false; let altPressed = false; @@ -499,8 +501,8 @@ class InlineImageView { return; } - const isScrollWhellKeyPressed = platform.isMacintosh ? altPressed : ctrlPressed; - if (!isScrollWhellKeyPressed && !e.ctrlKey) { // pinching is reported as scroll wheel + ctrl + const isScrollWheelKeyPressed = platform.isMacintosh ? altPressed : ctrlPressed; + if (!isScrollWheelKeyPressed && !e.ctrlKey) { // pinching is reported as scroll wheel + ctrl return; } @@ -511,12 +513,8 @@ class InlineImageView { firstZoom(); } - let delta = e.deltaY < 0 ? 1 : -1; + let delta = e.deltaY > 0 ? 1 : -1; - // Pinching should increase the scale - if (e.ctrlKey && !isScrollWhellKeyPressed) { - delta *= -1; - } updateScale(scale as number * (1 - delta * InlineImageView.SCALE_PINCH_FACTOR)); })); @@ -543,9 +541,9 @@ class InlineImageView { return; } if (typeof descriptor.size === 'number') { - metadataClb(nls.localize('imgMeta', '{0}x{1} {2}', image.naturalWidth, image.naturalHeight, BinarySize.formatSize(descriptor.size))); + delegate.metadataClb(nls.localize('imgMeta', '{0}x{1} {2}', image.naturalWidth, image.naturalHeight, BinarySize.formatSize(descriptor.size))); } else { - metadataClb(nls.localize('imgMetaNoSize', '{0}x{1}', image.naturalWidth, image.naturalHeight)); + delegate.metadataClb(nls.localize('imgMetaNoSize', '{0}x{1}', image.naturalWidth, image.naturalHeight)); } scrollbar.scanDomNode(); @@ -559,7 +557,7 @@ class InlineImageView { } })); - InlineImageView.imageSrc(descriptor, fileService).then(dataUri => { + InlineImageView.imageSrc(descriptor, textFileService).then(dataUri => { const imgs = container.getElementsByTagName('img'); if (imgs.length) { imgs[0].src = dataUri; @@ -569,12 +567,12 @@ class InlineImageView { return context; } - private static imageSrc(descriptor: IResourceDescriptor, fileService: IFileService): Promise { + private static imageSrc(descriptor: IResourceDescriptor, textFileService: ITextFileService): Promise { if (descriptor.resource.scheme === Schemas.data) { return Promise.resolve(descriptor.resource.toString(true /* skip encoding */)); } - return fileService.resolveContent(descriptor.resource, { encoding: 'base64' }).then(data => { + return textFileService.read(descriptor.resource, { encoding: 'base64' }).then(data => { const mime = getMime(descriptor); return `data:${mime};base64,${data.value}`; diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 6068f26f55a..54a09e72921 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -93,10 +93,10 @@ export class SideBySideEditor extends BaseEditor { this.updateStyles(); } - setInput(newInput: SideBySideEditorInput, options: EditorOptions, token: CancellationToken): Promise { - const oldInput = this.input; + setInput(newInput: EditorInput, options: EditorOptions, token: CancellationToken): Promise { + const oldInput = this.input as SideBySideEditorInput; return super.setInput(newInput, options, token) - .then(() => this.updateInput(oldInput, newInput, options, token)); + .then(() => this.updateInput(oldInput, newInput as SideBySideEditorInput, options, token)); } setOptions(options: EditorOptions): void { @@ -177,8 +177,8 @@ export class SideBySideEditor extends BaseEditor { } private setNewInput(newInput: SideBySideEditorInput, options: EditorOptions, token: CancellationToken): Promise { - const detailsEditor = this.doCreateEditor(newInput.details, this.detailsEditorContainer); - const masterEditor = this.doCreateEditor(newInput.master, this.masterEditorContainer); + const detailsEditor = this.doCreateEditor(newInput.details, this.detailsEditorContainer); + const masterEditor = this.doCreateEditor(newInput.master, this.masterEditorContainer); return this.onEditorsCreated(detailsEditor, masterEditor, newInput.details, newInput.master, options, token); } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 15040ef4684..919151455e1 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -41,6 +41,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; import { IFileService } from 'vs/platform/files/common/files'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { ILabelService } from 'vs/platform/label/common/label'; interface IEditorInputLabel { name: string; @@ -83,8 +84,9 @@ export class TabsTitleControl extends TitleControl { @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, @IFileService fileService: IFileService, + @ILabelService labelService: ILabelService ) { - super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService); + super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService, labelService); } protected create(parent: HTMLElement): void { @@ -358,6 +360,12 @@ export class TabsTitleControl extends TitleControl { updateEditorLabel(editor: IEditorInput): void { + // Update all labels to account for changes to tab labels + this.updateEditorLabels(); + } + + updateEditorLabels(): void { + // A change to a label requires to recompute all labels this.computeTabLabels(); diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 41bbfa16be7..e63f5649844 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -15,13 +15,12 @@ import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel'; -import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { ScrollType, IDiffEditorViewState, IDiffEditorModel } from 'vs/editor/common/editorCommon'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -230,10 +229,11 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { private isFileBinaryError(error: Error | Error[]): boolean { if (types.isArray(error)) { const errors = error; + return errors.some(e => this.isFileBinaryError(e)); } - return (error).fileOperationResult === FileOperationResult.FILE_IS_BINARY; + return (error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY; } clearInput(): void { diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 5dff948db35..4d32086b4e1 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -6,7 +6,7 @@ import { applyDragImage } from 'vs/base/browser/dnd'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { ActionsOrientation, IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IAction, IRunEvent } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; @@ -15,7 +15,7 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./media/titlecontrol'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { localize } from 'vs/nls'; -import { createActionItem, fillInActionBarActions, fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { createActionViewItem, fillInActionBarActions, fillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ExecuteCommandAction, IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -39,7 +39,8 @@ import { Themable } from 'vs/workbench/common/theme'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IFileService } from 'vs/platform/files/common/files'; -import { withNullAsUndefined } from 'vs/base/common/types'; +import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; +import { ILabelService } from 'vs/platform/label/common/label'; export interface IToolbarActions { primary: IAction[]; @@ -78,6 +79,7 @@ export abstract class TitleControl extends Themable { @IExtensionService private readonly extensionService: IExtensionService, @IConfigurationService protected configurationService: IConfigurationService, @IFileService private readonly fileService: IFileService, + @ILabelService private readonly labelService: ILabelService ) { super(themeService); @@ -90,6 +92,7 @@ export abstract class TitleControl extends Themable { private registerListeners(): void { this._register(this.extensionService.onDidRegisterExtensions(() => this.updateEditorActionsToolbar())); + this._register(this.labelService.onDidChangeFormatters(() => this.updateEditorLabels())); } protected abstract create(parent: HTMLElement): void; @@ -125,7 +128,7 @@ export abstract class TitleControl extends Themable { const context: IEditorCommandsContext = { groupId: this.group.id }; this.editorActionsToolbar = this._register(new ToolBar(container, this.contextMenuService, { - actionItemProvider: action => this.actionItemProvider(action), + actionViewItemProvider: action => this.actionViewItemProvider(action), orientation: ActionsOrientation.HORIZONTAL, ariaLabel: localize('araLabelEditorActions', "Editor actions"), getKeyBinding: action => this.getKeybinding(action), @@ -155,21 +158,21 @@ export abstract class TitleControl extends Themable { })); } - private actionItemProvider(action: IAction): IActionItem | undefined { + private actionViewItemProvider(action: IAction): IActionViewItem | undefined { const activeControl = this.group.activeControl; // Check Active Editor - let actionItem: IActionItem | undefined = undefined; + let actionViewItem: IActionViewItem | undefined = undefined; if (activeControl instanceof BaseEditor) { - actionItem = activeControl.getActionItem(action); + actionViewItem = activeControl.getActionViewItem(action); } // Check extensions - if (!actionItem) { - actionItem = createActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + if (!actionViewItem) { + actionViewItem = createActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); } - return actionItem; + return actionViewItem; } protected updateEditorActionsToolbar(): void { @@ -218,7 +221,7 @@ export abstract class TitleControl extends Themable { this.editorToolBarMenuDisposables = dispose(this.editorToolBarMenuDisposables); // Update the resource context - this.resourceContext.set(this.group.activeEditor ? toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null); + this.resourceContext.set(this.group.activeEditor ? withUndefinedAsNull(toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER })) : null); // Editor actions require the editor control to be there, so we retrieve it via service const activeControl = this.group.activeControl; @@ -285,7 +288,7 @@ export abstract class TitleControl extends Themable { // Update the resource context const currentContext = this.resourceContext.get(); - this.resourceContext.set(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER })); + this.resourceContext.set(withUndefinedAsNull(toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }))); // Find target anchor let anchor: HTMLElement | { x: number, y: number } = node; @@ -341,6 +344,8 @@ export abstract class TitleControl extends Themable { abstract updateEditorLabel(editor: IEditorInput): void; + abstract updateEditorLabels(): void; + abstract updateEditorDirty(editor: IEditorInput): void; abstract updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index e0888bad11a..918fafdc244 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -16,7 +16,7 @@ import { IAction, IActionRunner } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { DropdownMenuActionItem } from 'vs/base/browser/ui/dropdown/dropdown'; +import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { INotificationViewItem, NotificationViewItem, NotificationViewItemLabelKind, INotificationMessage, ChoiceAction } from 'vs/workbench/common/notifications'; import { ClearNotificationAction, ExpandNotificationAction, CollapseNotificationAction, ConfigureNotificationAction } from 'vs/workbench/browser/parts/notifications/notificationsActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -153,7 +153,7 @@ class NotificationMessageRenderer { const anchor = document.createElement('a'); anchor.textContent = link.name; - anchor.title = link.href; + anchor.title = link.title; anchor.href = link.href; if (actionHandler) { @@ -219,9 +219,9 @@ export class NotificationRenderer implements IListRenderer { + actionViewItemProvider: action => { if (action && action instanceof ConfigureNotificationAction) { - const item = new DropdownMenuActionItem(action, action.configurationActions, this.contextMenuService, undefined, this.actionRunner, undefined, action.class); + const item = new DropdownMenuActionViewItem(action, action.configurationActions, this.contextMenuService, undefined, this.actionRunner, undefined, action.class); data.toDispose.push(item); return item; diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 4120e525cec..ba440bfb2c2 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -394,9 +394,9 @@ export class PanelPart extends CompositePart implements IPanelService { private getCachedPanels(): ICachedPanel[] { const registeredPanels = this.getPanels(); - const storedStates = >JSON.parse(this.cachedPanelsValue); - const cachedPanels = storedStates.map(c => { - const serialized: ICachedPanel = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true } : c; + const storedStates: Array = JSON.parse(this.cachedPanelsValue); + const cachedPanels = storedStates.map(c => { + const serialized: ICachedPanel = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true } : c; const registered = registeredPanels.some(p => p.id === serialized.id); serialized.visible = registered ? isUndefinedOrNull(serialized.visible) ? true : serialized.visible : false; return serialized; diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.css b/src/vs/workbench/browser/parts/quickinput/quickInput.css index 113c80a9abc..39c4a74b246 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.css +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.css @@ -47,7 +47,15 @@ .quick-input-header { display: flex; - padding: 6px 6px 4px 6px; + padding: 6px 6px 0px 6px; + margin-bottom: -2px; +} + +.quick-input-and-message { + display: flex; + flex-direction: column; + flex-grow: 1; + position: relative; } .quick-input-check-all { @@ -65,7 +73,8 @@ flex-grow: 1; } -.quick-input-widget.show-checkboxes .quick-input-box { +.quick-input-widget.show-checkboxes .quick-input-box, +.quick-input-widget.show-checkboxes .quick-input-message { margin-left: 5px; } @@ -90,12 +99,13 @@ .quick-input-action .monaco-text-button { font-size: 85%; - padding: 7px 6px 6px 6px; + padding: 7px 6px 5.5px 6px; line-height: initial; } .quick-input-message { - margin: 0px 11px; + margin-top: -1px; + padding: 5px 5px 2px 5px; } .quick-input-progress.monaco-progress-container { @@ -109,6 +119,7 @@ .quick-input-list { line-height: 22px; + margin-top: 6px; } .quick-input-list .monaco-list { diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 2b4af5a46a1..2872d0a6c9e 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -33,7 +33,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ICommandAndKeybindingRule, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { inQuickOpenContext, InQuickOpenContextKey } from 'vs/workbench/browser/parts/quickopen/quickopen'; -import { ActionBar, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { URI } from 'vs/base/common/uri'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -255,7 +255,7 @@ class QuickInput implements IQuickInput { this.ui.leftActionBar.clear(); const leftButtons = this.buttons.filter(button => button === backButton); this.ui.leftActionBar.push(leftButtons.map((button, index) => { - const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath!), true, () => { + const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => { this.onDidTriggerButtonEmitter.fire(button); return Promise.resolve(null); }); @@ -265,7 +265,7 @@ class QuickInput implements IQuickInput { this.ui.rightActionBar.clear(); const rightButtons = this.buttons.filter(button => button !== backButton); this.ui.rightActionBar.push(rightButtons.map((button, index) => { - const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath!), true, () => { + const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => { this.onDidTriggerButtonEmitter.fire(button); return Promise.resolve(null); }); @@ -301,6 +301,20 @@ class QuickInput implements IQuickInput { return ''; } + protected showMessageDecoration(severity: Severity) { + this.ui.inputBox.showDecoration(severity); + if (severity === Severity.Error) { + const styles = this.ui.inputBox.stylesForType(severity); + this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : null; + this.ui.message.style.border = styles.border ? `1px solid ${styles.border}` : null; + this.ui.message.style.paddingBottom = '4px'; + } else { + this.ui.message.style.backgroundColor = ''; + this.ui.message.style.border = ''; + this.ui.message.style.paddingBottom = ''; + } + } + public dispose(): void { this.hide(); this.disposables = dispose(this.disposables); @@ -742,10 +756,10 @@ class QuickPick extends QuickInput implements IQuickPi } if (this.validationMessage) { this.ui.message.textContent = this.validationMessage; - this.ui.inputBox.showDecoration(Severity.Error); + this.showMessageDecoration(Severity.Error); } else { this.ui.message.textContent = null; - this.ui.inputBox.showDecoration(Severity.Ignore); + this.showMessageDecoration(Severity.Ignore); } this.ui.customButton.label = this.customLabel; this.ui.customButton.element.title = this.customHover; @@ -876,11 +890,11 @@ class InputBox extends QuickInput implements IInputBox { } if (!this.validationMessage && this.ui.message.textContent !== this.noValidationMessage) { this.ui.message.textContent = this.noValidationMessage; - this.ui.inputBox.showDecoration(Severity.Ignore); + this.showMessageDecoration(Severity.Ignore); } if (this.validationMessage && this.ui.message.textContent !== this.validationMessage) { this.ui.message.textContent = this.validationMessage; - this.ui.inputBox.showDecoration(Severity.Error); + this.showMessageDecoration(Severity.Error); } this.ui.setVisibilities({ title: !!this.title || !!this.step, inputBox: true, message: true }); } @@ -1044,7 +1058,8 @@ export class QuickInputService extends Component implements IQuickInputService { } })); - this.filterContainer = dom.append(headerContainer, $('.quick-input-filter')); + const extraContainer = dom.append(headerContainer, $('.quick-input-and-message')); + this.filterContainer = dom.append(extraContainer, $('.quick-input-filter')); const inputBox = this._register(new QuickInputBox(this.filterContainer)); inputBox.setAttribute('aria-describedby', `${this.idPrefix}message`); @@ -1075,7 +1090,7 @@ export class QuickInputService extends Component implements IQuickInputService { this.onDidCustomEmitter.fire(); })); - const message = dom.append(container, $(`#${this.idPrefix}message.quick-input-message`)); + const message = dom.append(extraContainer, $(`#${this.idPrefix}message.quick-input-message`)); const progressBar = new ProgressBar(container); dom.addClass(progressBar.getContainer(), 'quick-input-progress'); @@ -1427,11 +1442,11 @@ export class QuickInputService extends Component implements IQuickInputService { private setEnabled(enabled: boolean) { if (enabled !== this.enabled) { this.enabled = enabled; - for (const item of this.ui.leftActionBar.items) { - (item as ActionItem).getAction().enabled = enabled; + for (const item of this.ui.leftActionBar.viewItems) { + (item as ActionViewItem).getAction().enabled = enabled; } - for (const item of this.ui.rightActionBar.items) { - (item as ActionItem).getAction().enabled = enabled; + for (const item of this.ui.rightActionBar.viewItems) { + (item as ActionViewItem).getAction().enabled = enabled; } this.ui.checkAll.disabled = !enabled; // this.ui.inputBox.enabled = enabled; Avoid loosing focus. @@ -1558,4 +1573,4 @@ export class BackAction extends Action { } } -registerSingleton(IQuickInputService, QuickInputService, true); \ No newline at end of file +registerSingleton(IQuickInputService, QuickInputService, true); diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts b/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts index 6a7f637d530..919feb5f560 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts @@ -101,6 +101,10 @@ export class QuickInputBox { } } + stylesForType(decoration: Severity) { + return this.inputBox.stylesForType(decoration === Severity.Info ? MessageType.INFO : decoration === Severity.Warning ? MessageType.WARNING : MessageType.ERROR); + } + setFocus(): void { this.inputBox.focus(); } diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts b/src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts index 1feda0eee00..babe9495114 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts @@ -11,7 +11,10 @@ import { IdGenerator } from 'vs/base/common/idGenerator'; const iconPathToClass = {}; const iconClassGenerator = new IdGenerator('quick-input-button-icon-'); -export function getIconClass(iconPath: { dark: URI; light?: URI; }) { +export function getIconClass(iconPath: { dark: URI; light?: URI; } | undefined): string | undefined { + if (!iconPath) { + return undefined; + } let iconClass: string; const key = iconPath.dark.toString(); diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 68fd3569f5a..6683de3ce6c 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -710,7 +710,7 @@ class EditorHistoryItemAccessorClass extends QuickOpenItemAccessorClass { } getItemDescription(entry: QuickOpenEntry): string | null { - return this.allowMatchOnDescription ? entry.getDescription() : null; + return this.allowMatchOnDescription ? types.withUndefinedAsNull(entry.getDescription()) : null; } } @@ -724,8 +724,8 @@ export class EditorHistoryEntryGroup extends QuickOpenEntryGroup { export class EditorHistoryEntry extends EditorQuickOpenEntry { private input: IEditorInput | IResourceInput; private resource: URI | undefined; - private label: string | null; - private description: string | null; + private label: string | undefined; + private description?: string; private dirty: boolean; constructor( @@ -744,8 +744,8 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { if (input instanceof EditorInput) { this.resource = resourceForEditorHistory(input, fileService); - this.label = input.getName(); - this.description = input.getDescription(); + this.label = types.withNullAsUndefined(input.getName()); + this.description = types.withNullAsUndefined(input.getDescription()); this.dirty = input.isDirty(); } else { const resourceInput = input as IResourceInput; @@ -764,7 +764,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { return this.dirty ? 'dirty' : ''; } - getLabel(): string | null { + getLabel(): string | undefined { return this.label; } @@ -778,12 +778,12 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { return nls.localize('entryAriaLabel', "{0}, recently opened", this.getLabel()); } - getDescription(): string | null { + getDescription(): string | undefined { return this.description; } - getResource(): URI | null { - return types.withUndefinedAsNull(this.resource); + getResource(): URI | undefined { + return this.resource; } getInput(): IEditorInput | IResourceInput { @@ -848,7 +848,7 @@ export class RemoveFromEditorHistoryAction extends Action { return { input: h, - iconClasses: getIconClasses(this.modelService, this.modeService, types.withNullAsUndefined(entry.getResource())), + iconClasses: getIconClasses(this.modelService, this.modeService, entry.getResource()), label: entry.getLabel(), description: entry.getDescription() }; diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index f291f110653..8ac0298db71 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -248,7 +248,7 @@ export class SidebarPart extends CompositePart implements IViewletServi this.contextMenuService.showContextMenu({ getAnchor: () => anchor, getActions: () => contextMenuActions, - getActionItem: action => this.actionItemProvider(action as Action), + getActionViewItem: action => this.actionViewItemProvider(action as Action), actionRunner: activeViewlet.getActionRunner() }); } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 88bc332cb94..d96209b11f3 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -36,6 +36,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { RunOnceScheduler } from 'vs/base/common/async'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Schemas } from 'vs/base/common/network'; export class TitlebarPart extends Part implements ITitleService { @@ -179,7 +180,7 @@ export class TitlebarPart extends Part implements ITitleService { } private updateRepresentedFilename(): void { - const file = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: 'file' }); + const file = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file }); const path = file ? file.fsPath : ''; // Apply to window diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index c12e152107e..8c4e0f8337c 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -7,11 +7,11 @@ import 'vs/css!./media/views'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IAction, IActionItem, ActionRunner, Action } from 'vs/base/common/actions'; +import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; -import { ContextAwareMenuItemActionItem, fillInActionBarActions, fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { ContextAwareMenuEntryActionViewItem, fillInActionBarActions, fillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewsService, ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ViewContainer, ITreeItemLabel, Extensions } from 'vs/workbench/common/views'; import { IViewletViewOptions, FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet'; @@ -24,7 +24,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import * as DOM from 'vs/base/browser/dom'; import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree'; import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; -import { ActionBar, IActionItemProvider, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { URI } from 'vs/base/common/uri'; import { dirname, basename } from 'vs/base/common/resources'; import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -87,8 +87,8 @@ export class CustomTreeViewPanel extends ViewletPanel { return [...this.treeView.getSecondaryActions()]; } - getActionItem(action: IAction): IActionItem | undefined { - return action instanceof MenuItemAction ? new ContextAwareMenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : undefined; + getActionViewItem(action: IAction): IActionViewItem | undefined { + return action instanceof MenuItemAction ? new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : undefined; } getOptimalWidth(): number { @@ -378,11 +378,11 @@ export class CustomTreeView extends Disposable implements ITreeView { } private createTree() { - const actionItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuItemActionItem, action) : undefined; + const actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined; const menus = this._register(this.instantiationService.createInstance(TreeMenus, this.id)); this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: this.viewContainer.id }, () => task)); - const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, menus, this.treeLabels, actionItemProvider); + const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, menus, this.treeLabels, actionViewItemProvider); const controller = this.instantiationService.createInstance(TreeController, this.id, menus); this.tree = this._register(this.instantiationService.createInstance(FileIconThemableWorkbenchTree, this.treeContainer, { dataSource, renderer, controller }, {})); this.tree.contextKeyService.createKey(this.id, true); @@ -645,7 +645,7 @@ class TreeRenderer implements IRenderer { private treeViewId: string, private menus: TreeMenus, private labels: ResourceLabels, - private actionItemProvider: IActionItemProvider, + private actionViewItemProvider: IActionViewItemProvider, @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, @IConfigurationService private readonly configurationService: IConfigurationService, @ILabelService private readonly labelService: ILabelService @@ -668,7 +668,7 @@ class TreeRenderer implements IRenderer { DOM.addClass(resourceLabel.element, 'custom-view-tree-node-item-resourceLabel'); const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions')); const actionBar = new ActionBar(actionsContainer, { - actionItemProvider: this.actionItemProvider, + actionViewItemProvider: this.actionViewItemProvider, actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection()) }); @@ -809,10 +809,10 @@ class TreeController extends WorkbenchTreeController { getActions: () => actions, - getActionItem: (action) => { + getActionViewItem: (action) => { const keybinding = this._keybindingService.lookupKeybinding(action.id); if (keybinding) { - return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() }); + return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() }); } return undefined; }, diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/panelViewlet.ts index 2469435ae57..10e2fd42d31 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/panelViewlet.ts @@ -13,7 +13,7 @@ import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, a import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { firstIndex } from 'vs/base/common/arrays'; import { IAction, IActionRunner } from 'vs/base/common/actions'; -import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { Registry } from 'vs/platform/registry/common/platform'; import { prepareActions } from 'vs/workbench/browser/actions'; import { Viewlet, ViewletRegistry, Extensions } from 'vs/workbench/browser/viewlet'; @@ -28,7 +28,6 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IView } from 'vs/workbench/common/views'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { withNullAsUndefined } from 'vs/base/common/types'; export interface IPanelColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -127,9 +126,9 @@ export abstract class ViewletPanel extends Panel implements IView { const actions = append(container, $('.actions')); this.toolbar = new ToolBar(actions, this.contextMenuService, { orientation: ActionsOrientation.HORIZONTAL, - actionItemProvider: action => this.getActionItem(action), + actionViewItemProvider: action => this.getActionViewItem(action), ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title), - getKeyBinding: action => withNullAsUndefined(this.keybindingService.lookupKeybinding(action.id)), + getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), actionRunner: this.actionRunner }); @@ -180,7 +179,7 @@ export abstract class ViewletPanel extends Panel implements IView { return []; } - getActionItem(action: IAction): IActionItem | undefined { + getActionViewItem(action: IAction): IActionViewItem | undefined { return undefined; } @@ -289,12 +288,12 @@ export class PanelViewlet extends Viewlet { return []; } - getActionItem(action: IAction): IActionItem | undefined { + getActionViewItem(action: IAction): IActionViewItem | undefined { if (this.isSingleView()) { - return this.panelItems[0].panel.getActionItem(action); + return this.panelItems[0].panel.getActionViewItem(action); } - return super.getActionItem(action); + return super.getActionViewItem(action); } focus(): void { diff --git a/src/vs/workbench/browser/quickopen.ts b/src/vs/workbench/browser/quickopen.ts index 8fe1e477f72..230a461907f 100644 --- a/src/vs/workbench/browser/quickopen.ts +++ b/src/vs/workbench/browser/quickopen.ts @@ -229,12 +229,12 @@ export interface IEditorQuickOpenEntry { /** * The editor input used for this entry when opening. */ - getInput(): IResourceInput | IEditorInput | null; + getInput(): IResourceInput | IEditorInput | undefined; /** * The editor options used for this entry when opening. */ - getOptions(): IEditorOptions | null; + getOptions(): IEditorOptions | undefined; } /** @@ -250,12 +250,12 @@ export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuick return this._editorService; } - getInput(): IResourceInput | IEditorInput | null { - return null; + getInput(): IResourceInput | IEditorInput | undefined { + return undefined; } - getOptions(): IEditorOptions | null { - return null; + getOptions(): IEditorOptions | undefined { + return undefined; } run(mode: Mode, context: IEntryRunContext): boolean { @@ -301,12 +301,12 @@ export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuick */ export class EditorQuickOpenEntryGroup extends QuickOpenEntryGroup implements IEditorQuickOpenEntry { - getInput(): IEditorInput | IResourceInput | null { - return null; + getInput(): IEditorInput | IResourceInput | undefined { + return undefined; } - getOptions(): IEditorOptions | null { - return null; + getOptions(): IEditorOptions | undefined { + return undefined; } } diff --git a/src/vs/workbench/browser/nodeless.main.ts b/src/vs/workbench/browser/web.main.ts similarity index 94% rename from src/vs/workbench/browser/nodeless.main.ts rename to src/vs/workbench/browser/web.main.ts index af0a6c090de..c4867584fa2 100644 --- a/src/vs/workbench/browser/nodeless.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -8,7 +8,7 @@ import { domContentLoaded, addDisposableListener, EventType } from 'vs/base/brow import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; -import { SimpleLogService, SimpleProductService, SimpleWorkbenchEnvironmentService } from 'vs/workbench/browser/nodeless.simpleservices'; +import { SimpleLogService, SimpleProductService, SimpleWorkbenchEnvironmentService } from 'vs/workbench/browser/web.simpleservices'; import { Workbench } from 'vs/workbench/browser/workbench'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteExtensionsFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; @@ -19,7 +19,7 @@ import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remot import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IFileService } from 'vs/platform/files/common/files'; -import { FileService3 } from 'vs/workbench/services/files2/browser/fileService2'; +import { FileService } from 'vs/workbench/services/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; class CodeRendererMain extends Disposable { @@ -78,7 +78,7 @@ class CodeRendererMain extends Disposable { serviceCollection.set(IRemoteAgentService, remoteAgentService); // Files - const fileService = this._register(new FileService3(logService)); + const fileService = this._register(new FileService(logService)); serviceCollection.set(IFileService, fileService); const connection = remoteAgentService.getConnection(); diff --git a/src/vs/workbench/browser/nodeless.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts similarity index 95% rename from src/vs/workbench/browser/nodeless.simpleservices.ts rename to src/vs/workbench/browser/web.simpleservices.ts index 4c3a887963c..dd6c2fed8e9 100644 --- a/src/vs/workbench/browser/nodeless.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { ITextSnapshot } from 'vs/platform/files/common/files'; -import { ITextBufferFactory } from 'vs/editor/common/model'; +import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; +import { ITextSnapshot } from 'vs/editor/common/model'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { keys, ResourceMap } from 'vs/base/common/map'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -52,18 +51,18 @@ import { ExportData } from 'vs/base/common/performance'; import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { IWorkspaceContextService, Workspace, toWorkspaceFolders, IWorkspaceFolder, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, Workspace, toWorkspaceFolder, IWorkspaceFolder, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Color, RGBA } from 'vs/base/common/color'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IFileService } from 'vs/platform/files/common/files'; -export const workspaceResource = URI.from({ +export const workspaceResource = URI.file((self).USER_HOME_DIR || '/').with({ scheme: Schemas.vscodeRemote, - authority: document.location.host, - path: (self).USER_HOME_DIR || '/' + authority: document.location.host }); //#region Backup File @@ -87,20 +86,20 @@ export class SimpleBackupFileService implements IBackupFileService { return Promise.resolve(undefined); } - backupResource(resource: URI, content: ITextSnapshot, versionId?: number): Promise { + backupResource(resource: URI, content: ITextSnapshot, versionId?: number, meta?: T): Promise { const backupResource = this.toBackupResource(resource); this.backups.set(backupResource.toString(), content); return Promise.resolve(); } - resolveBackupContent(backupResource: URI): Promise { + resolveBackupContent(backupResource: URI): Promise> { const snapshot = this.backups.get(backupResource.toString()); if (snapshot) { - return Promise.resolve(createTextBufferFactoryFromSnapshot(snapshot)); + return Promise.resolve({ value: createTextBufferFactoryFromSnapshot(snapshot) }); } - return Promise.resolve(undefined); + return Promise.reject('Unexpected backup resource to resolve'); } getWorkspaceFileBackups(): Promise { @@ -235,14 +234,14 @@ export class SimpleWorkbenchEnvironmentService implements IWorkbenchEnvironmentS args = { _: [] }; execPath: string; cliPath: string; - appRoot: string = '/nodeless/'; + appRoot: string = '/web/'; userHome: string; userDataPath: string; appNameLong: string; appQuality?: string; - appSettingsHome: string = '/nodeless/settings'; - appSettingsPath: string = '/nodeless/settings/settings.json'; - appKeybindingsPath: string = '/nodeless/settings/keybindings.json'; + appSettingsHome: string = '/web/settings'; + appSettingsPath: string = '/web/settings/settings.json'; + appKeybindingsPath: string = '/web/settings/keybindings.json'; machineSettingsHome: string; machineSettingsPath: string; settingsSearchBuildId?: number; @@ -265,7 +264,7 @@ export class SimpleWorkbenchEnvironmentService implements IWorkbenchEnvironmentS wait: boolean; status: boolean; log?: string; - logsPath: string = '/nodeless/logs'; + logsPath: string = '/web/logs'; verbose: boolean; skipGettingStarted: boolean; skipReleaseNotes: boolean; @@ -703,7 +702,8 @@ export class SimpleSearchService implements ISearchService { constructor( @IModelService private modelService: IModelService, @IEditorService private editorService: IEditorService, - @IUntitledEditorService private untitledEditorService: IUntitledEditorService + @IUntitledEditorService private untitledEditorService: IUntitledEditorService, + @IFileService private fileService: IFileService ) { } @@ -733,8 +733,8 @@ export class SimpleSearchService implements ISearchService { return Disposable.None; } - private getLocalResults(query: ITextQuery): ResourceMap { - const localResults = new ResourceMap(); + private getLocalResults(query: ITextQuery): ResourceMap { + const localResults = new ResourceMap(); if (query.type === QueryType.Text) { const models = this.modelService.getModels(); @@ -755,10 +755,8 @@ export class SimpleSearchService implements ISearchService { } } - // Don't support other resource schemes than files for now - // why is that? we should search for resources from other - // schemes - else if (resource.scheme !== Schemas.file) { + // Block walkthrough, webview, etc. + else if (!this.fileService.canHandleResource(resource)) { return; } @@ -767,8 +765,7 @@ export class SimpleSearchService implements ISearchService { } // Use editor API to find matches - // @ts-ignore - const matches = model.findMatches(query.contentPattern.pattern, false, query.contentPattern.isRegExp, query.contentPattern.isCaseSensitive, query.contentPattern.isWordMatch ? query.contentPattern.wordSeparators : null, false, query.maxResults); + const matches = model.findMatches(query.contentPattern.pattern, false, !!query.contentPattern.isRegExp, !!query.contentPattern.isCaseSensitive, query.contentPattern.isWordMatch ? query.contentPattern.wordSeparators! : null, false, query.maxResults); if (matches.length) { const fileMatch = new FileMatch(resource); localResults.set(resource, fileMatch); @@ -776,7 +773,6 @@ export class SimpleSearchService implements ISearchService { const textSearchResults = editorMatchesToTextSearchResults(matches, model, query.previewOptions); fileMatch.results = addContextToEditorMatches(textSearchResults, model, query); } else { - // @ts-ignore localResults.set(resource, null); } }); @@ -786,13 +782,6 @@ export class SimpleSearchService implements ISearchService { } private matches(resource: URI, query: ITextQuery): boolean { - // includes - if (query.includePattern) { - if (resource.scheme !== Schemas.file) { - return false; // if we match on file patterns, we have to ignore non file resources - } - } - return pathIncludedInQuery(query, resource.fsPath); } } @@ -954,8 +943,7 @@ export class SimpleWindowConfiguration implements IWindowConfiguration { perfWindowLoadTime?: number; perfEntries: ExportData; - filesToOpen?: IPath[]; - filesToCreate?: IPath[]; + filesToOpenOrCreate?: IPath[]; filesToDiff?: IPath[]; filesToWait?: IPathsToWaitFor; termProgram?: string; @@ -1366,10 +1354,7 @@ export class SimpleWorkspaceService implements IWorkspaceContextService { readonly onDidChangeWorkbenchState = Event.None; constructor() { - this.workspace = new Workspace( - workspaceResource.toString(), - toWorkspaceFolders([{ uri: workspaceResource.toString() }]) - ); + this.workspace = new Workspace(workspaceResource.toString(), [toWorkspaceFolder(workspaceResource)]); } getFolders(): IWorkspaceFolder[] { diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index a802bb02b0b..0aab890a5c2 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -14,7 +14,7 @@ import { getZoomLevel } from 'vs/base/browser/browser'; import { mark } from 'vs/base/common/performance'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { Registry } from 'vs/platform/registry/common/platform'; -import { isWindows, isLinux } from 'vs/base/common/platform'; +import { isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions'; @@ -23,7 +23,6 @@ import { Position, Parts, IWorkbenchLayoutService } from 'vs/workbench/services/ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IFileService, ILegacyFileService } from 'vs/platform/files/common/files'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -190,20 +189,12 @@ export class Workbench extends Layout { instantiationService.invokeFunction(accessor => { const lifecycleService = accessor.get(ILifecycleService); - // TODO@Ben legacy file service - const fileService = accessor.get(IFileService) as any; - if (typeof fileService.setLegacyService === 'function') { - try { - fileService.setLegacyService(accessor.get(ILegacyFileService)); - } catch (error) { - //ignore, legacy file service might not be registered - } - } - // TODO@Sandeep debt around cyclic dependencies const configurationService = accessor.get(IConfigurationService) as any; if (typeof configurationService.acquireInstantiationService === 'function') { - configurationService.acquireInstantiationService(instantiationService); + setTimeout(() => { + configurationService.acquireInstantiationService(instantiationService); + }, 0); } // Signal to lifecycle that services are set @@ -272,7 +263,11 @@ export class Workbench extends Layout { ]); addClasses(this.container, ...workbenchClasses); - addClasses(document.body, platformClass); // used by our fonts + addClass(document.body, platformClass); // used by our fonts + + if (isWeb) { + addClass(document.body, 'web'); + } // Apply font aliasing this.setFontAliasing(configurationService); diff --git a/src/vs/workbench/buildfile.js b/src/vs/workbench/buildfile.js index a39a6a2fd4c..04e3814fa84 100644 --- a/src/vs/workbench/buildfile.js +++ b/src/vs/workbench/buildfile.js @@ -25,8 +25,8 @@ exports.collectModules = function () { createModuleDescription('vs/workbench/services/search/node/searchApp', []), - createModuleDescription('vs/workbench/services/files2/node/watcher/unix/watcherApp', []), - createModuleDescription('vs/workbench/services/files2/node/watcher/nsfw/watcherApp', []), + createModuleDescription('vs/workbench/services/files/node/watcher/unix/watcherApp', []), + createModuleDescription('vs/workbench/services/files/node/watcher/nsfw/watcherApp', []), createModuleDescription('vs/workbench/services/extensions/node/extensionHostProcess', []), ]; diff --git a/src/vs/workbench/common/composite.ts b/src/vs/workbench/common/composite.ts index 9d27d30d840..50b5bd7917c 100644 --- a/src/vs/workbench/common/composite.ts +++ b/src/vs/workbench/common/composite.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAction, IActionItem } from 'vs/base/common/actions'; +import { IAction, IActionViewItem } from 'vs/base/common/actions'; export interface IComposite { @@ -35,7 +35,7 @@ export interface IComposite { /** * Returns the action item for a specific action. */ - getActionItem(action: IAction): IActionItem | undefined; + getActionViewItem(action: IAction): IActionViewItem | undefined; /** * Returns the underlying control of this composite. diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts deleted file mode 100644 index cb129ec8507..00000000000 --- a/src/vs/workbench/common/contextkeys.ts +++ /dev/null @@ -1,25 +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 { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; - -export const IsMacContext = new RawContextKey('isMac', isMacintosh); -export const IsLinuxContext = new RawContextKey('isLinux', isLinux); -export const IsWindowsContext = new RawContextKey('isWindows', isWindows); - -export const RemoteAuthorityContext = new RawContextKey('remoteAuthority', ''); - -export const HasMacNativeTabsContext = new RawContextKey('hasMacNativeTabs', false); - -export const SupportsWorkspacesContext = new RawContextKey('supportsWorkspaces', true); - -export const IsDevelopmentContext = new RawContextKey('isDevelopment', false); - -export const WorkbenchStateContext = new RawContextKey('workbenchState', undefined); - -export const WorkspaceFolderCountContext = new RawContextKey('workspaceFolderCount', 0); - -export const RemoteFileDialogContext = new RawContextKey('remoteFileDialogVisible', false); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index bd59c091420..73b8e872b92 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -5,11 +5,11 @@ import { Event, Emitter } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; -import { isUndefinedOrNull, withUndefinedAsNull } from 'vs/base/common/types'; +import { isUndefinedOrNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IEditor as ICodeEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; -import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput } from 'vs/platform/editor/common/editor'; +import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, IResourceInput } from 'vs/platform/editor/common/editor'; import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -17,6 +17,9 @@ import { ITextModel } from 'vs/editor/common/model'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ICompositeControl } from 'vs/workbench/common/composite'; import { ActionRunner, IAction } from 'vs/base/common/actions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IPathData } from 'vs/platform/windows/common/windows'; +import { coalesce } from 'vs/base/common/arrays'; export const ActiveEditorContext = new RawContextKey('activeEditor', null); export const EditorsVisibleContext = new RawContextKey('editorIsOpen', false); @@ -141,7 +144,7 @@ export interface IEditorControl extends ICompositeControl { } export interface IFileInputFactory { - createFileInput(resource: URI, encoding: string | undefined, instantiationService: IInstantiationService): IFileEditorInput; + createFileInput(resource: URI, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput; isFileInput(obj: any): obj is IFileEditorInput; } @@ -198,19 +201,15 @@ export interface IEditorInputFactory { export interface IUntitledResourceInput extends IBaseResourceInput { /** - * Optional resource. If the resource is not provided a new untitled file is created. + * Optional resource. If the resource is not provided a new untitled file is created (e.g. Untitled-1). + * Otherwise the untitled editor will have an associated path and use that when saving. */ resource?: URI; - /** - * Optional file path. Using the file resource will associate the file to the untitled resource. - */ - filePath?: string; - /** * Optional language of the untitled resource. */ - language?: string; + mode?: string; /** * Optional contents of the untitled resource. @@ -506,19 +505,35 @@ export interface IEncodingSupport { setEncoding(encoding: string, mode: EncodingMode): void; } +export interface IModeSupport { + + /** + * Sets the language mode of the input. + */ + setMode(mode: string): void; +} + /** * This is a tagging interface to declare an editor input being capable of dealing with files. It is only used in the editor registry * to register this kind of input to the platform. */ -export interface IFileEditorInput extends IEditorInput, IEncodingSupport { +export interface IFileEditorInput extends IEditorInput, IEncodingSupport, IModeSupport { + /** + * Gets the resource this editor is about. + */ getResource(): URI; /** - * Sets the preferred encodingt to use for this input. + * Sets the preferred encoding to use for this input. */ setPreferredEncoding(encoding: string): void; + /** + * Sets the preferred language mode to use for this input. + */ + setPreferredMode(mode: string): void; + /** * Forces this file input to open as binary instead of text. */ @@ -567,7 +582,7 @@ export class SideBySideEditorInput extends EditorInput { return this.master.revert(); } - getTelemetryDescriptor(): object { + getTelemetryDescriptor(): { [key: string]: unknown } { const descriptor = this.master.getTelemetryDescriptor(); return assign(descriptor, super.getTelemetryDescriptor()); @@ -621,8 +636,7 @@ export class SideBySideEditorInput extends EditorInput { return false; } - const otherDiffInput = otherInput; - return this.details.matches(otherDiffInput.details) && this.master.matches(otherDiffInput.master); + return this.details.matches(otherInput.details) && this.master.matches(otherInput.master); } return false; @@ -988,9 +1002,9 @@ export interface IResourceOptions { filterByScheme?: string | string[]; } -export function toResource(editor: IEditorInput | null | undefined, options?: IResourceOptions): URI | null { +export function toResource(editor: IEditorInput | undefined, options?: IResourceOptions): URI | undefined { if (!editor) { - return null; + return undefined; } if (options && options.supportSideBySide && editor instanceof SideBySideEditorInput) { @@ -999,7 +1013,7 @@ export function toResource(editor: IEditorInput | null | undefined, options?: IR const resource = editor.getResource(); if (!resource || !options || !options.filterByScheme) { - return withUndefinedAsNull(resource); + return resource; } if (Array.isArray(options.filterByScheme) && options.filterByScheme.some(scheme => resource.scheme === scheme)) { @@ -1010,7 +1024,7 @@ export function toResource(editor: IEditorInput | null | undefined, options?: IR return resource; } - return null; + return undefined; } export const enum CloseDirection { @@ -1078,3 +1092,37 @@ export const Extensions = { }; Registry.add(Extensions.EditorInputFactories, new EditorInputFactoryRegistry()); + +export async function pathsToEditors(paths: IPathData[] | undefined, fileService: IFileService): Promise<(IResourceInput | IUntitledResourceInput)[]> { + if (!paths || !paths.length) { + return []; + } + + const editors = await Promise.all(paths.map(async path => { + const resource = URI.revive(path.fileUri); + if (!resource || !fileService.canHandleResource(resource)) { + return; + } + + const exists = (typeof path.exists === 'boolean') ? path.exists : await fileService.exists(resource); + + const options: ITextEditorOptions = { pinned: true }; + if (exists && typeof path.lineNumber === 'number') { + options.selection = { + startLineNumber: path.lineNumber, + startColumn: path.columnNumber || 1 + }; + } + + let input: IResourceInput | IUntitledResourceInput; + if (!exists) { + input = { resource, options, forceUntitled: true }; + } else { + input = { resource, options, forceFile: true }; + } + + return input; + })); + + return coalesce(editors); +} \ No newline at end of file diff --git a/src/vs/workbench/common/editor/dataUriEditorInput.ts b/src/vs/workbench/common/editor/dataUriEditorInput.ts index 9b154d2783f..7c82702e21c 100644 --- a/src/vs/workbench/common/editor/dataUriEditorInput.ts +++ b/src/vs/workbench/common/editor/dataUriEditorInput.ts @@ -68,11 +68,9 @@ export class DataUriEditorInput extends EditorInput { return true; } + // Compare by resource if (otherInput instanceof DataUriEditorInput) { - const otherDataUriEditorInput = otherInput; - - // Compare by resource - return otherDataUriEditorInput.resource.toString() === this.resource.toString(); + return otherInput.resource.toString() === this.resource.toString(); } return false; diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index b8ed8c9ec0f..b962443bd7e 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -67,7 +67,7 @@ export class DiffEditorInput extends SideBySideEditorInput { // If both are text models, return textdiffeditor model if (modifiedEditorModel instanceof BaseTextEditorModel && originalEditorModel instanceof BaseTextEditorModel) { - return new TextDiffEditorModel(originalEditorModel, modifiedEditorModel); + return new TextDiffEditorModel(originalEditorModel, modifiedEditorModel); } // Otherwise return normal diff model diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index 7fed290b43b..6ff63c0a617 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EditorInput, ITextEditorModel } from 'vs/workbench/common/editor'; +import { EditorInput, ITextEditorModel, IModeSupport } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { IReference } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -13,16 +13,18 @@ import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorMo * A read-only text editor input whos contents are made of the provided resource that points to an existing * code editor model. */ -export class ResourceEditorInput extends EditorInput { +export class ResourceEditorInput extends EditorInput implements IModeSupport { static readonly ID: string = 'workbench.editors.resourceEditorInput'; + private cachedModel: ResourceEditorModel | null; private modelReference: Promise> | null; constructor( private name: string, private description: string | null, private readonly resource: URI, + private preferredMode: string | undefined, @ITextModelService private readonly textModelResolverService: ITextModelService ) { super(); @@ -62,6 +64,18 @@ export class ResourceEditorInput extends EditorInput { } } + setMode(mode: string): void { + this.setPreferredMode(mode); + + if (this.cachedModel) { + this.cachedModel.setMode(mode); + } + } + + setPreferredMode(mode: string): void { + this.preferredMode = mode; + } + resolve(): Promise { if (!this.modelReference) { this.modelReference = this.textModelResolverService.createModelReference(this.resource); @@ -70,6 +84,7 @@ export class ResourceEditorInput extends EditorInput { return this.modelReference.then(ref => { const model = ref.object; + // Ensure the resolved model is of expected type if (!(model instanceof ResourceEditorModel)) { ref.dispose(); this.modelReference = null; @@ -77,6 +92,13 @@ export class ResourceEditorInput extends EditorInput { return Promise.reject(new Error(`Unexpected model for ResourceInput: ${this.resource}`)); } + this.cachedModel = model; + + // Set mode if we have a preferred mode configured + if (this.preferredMode) { + model.setMode(this.preferredMode); + } + return model; }); } @@ -86,11 +108,9 @@ export class ResourceEditorInput extends EditorInput { return true; } + // Compare by properties if (otherInput instanceof ResourceEditorInput) { - let otherResourceEditorInput = otherInput; - - // Compare by properties - return otherResourceEditorInput.resource.toString() === this.resource.toString(); + return otherInput.resource.toString() === this.resource.toString(); } return false; @@ -102,6 +122,8 @@ export class ResourceEditorInput extends EditorInput { this.modelReference = null; } + this.cachedModel = null; + super.dispose(); } } diff --git a/src/vs/workbench/common/editor/resourceEditorModel.ts b/src/vs/workbench/common/editor/resourceEditorModel.ts index 8177f346457..ae5d8ff2e90 100644 --- a/src/vs/workbench/common/editor/resourceEditorModel.ts +++ b/src/vs/workbench/common/editor/resourceEditorModel.ts @@ -19,12 +19,18 @@ export class ResourceEditorModel extends BaseTextEditorModel { @IModelService modelService: IModelService ) { super(modelService, modeService, resource); - - // TODO@Joao: force this class to dispose the underlying model - this.createdEditorModel = true; } isReadonly(): boolean { return true; } + + dispose(): void { + // TODO@Joao: force this class to dispose the underlying model + if (this.textEditorModelHandle) { + this.modelService.destroyModel(this.textEditorModelHandle); + } + + super.dispose(); + } } \ No newline at end of file diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index c640dddff9a..8dd3c4716e7 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -3,23 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITextModel, ITextBufferFactory } from 'vs/editor/common/model'; -import { EditorModel } from 'vs/workbench/common/editor'; +import { ITextModel, ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; +import { EditorModel, IModeSupport } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { ITextSnapshot } from 'vs/platform/files/common/files'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; /** * The base text editor model leverages the code editor model. This class is only intended to be subclassed and not instantiated. */ -export abstract class BaseTextEditorModel extends EditorModel implements ITextEditorModel { +export abstract class BaseTextEditorModel extends EditorModel implements ITextEditorModel, IModeSupport { + protected textEditorModelHandle: URI | null; + private createdEditorModel: boolean; - protected createdEditorModel: boolean; - - private textEditorModelHandle: URI | null; private modelDisposeListener: IDisposable | null; constructor( @@ -65,12 +64,25 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd abstract isReadonly(): boolean; + setMode(mode: string): void { + if (!this.isResolved()) { + return; + } + + if (!mode || mode === this.textEditorModel.getModeId()) { + return; + } + + this.modelService.setMode(this.textEditorModel, this.modeService.create(mode)); + } + /** - * Creates the text editor model with the provided value, modeId (can be comma separated for multiple values) and optional resource URL. + * Creates the text editor model with the provided value, optional preferred mode + * (can be comma separated for multiple values) and optional resource URL. */ - protected createTextEditorModel(value: ITextBufferFactory, resource: URI | undefined, modeId?: string): EditorModel { + protected createTextEditorModel(value: ITextBufferFactory, resource: URI | undefined, preferredMode?: string): EditorModel { const firstLineText = this.getFirstLineText(value); - const languageSelection = this.getOrCreateMode(this.modeService, modeId, firstLineText); + const languageSelection = this.getOrCreateMode(resource, this.modeService, preferredMode, firstLineText); return this.doCreateTextEditorModel(value, languageSelection, resource); } @@ -84,8 +96,7 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd // Make sure we clean up when this model gets disposed this.registerModelDisposeListener(model); } else { - this.modelService.updateModel(model, value); - this.modelService.setMode(model, languageSelection); + this.updateTextEditorModel(value, languageSelection.languageIdentifier.language); } this.textEditorModelHandle = model.uri; @@ -111,28 +122,42 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd * * @param firstLineText optional first line of the text buffer to set the mode on. This can be used to guess a mode from content. */ - protected getOrCreateMode(modeService: IModeService, modeId: string | undefined, firstLineText?: string): ILanguageSelection { - return modeService.create(modeId); + protected getOrCreateMode(resource: URI | undefined, modeService: IModeService, preferredMode: string | undefined, firstLineText?: string): ILanguageSelection { + + // lookup mode via resource path if the provided mode is unspecific + if (!preferredMode || preferredMode === PLAINTEXT_MODE_ID) { + return modeService.createByFilepathOrFirstLine(resource ? resource.path : null, firstLineText); + } + + // otherwise take the preferred mode for granted + return modeService.create(preferredMode); } /** * Updates the text editor model with the provided value. If the value is the same as the model has, this is a no-op. */ - protected updateTextEditorModel(newValue: ITextBufferFactory): void { - if (!this.textEditorModel) { + protected updateTextEditorModel(newValue: ITextBufferFactory, preferredMode?: string): void { + if (!this.isResolved()) { return; } + // contents this.modelService.updateModel(this.textEditorModel, newValue); + + // mode (only if specific and changed) + if (preferredMode && preferredMode !== PLAINTEXT_MODE_ID && this.textEditorModel.getModeId() !== preferredMode) { + this.modelService.setMode(this.textEditorModel, this.modeService.create(preferredMode)); + } } + createSnapshot(this: IResolvedTextEditorModel): ITextSnapshot; + createSnapshot(this: ITextEditorModel): ITextSnapshot | null; createSnapshot(): ITextSnapshot | null { - const model = this.textEditorModel; - if (model) { - return model.createSnapshot(true /* Preserve BOM */); + if (!this.textEditorModel) { + return null; } - return null; + return this.textEditorModel.createSnapshot(true /* preserve BOM */); } isResolved(): this is IResolvedTextEditorModel { diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index dea43a4c322..585543b6560 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -9,7 +9,7 @@ import { memoize } from 'vs/base/common/decorators'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { basename } from 'vs/base/common/path'; import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; -import { EditorInput, IEncodingSupport, EncodingMode, ConfirmResult, Verbosity } from 'vs/workbench/common/editor'; +import { EditorInput, IEncodingSupport, EncodingMode, ConfirmResult, Verbosity, IModeSupport } from 'vs/workbench/common/editor'; import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; @@ -20,12 +20,12 @@ import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverServ /** * An editor input to be used for untitled text buffers. */ -export class UntitledEditorInput extends EditorInput implements IEncodingSupport { +export class UntitledEditorInput extends EditorInput implements IEncodingSupport, IModeSupport { static readonly ID: string = 'workbench.editors.untitledEditorInput'; - private cachedModel: UntitledEditorModel; - private modelResolve?: Promise; + private cachedModel: UntitledEditorModel | null; + private modelResolve: Promise | null; private readonly _onDidModelChangeContent: Emitter = this._register(new Emitter()); get onDidModelChangeContent(): Event { return this._onDidModelChangeContent.event; } @@ -36,7 +36,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport constructor( private readonly resource: URI, private readonly _hasAssociatedFilePath: boolean, - private readonly modeId: string, + private preferredMode: string, private readonly initialValue: string, private preferredEncoding: string, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -58,14 +58,6 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return this.resource; } - getModeId(): string | null { - if (this.cachedModel) { - return this.cachedModel.getModeId(); - } - - return this.modeId; - } - getName(): string { return this.hasAssociatedFilePath ? basenameOrAuthority(this.resource) : this.resource.path; } @@ -168,9 +160,9 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport suggestFileName(): string { if (!this.hasAssociatedFilePath) { if (this.cachedModel) { - const modeId = this.cachedModel.getModeId(); - if (modeId !== PLAINTEXT_MODE_ID) { // do not suggest when the mode ID is simple plain text - return suggestFilename(modeId, this.getName()); + const mode = this.cachedModel.getMode(); + if (mode !== PLAINTEXT_MODE_ID) { // do not suggest when the mode ID is simple plain text + return suggestFilename(mode, this.getName()); } } } @@ -194,6 +186,22 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport } } + setMode(mode: string): void { + this.preferredMode = mode; + + if (this.cachedModel) { + this.cachedModel.setMode(mode); + } + } + + getMode(): string | undefined { + if (this.cachedModel) { + return this.cachedModel.getMode(); + } + + return this.preferredMode; + } + resolve(): Promise { // Join a model resolve if we have had one before @@ -209,7 +217,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport } private createModel(): UntitledEditorModel { - const model = this._register(this.instantiationService.createInstance(UntitledEditorModel, this.modeId, this.resource, this.hasAssociatedFilePath, this.initialValue, this.preferredEncoding)); + const model = this._register(this.instantiationService.createInstance(UntitledEditorModel, this.preferredMode, this.resource, this.hasAssociatedFilePath, this.initialValue, this.preferredEncoding)); // re-emit some events from the model this._register(model.onDidChangeContent(() => this._onDidModelChangeContent.fire())); @@ -224,18 +232,17 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return true; } + // Otherwise compare by properties if (otherInput instanceof UntitledEditorInput) { - const otherUntitledEditorInput = otherInput; - - // Otherwise compare by properties - return otherUntitledEditorInput.resource.toString() === this.resource.toString(); + return otherInput.resource.toString() === this.resource.toString(); } return false; } dispose(): void { - this.modelResolve = undefined; + this.cachedModel = null; + this.modelResolve = null; super.dispose(); } diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledEditorModel.ts index 4f8901e3001..a658a8f3793 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledEditorModel.ts @@ -6,9 +6,8 @@ import { IEncodingSupport } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { URI } from 'vs/base/common/uri'; -import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { CONTENT_CHANGE_EVENT_BUFFER_DELAY } from 'vs/platform/files/common/files'; -import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; +import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { Event, Emitter } from 'vs/base/common/event'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -37,7 +36,7 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin private configuredEncoding: string; constructor( - private readonly modeId: string, + private readonly preferredMode: string, private readonly resource: URI, private _hasAssociatedFilePath: boolean, private readonly initialValue: string, @@ -58,14 +57,6 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin return this._hasAssociatedFilePath; } - protected getOrCreateMode(modeService: IModeService, modeId: string, firstLineText?: string): ILanguageSelection { - if (!modeId || modeId === PLAINTEXT_MODE_ID) { - return modeService.createByFilepathOrFirstLine(this.resource.fsPath, firstLineText); // lookup mode via resource path if the provided modeId is unspecific - } - - return super.getOrCreateMode(modeService, modeId, firstLineText); - } - private registerListeners(): void { // Config Changes @@ -88,12 +79,12 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin return this.versionId; } - getModeId(): string | null { + getMode(): string | undefined { if (this.textEditorModel) { - return this.textEditorModel.getLanguageIdentifier().language; + return this.textEditorModel.getModeId(); } - return this.modeId; + return this.preferredMode; } getEncoding(): string { @@ -134,36 +125,44 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin this.contentChangeEventScheduler.schedule(); } + backup(): Promise { + if (this.isResolved()) { + return this.backupFileService.backupResource(this.resource, this.createSnapshot(), this.versionId); + } + + return Promise.resolve(); + } + load(): Promise { // Check for backups first - return this.backupFileService.loadBackupResource(this.resource).then((backupResource) => { + return this.backupFileService.loadBackupResource(this.resource).then(backupResource => { if (backupResource) { return this.backupFileService.resolveBackupContent(backupResource); } - return undefined; - }).then(backupTextBufferFactory => { - const hasBackup = !!backupTextBufferFactory; + return Promise.resolve(undefined); + }).then(backup => { + const hasBackup = !!backup; // untitled associated to file path are dirty right away as well as untitled with content - this.setDirty(this._hasAssociatedFilePath || hasBackup); + this.setDirty(this._hasAssociatedFilePath || hasBackup || !!this.initialValue); let untitledContents: ITextBufferFactory; - if (backupTextBufferFactory) { - untitledContents = backupTextBufferFactory; + if (backup) { + untitledContents = backup.value; } else { untitledContents = createTextBufferFactory(this.initialValue || ''); } // Create text editor model if not yet done if (!this.textEditorModel) { - this.createTextEditorModel(untitledContents, this.resource, this.modeId); + this.createTextEditorModel(untitledContents, this.resource, this.preferredMode); } // Otherwise update else { - this.updateTextEditorModel(untitledContents); + this.updateTextEditorModel(untitledContents, this.preferredMode); } // Encoding diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 3c0544e5f53..c7ddc318215 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -10,6 +10,8 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { Action } from 'vs/base/common/actions'; import { isErrorWithActions } from 'vs/base/common/errorsWithActions'; +import { startsWith } from 'vs/base/common/strings'; +import { localize } from 'vs/nls'; export interface INotificationsModel { @@ -306,8 +308,9 @@ export class NotificationViewItemProgress extends Disposable implements INotific } export interface IMessageLink { - name: string; href: string; + name: string; + title: string; offset: number; length: number; } @@ -325,7 +328,7 @@ export class NotificationViewItem extends Disposable implements INotificationVie // Example link: "Some message with [link text](http://link.href)." // RegEx: [, anything not ], ], (, http://|https://|command:, no whitespace) - private static LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)\)/gi; + private static LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:)[^\)\s]+)(?: "([^"]+)")?\)/gi; private _expanded: boolean; @@ -392,8 +395,17 @@ export class NotificationViewItem extends Disposable implements INotificationVie // Parse Links const links: IMessageLink[] = []; - message.replace(NotificationViewItem.LINK_REGEX, (matchString: string, name: string, href: string, offset: number) => { - links.push({ name, href, offset, length: matchString.length }); + message.replace(NotificationViewItem.LINK_REGEX, (matchString: string, name: string, href: string, title: string, offset: number) => { + let massagedTitle: string; + if (title && title.length > 0) { + massagedTitle = title; + } else if (startsWith(href, 'command:')) { + massagedTitle = localize('executeCommand', "Click to execute command '{0}'", href.substr('command:'.length)); + } else { + massagedTitle = href; + } + + links.push({ name, href, title: massagedTitle, offset, length: matchString.length }); return matchString; }); diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index ee256d4e978..957b3b244ad 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -309,13 +309,6 @@ export const STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND = registerColor('statusB hc: Color.black.transparent(0.3), }, nls.localize('statusBarProminentItemHoverBackground', "Status bar prominent items background color when hovering. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window.")); -export const STATUS_BAR_HOST_NAME_BACKGROUND = registerColor('statusBarItem.hostBackground', { - dark: STATUS_BAR_PROMINENT_ITEM_BACKGROUND, - light: STATUS_BAR_PROMINENT_ITEM_BACKGROUND, - hc: STATUS_BAR_PROMINENT_ITEM_BACKGROUND -}, nls.localize('statusBarItemHostBackground', "Background color for the remote host name on the status bar.")); - - // < --- Activity Bar --- > export const ACTIVITY_BAR_BACKGROUND = registerColor('activityBar.background', { @@ -361,6 +354,33 @@ export const ACTIVITY_BAR_BADGE_FOREGROUND = registerColor('activityBarBadge.for }, nls.localize('activityBarBadgeForeground', "Activity notification badge foreground color. The activity bar is showing on the far left or right and allows to switch between views of the side bar.")); +// < --- Remote --- > + +export const STATUS_BAR_HOST_NAME_BACKGROUND = registerColor('statusBarItem.remoteBackground', { + dark: ACTIVITY_BAR_BADGE_BACKGROUND, + light: ACTIVITY_BAR_BADGE_BACKGROUND, + hc: ACTIVITY_BAR_BADGE_BACKGROUND +}, nls.localize('statusBarItemHostBackground', "Background color for the remote indicator on the status bar.")); + +export const STATUS_BAR_HOST_NAME_FOREGROUND = registerColor('statusBarItem.remoteForeground', { + dark: ACTIVITY_BAR_BADGE_FOREGROUND, + light: ACTIVITY_BAR_BADGE_FOREGROUND, + hc: ACTIVITY_BAR_BADGE_FOREGROUND +}, nls.localize('statusBarItemHostForeground', "Foreground color for the remote indicator on the status bar.")); + +export const EXTENSION_BADGE_REMOTE_BACKGROUND = registerColor('extensionBadge.remoteBackground', { + dark: ACTIVITY_BAR_BADGE_BACKGROUND, + light: ACTIVITY_BAR_BADGE_BACKGROUND, + hc: ACTIVITY_BAR_BADGE_BACKGROUND +}, nls.localize('extensionBadge.remoteBackground', "Background color for the remote badge in the extensions view")); + +export const EXTENSION_BADGE_REMOTE_FOREGROUND = registerColor('extensionBadge.remoteForeground', { + dark: ACTIVITY_BAR_BADGE_FOREGROUND, + light: ACTIVITY_BAR_BADGE_FOREGROUND, + hc: ACTIVITY_BAR_BADGE_FOREGROUND +}, nls.localize('extensionBadge.remoteForeground', "Foreground color for the remote badge in the extensions view")); + + // < --- Side Bar --- > export const SIDE_BAR_BACKGROUND = registerColor('sideBar.background', { diff --git a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts index 2b245986c9b..aa16c733036 100644 --- a/src/vs/workbench/contrib/backup/common/backupModelTracker.ts +++ b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts @@ -66,10 +66,7 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu if (!this.configuredAutoSaveAfterDelay) { const model = this.textFileService.models.get(event.resource); if (model) { - const snapshot = model.createSnapshot(); - if (snapshot) { - this.backupFileService.backupResource(model.getResource(), snapshot, model.getVersionId()); - } + model.backup(); } } } @@ -77,12 +74,7 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu private onUntitledModelChanged(resource: Uri): void { if (this.untitledEditorService.isDirty(resource)) { - this.untitledEditorService.loadOrCreate({ resource }).then(model => { - const snapshot = model.createSnapshot(); - if (snapshot) { - this.backupFileService.backupResource(resource, snapshot, model.getVersionId()); - } - }); + this.untitledEditorService.loadOrCreate({ resource }).then(model => model.backup()); } else { this.discardBackup(resource); } diff --git a/src/vs/workbench/contrib/backup/common/backupRestorer.ts b/src/vs/workbench/contrib/backup/common/backupRestorer.ts index 8677198687d..a3a17011b46 100644 --- a/src/vs/workbench/contrib/backup/common/backupRestorer.ts +++ b/src/vs/workbench/contrib/backup/common/backupRestorer.ts @@ -11,6 +11,8 @@ import { IResourceInput } from 'vs/platform/editor/common/editor'; import { Schemas } from 'vs/base/common/network'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IUntitledResourceInput } from 'vs/workbench/common/editor'; +import { toLocalResource } from 'vs/base/common/resources'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class BackupRestorer implements IWorkbenchContribution { @@ -19,7 +21,8 @@ export class BackupRestorer implements IWorkbenchContribution { constructor( @IEditorService private readonly editorService: IEditorService, @IBackupFileService private readonly backupFileService: IBackupFileService, - @ILifecycleService private readonly lifecycleService: ILifecycleService + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { this.restoreBackups(); } @@ -73,8 +76,11 @@ export class BackupRestorer implements IWorkbenchContribution { private resolveInput(resource: URI, index: number, hasOpenedEditors: boolean): IResourceInput | IUntitledResourceInput { const options = { pinned: true, preserveFocus: true, inactive: index > 0 || hasOpenedEditors }; - if (resource.scheme === Schemas.untitled && !BackupRestorer.UNTITLED_REGEX.test(resource.fsPath)) { - return { filePath: resource.fsPath, options }; + // this is a (weak) strategy to find out if the untitled input had + // an associated file path or not by just looking at the path. and + // if so, we must ensure to restore the local resource it had. + if (resource.scheme === Schemas.untitled && !BackupRestorer.UNTITLED_REGEX.test(resource.path)) { + return { resource: toLocalResource(resource, this.environmentService.configuration.remoteAuthority), options, forceUntitled: true }; } return { resource, options }; diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 787c9ef03f8..32d2eeaaa2d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -282,7 +282,7 @@ class ShowAccessibilityHelpAction extends EditorAction { id: 'editor.action.showAccessibilityHelp', label: nls.localize('ShowAccessibilityHelpAction', "Show Accessibility Help"), alias: 'Show Accessibility Help', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.focus, primary: KeyMod.Alt | KeyCode.F1, diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts index 516640bd086..b66f6d7b093 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts @@ -17,7 +17,7 @@ class InspectKeyMap extends EditorAction { id: 'workbench.action.inspectKeyMappings', label: nls.localize('workbench.action.inspectKeyMap', "Developer: Inspect Key Mappings"), alias: 'Developer: Inspect Key Mappings', - precondition: null + precondition: undefined }); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts index bf47a0c870d..c01b75c3cbb 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts @@ -102,7 +102,7 @@ class InspectTMScopes extends EditorAction { id: 'editor.action.inspectTMScopes', label: nls.localize('inspectTMScopes', "Developer: Inspect TM Scopes"), alias: 'Developer: Inspect TM Scopes', - precondition: null + precondition: undefined }); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts index c3d785d048a..59060ed7fb4 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts @@ -97,7 +97,7 @@ export class LanguageConfigurationFileHandler { } private _handleConfigFile(languageIdentifier: LanguageIdentifier, configFileLocation: URI): void { - this._fileService.resolveContent(configFileLocation, { encoding: 'utf8' }).then((contents) => { + this._fileService.readFile(configFileLocation).then((contents) => { const errors: ParseError[] = []; const configuration = parse(contents.value.toString(), errors); if (errors.length) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index e192d5cf4cc..d2a7bd7f4c0 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -121,7 +121,7 @@ class ToggleWordWrapAction extends EditorAction { id: TOGGLE_WORD_WRAP_ID, label: nls.localize('toggle.wordwrap', "View: Toggle Word Wrap"), alias: 'View: Toggle Word Wrap', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: null, primary: KeyMod.Alt | KeyCode.KEY_Z, diff --git a/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.css b/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.css index 113a5a1fbb4..28042461298 100644 --- a/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.css +++ b/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.css @@ -3,43 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-editor .codelens-decoration { - overflow: hidden; - display: inline-block; - text-overflow: ellipsis; -} - -.monaco-editor .codelens-decoration > span, -.monaco-editor .codelens-decoration > a { - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; - white-space: nowrap; - vertical-align: sub; -} - -.monaco-editor .codelens-decoration > a { - text-decoration: none; -} - -.monaco-editor .codelens-decoration > a:hover { - text-decoration: underline; - cursor: pointer; -} - -.monaco-editor .codelens-decoration.invisible-cl { - opacity: 0; -} - -@keyframes fadein { 0% { opacity:0; visibility:visible;} 100% { opacity:1; } } -@-moz-keyframes fadein { 0% { opacity:0; visibility:visible;} 100% { opacity:1; } } -@-o-keyframes fadein { 0% { opacity:0; visibility:visible;} 100% { opacity:1; } } -@-webkit-keyframes fadein { 0% { opacity:0; visibility:visible;} 100% { opacity:1; } } - -.monaco-editor .codelens-decoration.fadein { - -webkit-animation: fadein 0.5s linear; - -moz-animation: fadein 0.5s linear; - -o-animation: fadein 0.5s linear; - animation: fadein 0.5s linear; +.monaco-editor .code-inset { + z-index: 10; } diff --git a/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.ts b/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.ts index 5522eb6a840..d5e16dc47a9 100644 --- a/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.ts +++ b/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.ts @@ -155,6 +155,7 @@ export class CodeInsetWidget { } const div = document.createElement('div'); + div.className = 'code-inset'; webview.mountTo(div); webview.onMessage((e: { type: string, payload: any }) => { // The webview contents can use a "size-info" message to report its size. diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 975bb6c6837..8b4b7bbfe0a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import * as modes from 'vs/editor/common/modes'; -import { ActionsOrientation, ActionItem, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionsOrientation, ActionViewItem, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button } from 'vs/base/browser/ui/button/button'; import { Action, IActionRunner } from 'vs/base/common/actions'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -29,9 +29,9 @@ import { assign } from 'vs/base/common/objects'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { DropdownMenuActionItem } from 'vs/base/browser/ui/dropdown/dropdown'; +import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; -import { ToggleReactionsAction, ReactionAction, ReactionActionItem } from './reactionsAction'; +import { ToggleReactionsAction, ReactionAction, ReactionActionViewItem } from './reactionsAction'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; @@ -165,14 +165,14 @@ export class CommentNode extends Disposable { if (actions.length) { this.toolbar = new ToolBar(this._actionsToolbarContainer, this.contextMenuService, { - actionItemProvider: action => { + actionViewItemProvider: action => { if (action.id === ToggleReactionsAction.ID) { - return new DropdownMenuActionItem( + return new DropdownMenuActionViewItem( action, (action).menuActions, this.contextMenuService, action => { - return this.actionItemProvider(action as Action); + return this.actionViewItemProvider(action as Action); }, this.actionRunner!, undefined, @@ -180,7 +180,7 @@ export class CommentNode extends Disposable { () => { return AnchorAlignment.RIGHT; } ); } - return this.actionItemProvider(action as Action); + return this.actionViewItemProvider(action as Action); }, orientation: ActionsOrientation.HORIZONTAL }); @@ -191,7 +191,7 @@ export class CommentNode extends Disposable { } } - actionItemProvider(action: Action) { + actionViewItemProvider(action: Action) { let options = {}; if (action.id === 'comment.delete' || action.id === 'comment.edit' || action.id === ToggleReactionsAction.ID) { options = { label: false, icon: true }; @@ -200,19 +200,19 @@ export class CommentNode extends Disposable { } if (action.id === ReactionAction.ID) { - let item = new ReactionActionItem(action); + let item = new ReactionActionViewItem(action); return item; } else { - let item = new ActionItem({}, action, options); + let item = new ActionViewItem({}, action, options); return item; } } private createReactionPicker2(): ToggleReactionsAction { - let toggleReactionActionItem: DropdownMenuActionItem; + let toggleReactionActionViewItem: DropdownMenuActionViewItem; let toggleReactionAction = this._register(new ToggleReactionsAction(() => { - if (toggleReactionActionItem) { - toggleReactionActionItem.show(); + if (toggleReactionActionViewItem) { + toggleReactionActionViewItem.show(); } }, nls.localize('commentToggleReaction', "Toggle Reaction"))); @@ -235,15 +235,15 @@ export class CommentNode extends Disposable { toggleReactionAction.menuActions = reactionMenuActions; - toggleReactionActionItem = new DropdownMenuActionItem( + toggleReactionActionViewItem = new DropdownMenuActionViewItem( toggleReactionAction, (toggleReactionAction).menuActions, this.contextMenuService, action => { if (action.id === ToggleReactionsAction.ID) { - return toggleReactionActionItem; + return toggleReactionActionViewItem; } - return this.actionItemProvider(action as Action); + return this.actionViewItemProvider(action as Action); }, this.actionRunner!, undefined, @@ -255,10 +255,10 @@ export class CommentNode extends Disposable { } private createReactionPicker(): ToggleReactionsAction { - let toggleReactionActionItem: DropdownMenuActionItem; + let toggleReactionActionViewItem: DropdownMenuActionViewItem; let toggleReactionAction = this._register(new ToggleReactionsAction(() => { - if (toggleReactionActionItem) { - toggleReactionActionItem.show(); + if (toggleReactionActionViewItem) { + toggleReactionActionViewItem.show(); } }, nls.localize('commentAddReaction', "Add Reaction"))); @@ -281,15 +281,15 @@ export class CommentNode extends Disposable { toggleReactionAction.menuActions = reactionMenuActions; - toggleReactionActionItem = new DropdownMenuActionItem( + toggleReactionActionViewItem = new DropdownMenuActionViewItem( toggleReactionAction, (toggleReactionAction).menuActions, this.contextMenuService, action => { if (action.id === ToggleReactionsAction.ID) { - return toggleReactionActionItem; + return toggleReactionActionViewItem; } - return this.actionItemProvider(action as Action); + return this.actionViewItemProvider(action as Action); }, this.actionRunner!, undefined, @@ -303,14 +303,14 @@ export class CommentNode extends Disposable { private createReactionsContainer(commentDetailsContainer: HTMLElement): void { this._reactionActionsContainer = dom.append(commentDetailsContainer, dom.$('div.comment-reactions')); this._reactionsActionBar = new ActionBar(this._reactionActionsContainer, { - actionItemProvider: action => { + actionViewItemProvider: action => { if (action.id === ToggleReactionsAction.ID) { - return new DropdownMenuActionItem( + return new DropdownMenuActionViewItem( action, (action).menuActions, this.contextMenuService, action => { - return this.actionItemProvider(action as Action); + return this.actionViewItemProvider(action as Action); }, this.actionRunner!, undefined, @@ -318,7 +318,7 @@ export class CommentNode extends Disposable { () => { return AnchorAlignment.RIGHT; } ); } - return this.actionItemProvider(action as Action); + return this.actionViewItemProvider(action as Action); } }); this._toDispose.push(this._reactionsActionBar); diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index a4804803543..296267ceb60 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -66,6 +66,7 @@ export interface ICommentService { deleteReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise; getReactionGroup(owner: string): CommentReaction[] | undefined; toggleReaction(owner: string, resource: URI, thread: CommentThread2, comment: Comment, reaction: CommentReaction): Promise; + getCommentThreadFromTemplate(owner: string, resource: URI, range: IRange, ): CommentThread2 | undefined; setActiveCommentThread(commentThread: CommentThread | null): void; setInput(input: string): void; } @@ -255,6 +256,16 @@ export class CommentService extends Disposable implements ICommentService { } } + getCommentThreadFromTemplate(owner: string, resource: URI, range: IRange, ): CommentThread2 | undefined { + const commentController = this._commentControls.get(owner); + + if (commentController) { + return commentController.getCommentThreadFromTemplate(resource, range); + } + + return undefined; + } + getReactionGroup(owner: string): CommentReaction[] | undefined { const commentProvider = this._commentControls.get(owner); @@ -322,15 +333,17 @@ export class CommentService extends Disposable implements ICommentService { } } - let commentControlResult: Promise[] = []; + let commentControlResult: Promise[] = []; this._commentControls.forEach(control => { - commentControlResult.push(control.getDocumentComments(resource, CancellationToken.None)); + commentControlResult.push(control.getDocumentComments(resource, CancellationToken.None) + .catch(e => { + console.log(e); + return null; + })); }); - let ret = [...await Promise.all(result), ...await Promise.all(commentControlResult)]; - - return ret; + return Promise.all([...result, ...commentControlResult]); } async getCommentingRanges(resource: URI): Promise { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 5d223a35e47..db53215e8e6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -65,6 +65,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _commentGlyph?: CommentGlyphWidget; private _submitActionsDisposables: IDisposable[]; private _globalToDispose: IDisposable[]; + private _commentThreadDisposables: IDisposable[] = []; private _markdownRenderer: MarkdownRenderer; private _styleElement: HTMLStyleElement; private _formActions: HTMLElement | null; @@ -103,6 +104,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._resizeObserver = null; this._isExpanded = _commentThread.collapsibleState ? _commentThread.collapsibleState === modes.CommentThreadCollapsibleState.Expanded : undefined; this._globalToDispose = []; + this._commentThreadDisposables = []; this._submitActionsDisposables = []; this._formActions = null; this.create(); @@ -212,7 +214,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } else { const deleteCommand = (this._commentThread as modes.CommentThread2).deleteCommand; if (deleteCommand) { + this.commentService.setActiveCommentThread(this._commentThread); return this.commandService.executeCommand(deleteCommand.id, ...(deleteCommand.arguments || [])); + } else if (this._commentEditor.getValue() === '') { + this.dispose(); + return Promise.resolve(); } } } @@ -239,7 +245,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } } - async update(commentThread: modes.CommentThread | modes.CommentThread2) { + async update(commentThread: modes.CommentThread | modes.CommentThread2, replaceTemplate: boolean = false) { const oldCommentsLen = this._commentElements.length; const newCommentsLen = commentThread.comments ? commentThread.comments.length : 0; @@ -288,12 +294,26 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThread = commentThread; this._commentElements = newCommentNodeList; - this.createThreadLabel(); + this.createThreadLabel(replaceTemplate); + + if (replaceTemplate) { + // since we are replacing the old comment thread, we need to rebind the listeners. + this._commentThreadDisposables.forEach(global => global.dispose()); + this._commentThreadDisposables = []; + } + + if (replaceTemplate) { + this.createTextModelListener(); + } if (this._formActions && this._commentEditor.hasModel()) { dom.clearNode(this._formActions); const model = this._commentEditor.getModel(); this.createCommentWidgetActions2(this._formActions, model); + + if (replaceTemplate) { + this.createCommentWidgetActionsListener(this._formActions, model); + } } // Move comment glyph widget and show position if the line has changed. @@ -348,7 +368,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ }); } - display(lineNumber: number) { + display(lineNumber: number, fromTemplate: boolean = false) { this._commentGlyph = new CommentGlyphWidget(this.editor, lineNumber); this._disposables.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); @@ -387,57 +407,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._disposables.push(this._commentEditor); this._disposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => this.setCommentEditorDecorations())); if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { - this._disposables.push(this._commentEditor.onDidFocusEditorWidget(() => { - let commentThread = this._commentThread as modes.CommentThread2; - commentThread.input = { - uri: this._commentEditor.getModel()!.uri, - value: this._commentEditor.getValue() - }; - this.commentService.setActiveCommentThread(this._commentThread); - })); - - this._disposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => { - let modelContent = this._commentEditor.getValue(); - let thread = (this._commentThread as modes.CommentThread2); - if (thread.input && thread.input.uri === this._commentEditor.getModel()!.uri && thread.input.value !== modelContent) { - let newInput: modes.CommentInput = thread.input; - newInput.value = modelContent; - thread.input = newInput; - } - })); - - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeInput(input => { - let thread = (this._commentThread as modes.CommentThread2); - - if (thread.input && thread.input.uri !== this._commentEditor.getModel()!.uri) { - return; - } - if (!input) { - return; - } - - if (this._commentEditor.getValue() !== input.value) { - this._commentEditor.setValue(input.value); - - if (input.value === '') { - this._pendingComment = ''; - if (dom.hasClass(this._commentForm, 'expand')) { - dom.removeClass(this._commentForm, 'expand'); - } - this._commentEditor.getDomNode()!.style.outline = ''; - this._error.textContent = ''; - dom.addClass(this._error, 'hidden'); - } - } - })); - - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeComments(async _ => { - await this.update(this._commentThread); - })); - - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeLabel(_ => { - this.createThreadLabel(); - })); + this.createTextModelListener(); } this.setCommentEditorDecorations(); @@ -456,50 +426,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._formActions = dom.append(this._commentForm, dom.$('.form-actions')); if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { this.createCommentWidgetActions2(this._formActions, model); - - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeAcceptInputCommand(_ => { - if (this._formActions) { - dom.clearNode(this._formActions); - this.createCommentWidgetActions2(this._formActions, model); - } - })); - - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeAdditionalCommands(_ => { - if (this._formActions) { - dom.clearNode(this._formActions); - this.createCommentWidgetActions2(this._formActions, model); - } - })); - - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeRange(range => { - // Move comment glyph widget and show position if the line has changed. - const lineNumber = this._commentThread.range.startLineNumber; - let shouldMoveWidget = false; - if (this._commentGlyph) { - if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) { - shouldMoveWidget = true; - this._commentGlyph.setLineNumber(lineNumber); - } - } - - if (shouldMoveWidget && this._isExpanded) { - this.show({ lineNumber, column: 1 }, 2); - } - })); - - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeCollasibleState(state => { - if (state === modes.CommentThreadCollapsibleState.Expanded && !this._isExpanded) { - const lineNumber = this._commentThread.range.startLineNumber; - - this.show({ lineNumber, column: 1 }, 2); - return; - } - - if (state === modes.CommentThreadCollapsibleState.Collapsed && this._isExpanded) { - this.hide(); - return; - } - })); + if (!fromTemplate) { + this.createCommentWidgetActionsListener(this._formActions, model); + } } else { this.createCommentWidgetActions(this._formActions, model); } @@ -526,6 +455,106 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } } + private createTextModelListener() { + this._commentThreadDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => { + let commentThread = this._commentThread as modes.CommentThread2; + commentThread.input = { + uri: this._commentEditor.getModel()!.uri, + value: this._commentEditor.getValue() + }; + this.commentService.setActiveCommentThread(this._commentThread); + })); + + this._commentThreadDisposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => { + let modelContent = this._commentEditor.getValue(); + let thread = (this._commentThread as modes.CommentThread2); + if (thread.input && thread.input.uri === this._commentEditor.getModel()!.uri && thread.input.value !== modelContent) { + let newInput: modes.CommentInput = thread.input; + newInput.value = modelContent; + thread.input = newInput; + } + })); + + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeInput(input => { + let thread = (this._commentThread as modes.CommentThread2); + + if (thread.input && thread.input.uri !== this._commentEditor.getModel()!.uri) { + return; + } + if (!input) { + return; + } + + if (this._commentEditor.getValue() !== input.value) { + this._commentEditor.setValue(input.value); + + if (input.value === '') { + this._pendingComment = ''; + if (dom.hasClass(this._commentForm, 'expand')) { + dom.removeClass(this._commentForm, 'expand'); + } + this._commentEditor.getDomNode()!.style.outline = ''; + this._error.textContent = ''; + dom.addClass(this._error, 'hidden'); + } + } + })); + + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeComments(async _ => { + await this.update(this._commentThread); + })); + + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeLabel(_ => { + this.createThreadLabel(); + })); + } + + private createCommentWidgetActionsListener(container: HTMLElement, model: ITextModel) { + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeAcceptInputCommand(_ => { + if (container) { + dom.clearNode(container); + this.createCommentWidgetActions2(container, model); + } + })); + + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeAdditionalCommands(_ => { + if (container) { + dom.clearNode(container); + this.createCommentWidgetActions2(container, model); + } + })); + + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeRange(range => { + // Move comment glyph widget and show position if the line has changed. + const lineNumber = this._commentThread.range.startLineNumber; + let shouldMoveWidget = false; + if (this._commentGlyph) { + if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) { + shouldMoveWidget = true; + this._commentGlyph.setLineNumber(lineNumber); + } + } + + if (shouldMoveWidget && this._isExpanded) { + this.show({ lineNumber, column: 1 }, 2); + } + })); + + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeCollasibleState(state => { + if (state === modes.CommentThreadCollapsibleState.Expanded && !this._isExpanded) { + const lineNumber = this._commentThread.range.startLineNumber; + + this.show({ lineNumber, column: 1 }, 2); + return; + } + + if (state === modes.CommentThreadCollapsibleState.Collapsed && this._isExpanded) { + this.hide(); + return; + } + })); + } + private handleError(e: Error) { this._error.textContent = e.message; this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`; @@ -798,13 +827,14 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } } - private createThreadLabel() { + private createThreadLabel(replaceTemplate: boolean = false) { let label: string | undefined; if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { label = (this._commentThread as modes.CommentThread2).label; } - if (label === undefined) { + if (label === undefined && !replaceTemplate) { + // if it's for replacing the comment thread template, the comment thread widget title can be undefined as extensions may set it later if (this._commentThread.comments && this._commentThread.comments.length) { const participantsList = this._commentThread.comments.filter(arrays.uniqueFilter(comment => comment.userName)).map(comment => `@${comment.userName}`).join(', '); label = nls.localize('commentThreadParticipants', "Participants: {0}", participantsList); @@ -813,8 +843,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } } - this._headingLabel.innerHTML = strings.escape(label); - this._headingLabel.setAttribute('aria-label', label); + if (label) { + this._headingLabel.innerHTML = strings.escape(label); + this._headingLabel.setAttribute('aria-label', label); + } + } private expandReplyArea() { @@ -1040,6 +1073,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } this._globalToDispose.forEach(global => global.dispose()); + this._commentThreadDisposables.forEach(global => global.dispose()); this._submitActionsDisposables.forEach(local => local.dispose()); this._onDidClose.fire(undefined); } diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 62c9bbec380..866bd19ca5a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -30,7 +30,7 @@ import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/com import { overviewRulerCommentingRangeForeground } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; -import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ctxCommentEditorFocused, SimpleCommentEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -64,7 +64,7 @@ class CommentingRangeDecoration { return this._decorationId; } - constructor(private _editor: ICodeEditor, private _ownerId: string, private _extensionId: string | undefined, private _label: string | undefined, private _range: IRange, private _reply: modes.Command | undefined, commentingOptions: ModelDecorationOptions, private commentingRangesInfo?: modes.CommentingRanges) { + constructor(private _editor: ICodeEditor, private _ownerId: string, private _extensionId: string | undefined, private _label: string | undefined, private _range: IRange, private _reply: modes.Command | undefined, commentingOptions: ModelDecorationOptions, private _template: modes.CommentThreadTemplate | undefined, private commentingRangesInfo?: modes.CommentingRanges) { const startLineNumber = _range.startLineNumber; const endLineNumber = _range.endLineNumber; let commentingRangeDecorations = [{ @@ -81,13 +81,14 @@ class CommentingRangeDecoration { } } - public getCommentAction(): { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined } { + public getCommentAction(): { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined, template: modes.CommentThreadTemplate | undefined } { return { extensionId: this._extensionId, label: this._label, replyCommand: this._reply, ownerId: this._ownerId, - commentingRangesInfo: this.commentingRangesInfo + commentingRangesInfo: this.commentingRangesInfo, + template: this._template }; } @@ -124,11 +125,11 @@ class CommentingRangeDecorator { for (const info of commentInfos) { if (Array.isArray(info.commentingRanges)) { info.commentingRanges.forEach(range => { - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, info.reply, this.decorationOptions)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, info.reply, this.decorationOptions, info.template)); }); } else { (info.commentingRanges ? info.commentingRanges.ranges : []).forEach(range => { - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, (info.commentingRanges as modes.CommentingRanges).newCommentThreadCommand, this.decorationOptions, info.commentingRanges as modes.CommentingRanges)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, undefined, this.decorationOptions, info.template, info.commentingRanges as modes.CommentingRanges)); }); } } @@ -178,7 +179,6 @@ export class ReviewController implements IEditorContribution { constructor( editor: ICodeEditor, @ICommentService private readonly commentService: ICommentService, - @ICommandService private readonly _commandService: ICommandService, @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @@ -441,6 +441,18 @@ export class ReviewController implements IEditorContribution { } }); added.forEach(thread => { + let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId); + if (matchedZones.length) { + return; + } + + let matchedNewCommentThreadZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && (zoneWidget.commentThread as any).commentThreadHandle === -1 && Range.equalsRange(zoneWidget.commentThread.range, thread.range)); + + if (matchedNewCommentThreadZones.length) { + matchedNewCommentThreadZones[0].update(thread, true); + return; + } + const pendingCommentText = this._pendingCommentCache[e.owner] && this._pendingCommentCache[e.owner][thread.threadId]; this.displayCommentThread(e.owner, thread, pendingCommentText, draftMode); this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread); @@ -457,6 +469,22 @@ export class ReviewController implements IEditorContribution { this._commentWidgets.push(zoneWidget); } + private addCommentThreadFromTemplate(lineNumber: number, ownerId: string): ReviewZoneWidget { + let templateCommentThread = this.commentService.getCommentThreadFromTemplate(ownerId, this.editor.getModel()!.uri, { + startLineNumber: lineNumber, + startColumn: 1, + endLineNumber: lineNumber, + endColumn: 1 + })!; + + templateCommentThread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded; + templateCommentThread.comments = []; + + let templateReviewZoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, ownerId, templateCommentThread, '', modes.DraftMode.NotSupported); + + return templateReviewZoneWidget; + } + private addComment(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, draftMode: modes.DraftMode | undefined, pendingComment: string | null) { if (this._newCommentWidget) { this.notificationService.warn(`Please submit the comment at line ${this._newCommentWidget.position ? this._newCommentWidget.position.lineNumber : -1} before creating a new one.`); @@ -612,16 +640,16 @@ export class ReviewController implements IEditorContribution { const commentInfos = newCommentInfos.filter(info => info.ownerId === pick.id); if (commentInfos.length) { - const { replyCommand, ownerId, extensionId, commentingRangesInfo } = commentInfos[0]; - this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo); + const { replyCommand, ownerId, extensionId, commentingRangesInfo, template } = commentInfos[0]; + this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo, template); } }).then(() => { this._addInProgress = false; }); } } else { - const { replyCommand, ownerId, extensionId, commentingRangesInfo } = newCommentInfos[0]!; - this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo); + const { replyCommand, ownerId, extensionId, commentingRangesInfo, template } = newCommentInfos[0]!; + this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo, template); } return Promise.resolve(); @@ -640,11 +668,11 @@ export class ReviewController implements IEditorContribution { return picks; } - private getContextMenuActions(commentInfos: { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined }[], lineNumber: number): (IAction | ContextSubMenu)[] { + private getContextMenuActions(commentInfos: { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined, template: modes.CommentThreadTemplate | undefined }[], lineNumber: number): (IAction | ContextSubMenu)[] { const actions: (IAction | ContextSubMenu)[] = []; commentInfos.forEach(commentInfo => { - const { replyCommand, ownerId, extensionId, label, commentingRangesInfo } = commentInfo; + const { replyCommand, ownerId, extensionId, label, commentingRangesInfo, template } = commentInfo; actions.push(new Action( 'addCommentThread', @@ -652,7 +680,7 @@ export class ReviewController implements IEditorContribution { undefined, true, () => { - this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo); + this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo, template); return Promise.resolve(); } )); @@ -660,17 +688,22 @@ export class ReviewController implements IEditorContribution { return actions; } - public addCommentAtLine2(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined) { + public addCommentAtLine2(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined, template: modes.CommentThreadTemplate | undefined) { if (commentingRangesInfo) { let range = new Range(lineNumber, 1, lineNumber, 1); - if (commentingRangesInfo.newCommentThreadCommand) { - if (replyCommand) { - const commandId = replyCommand.id; - const args = replyCommand.arguments || []; - - this._commandService.executeCommand(commandId, ...args); - this._addInProgress = false; - } + if (template) { + // create comment widget through template + let commentThreadWidget = this.addCommentThreadFromTemplate(lineNumber, ownerId); + commentThreadWidget.display(lineNumber, true); + this._commentWidgets.push(commentThreadWidget); + commentThreadWidget.onDidClose(() => { + this._commentWidgets = this._commentWidgets.filter(zoneWidget => !( + zoneWidget.owner === commentThreadWidget.owner && + (zoneWidget.commentThread as any).commentThreadHandle === -1 && + Range.equalsRange(zoneWidget.commentThread.range, commentThreadWidget.commentThread.range) + )); + }); + this.processNextThreadToAdd(); } else if (commentingRangesInfo.newCommentThreadCallback) { return commentingRangesInfo.newCommentThreadCallback(this.editor.getModel()!.uri, range) .then(_ => { @@ -738,6 +771,7 @@ export class ReviewController implements IEditorContribution { this._commentInfos.forEach(info => { let providerCacheStore = this._pendingCommentCache[info.owner]; + info.threads = info.threads.filter(thread => !thread.isDisposed); info.threads.forEach(thread => { let pendingComment: string | null = null; if (providerCacheStore) { @@ -806,7 +840,7 @@ export class NextCommentThreadAction extends EditorAction { id: 'editor.action.nextCommentThreadAction', label: nls.localize('nextCommentThreadAction', "Go to Next Comment Thread"), alias: 'Go to Next Comment Thread', - precondition: null, + precondition: undefined, }); } diff --git a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts index 0b8bc2e8bf9..c14030dab80 100644 --- a/src/vs/workbench/contrib/comments/browser/reactionsAction.ts +++ b/src/vs/workbench/contrib/comments/browser/reactionsAction.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action, IAction } from 'vs/base/common/actions'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -29,7 +29,7 @@ export class ToggleReactionsAction extends Action { this._menuActions = actions; } } -export class ReactionActionItem extends ActionItem { +export class ReactionActionViewItem extends ActionViewItem { constructor(action: ReactionAction) { super(null, action, {}); } diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index e7ef34d2dd4..af8854bac34 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -21,7 +21,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { ILabelService } from 'vs/platform/label/common/label'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { TreeResourceNavigator2, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; diff --git a/src/vs/workbench/contrib/debug/browser/debugActionItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts similarity index 96% rename from src/vs/workbench/contrib/debug/browser/debugActionItems.ts rename to src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index afd9dbbc666..f07ca7f2219 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -9,7 +9,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; -import { SelectActionItem, IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { SelectActionViewItem, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IDebugService, IDebugSession, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; @@ -23,7 +23,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; const $ = dom.$; -export class StartDebugActionItem implements IActionItem { +export class StartDebugActionViewItem implements IActionViewItem { private static readonly SEPARATOR = '─────────'; @@ -171,7 +171,7 @@ export class StartDebugActionItem implements IActionItem { if (this.options.length === 0) { this.options.push({ label: nls.localize('noConfigurations', "No Configurations"), handler: () => false }); } else { - this.options.push({ label: StartDebugActionItem.SEPARATOR, handler: undefined }); + this.options.push({ label: StartDebugActionViewItem.SEPARATOR, handler: undefined }); } const disabledIdx = this.options.length - 1; @@ -189,7 +189,7 @@ export class StartDebugActionItem implements IActionItem { } } -export class FocusSessionActionItem extends SelectActionItem { +export class FocusSessionActionViewItem extends SelectActionViewItem { constructor( action: IAction, @IDebugService protected readonly debugService: IDebugService, diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index 15edca38b8f..a55cb493a1e 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -26,7 +26,7 @@ class ToggleBreakpointAction extends EditorAction { id: TOGGLE_BREAKPOINT_ID, label: nls.localize('toggleBreakpointAction', "Debug: Toggle Breakpoint"), alias: 'Debug: Toggle Breakpoint', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyCode.F9, @@ -63,7 +63,7 @@ class ConditionalBreakpointAction extends EditorAction { id: TOGGLE_CONDITIONAL_BREAKPOINT_ID, label: nls.localize('conditionalBreakpointEditorAction', "Debug: Add Conditional Breakpoint..."), alias: 'Debug: Add Conditional Breakpoint...', - precondition: null + precondition: undefined }); } @@ -85,7 +85,7 @@ class LogPointAction extends EditorAction { id: TOGGLE_LOG_POINT_ID, label: nls.localize('logPointEditorAction', "Debug: Add Logpoint..."), alias: 'Debug: Add Logpoint...', - precondition: null + precondition: undefined }); } @@ -288,7 +288,7 @@ class GoToNextBreakpointAction extends GoToBreakpointAction { id: 'editor.debug.action.goToNextBreakpoint', label: nls.localize('goToNextBreakpoint', "Debug: Go To Next Breakpoint"), alias: 'Debug: Go To Next Breakpoint', - precondition: null + precondition: undefined }); } } @@ -299,7 +299,7 @@ class GoToPreviousBreakpointAction extends GoToBreakpointAction { id: 'editor.debug.action.goToPreviousBreakpoint', label: nls.localize('goToPreviousBreakpoint', "Debug: Go To Previous Breakpoint"), alias: 'Debug: Go To Previous Breakpoint', - precondition: null + precondition: undefined }); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 1932720ed59..84a184174ff 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -14,7 +14,7 @@ import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/act import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IDebugConfiguration, IDebugService, State } from 'vs/workbench/contrib/debug/common/debug'; -import { FocusSessionActionItem } from 'vs/workbench/contrib/debug/browser/debugActionItems'; +import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -27,7 +27,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { INotificationService } from 'vs/platform/notification/common/notification'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { fillInActionBarActions, MenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; +import { fillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; @@ -86,12 +86,12 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.activeActions = []; this.actionBar = this._register(new ActionBar(actionBarContainer, { orientation: ActionsOrientation.HORIZONTAL, - actionItemProvider: (action: IAction) => { + actionViewItemProvider: (action: IAction) => { if (action.id === FocusSessionAction.ID) { - return this.instantiationService.createInstance(FocusSessionActionItem, action); + return this.instantiationService.createInstance(FocusSessionActionViewItem, action); } if (action instanceof MenuItemAction) { - return new MenuItemActionItem(action, this.keybindingService, this.notificationService, contextMenuService); + return new MenuEntryActionViewItem(action, this.keybindingService, this.notificationService, contextMenuService); } return undefined; diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 7bb82f5bf93..2f2d7c2ea09 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -7,11 +7,11 @@ import 'vs/css!./media/debugViewlet'; import * as nls from 'vs/nls'; import { IAction } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; -import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration, REPL_ID } from 'vs/workbench/contrib/debug/common/debug'; import { StartAction, ConfigureAction, SelectAndStartAction, FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; -import { StartDebugActionItem, FocusSessionActionItem } from 'vs/workbench/contrib/debug/browser/debugActionItems'; +import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; @@ -29,14 +29,14 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IMenu, MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { MenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; +import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; export class DebugViewlet extends ViewContainerViewlet { - private startDebugActionItem: StartDebugActionItem; + private startDebugActionViewItem: StartDebugActionViewItem; private progressRunner: IProgressRunner; private breakpointView: ViewletPanel; private panelListeners = new Map(); @@ -80,8 +80,8 @@ export class DebugViewlet extends ViewContainerViewlet { focus(): void { super.focus(); - if (this.startDebugActionItem) { - this.startDebugActionItem.focus(); + if (this.startDebugActionViewItem) { + this.startDebugActionViewItem.focus(); } } @@ -130,16 +130,16 @@ export class DebugViewlet extends ViewContainerViewlet { return [this.selectAndStartAction, this.configureAction, this.toggleReplAction]; } - getActionItem(action: IAction): IActionItem | undefined { + getActionViewItem(action: IAction): IActionViewItem | undefined { if (action.id === StartAction.ID) { - this.startDebugActionItem = this.instantiationService.createInstance(StartDebugActionItem, null, action); - return this.startDebugActionItem; + this.startDebugActionViewItem = this.instantiationService.createInstance(StartDebugActionViewItem, null, action); + return this.startDebugActionViewItem; } if (action.id === FocusSessionAction.ID) { - return new FocusSessionActionItem(action, this.debugService, this.themeService, this.contextViewService, this.configurationService); + return new FocusSessionActionViewItem(action, this.debugService, this.themeService, this.contextViewService, this.configurationService); } if (action instanceof MenuItemAction) { - return new MenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + return new MenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); } return undefined; diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 75b1edfd663..d2acc3d92b4 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -7,7 +7,7 @@ import 'vs/css!vs/workbench/contrib/debug/browser/media/repl'; import * as nls from 'vs/nls'; import { URI as uri } from 'vs/base/common/uri'; import * as errors from 'vs/base/common/errors'; -import { IAction, IActionItem, Action } from 'vs/base/common/actions'; +import { IAction, IActionViewItem, Action } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -39,7 +39,7 @@ import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/wor import { IDecorationOptions } from 'vs/editor/common/editorCommon'; import { transparent, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { FocusSessionActionItem } from 'vs/workbench/contrib/debug/browser/debugActionItems'; +import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems'; import { CompletionContext, CompletionList, CompletionProviderRegistry } from 'vs/editor/common/modes'; import { first } from 'vs/base/common/arrays'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -309,9 +309,9 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.replInput.focus(); } - getActionItem(action: IAction): IActionItem | undefined { + getActionViewItem(action: IAction): IActionViewItem | undefined { if (action.id === SelectReplAction.ID) { - return this.instantiationService.createInstance(SelectReplActionItem, this.selectReplAction); + return this.instantiationService.createInstance(SelectReplActionViewItem, this.selectReplAction); } return undefined; @@ -852,7 +852,7 @@ class ReplCopyAllAction extends EditorAction { registerEditorAction(AcceptReplInputAction); registerEditorAction(ReplCopyAllAction); -class SelectReplActionItem extends FocusSessionActionItem { +class SelectReplActionViewItem extends FocusSessionActionViewItem { protected getActionContext(_: string, index: number): any { return this.debugService.getModel().getSessions(true)[index]; diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 910e122ab34..10e14db4745 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -25,6 +25,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; +import { ITerminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminal'; export const VIEWLET_ID = 'workbench.view.debug'; export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); @@ -573,13 +574,7 @@ export interface ITerminalSettings { osxExec: string, linuxExec: string }; - integrated: { - shell: { - osx: string, - windows: string, - linux: string - } - }; + integrated: ITerminalConfiguration; } export interface IConfigurationManager { @@ -609,7 +604,6 @@ export interface IConfigurationManager { activateDebuggers(activationEvent: string, debugType?: string): Promise; - needsToRunInExtHost(debugType: string): boolean; hasDebugConfigurationProvider(debugType: string): boolean; registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable; @@ -618,9 +612,6 @@ export interface IConfigurationManager { registerDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): IDisposable; unregisterDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): void; - registerDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): IDisposable; - unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): void; - resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): Promise; getDebugAdapterDescriptor(session: IDebugSession): Promise; diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index b7be3e1ff3a..2a2c9bd8848 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -18,7 +18,7 @@ import { ITreeElement, IExpression, IExpressionContainer, IDebugSession, IStackFrame, IExceptionBreakpoint, IBreakpoint, IFunctionBreakpoint, IDebugModel, IReplElementSource, IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IBreakpointData, IExceptionInfo, IReplElement, IBreakpointsChangeEvent, IBreakpointUpdateData, IBaseBreakpoint, State } from 'vs/workbench/contrib/debug/common/debug'; -import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; +import { Source, UNKNOWN_SOURCE_LABEL } from 'vs/workbench/contrib/debug/common/debugSource'; import { commonSuffixLength } from 'vs/base/common/strings'; import { posix } from 'vs/base/common/path'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -381,7 +381,10 @@ export class StackFrame implements IStackFrame { } toString(): string { - return `${this.name} (${this.source.inMemory ? this.source.name : this.source.uri.fsPath}:${this.range.startLineNumber})`; + const lineNumberToString = typeof this.range.startLineNumber === 'number' ? `:${this.range.startLineNumber}` : ''; + const sourceToString = `${this.source.inMemory ? this.source.name : this.source.uri.fsPath}${lineNumberToString}`; + + return sourceToString === UNKNOWN_SOURCE_LABEL ? this.name : `${this.name} (${sourceToString})`; } openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { diff --git a/src/vs/workbench/contrib/debug/common/debugSource.ts b/src/vs/workbench/contrib/debug/common/debugSource.ts index 96897b469b5..0c4b3f3929e 100644 --- a/src/vs/workbench/contrib/debug/common/debugSource.ts +++ b/src/vs/workbench/contrib/debug/common/debugSource.ts @@ -13,7 +13,7 @@ import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/ import { Schemas } from 'vs/base/common/network'; import { isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; -const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source"); +export const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source"); /** * Debug URI format diff --git a/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts index 1ee59ddda74..7c53b632718 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts @@ -21,7 +21,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugAdapterTrackerFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { Debugger } from 'vs/workbench/contrib/debug/node/debugger'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -52,7 +52,6 @@ export class ConfigurationManager implements IConfigurationManager { private _onDidSelectConfigurationName = new Emitter(); private configProviders: IDebugConfigurationProvider[]; private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[]; - private adapterTrackerFactories: IDebugAdapterTrackerFactory[]; private debugAdapterFactories: Map; private terminalLauncher: ITerminalLauncher; private debugConfigurationTypeContext: IContextKey; @@ -72,7 +71,6 @@ export class ConfigurationManager implements IConfigurationManager { ) { this.configProviders = []; this.adapterDescriptorFactories = []; - this.adapterTrackerFactories = []; this.debuggers = []; this.toDispose = []; this.registerListeners(lifecycleService); @@ -164,24 +162,6 @@ export class ConfigurationManager implements IConfigurationManager { return Promise.resolve(undefined); } - // debug adapter trackers - - registerDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): IDisposable { - this.adapterTrackerFactories.push(debugAdapterTrackerFactory); - return { - dispose: () => { - this.unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory); - } - }; - } - - unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): void { - const ix = this.adapterTrackerFactories.indexOf(debugAdapterTrackerFactory); - if (ix >= 0) { - this.adapterTrackerFactories.splice(ix, 1); - } - } - // debug configurations registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable { @@ -206,13 +186,6 @@ export class ConfigurationManager implements IConfigurationManager { return providers.length > 0; } - needsToRunInExtHost(debugType: string): boolean { - - // if the given debugType matches any registered tracker factory we need to run the DA in the EH - const providers = this.adapterTrackerFactories.filter(p => p.type === debugType || p.type === '*'); - return providers.length > 0; - } - resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig): Promise { return this.activateDebuggers('onDebugResolve', type).then(() => { // pipe the config through the promises sequentially. Append at the end the '*' types @@ -559,7 +532,7 @@ class Launch extends AbstractLaunch implements ILaunch { const resource = this.uri; let created = false; - return this.fileService.resolveContent(resource).then(content => content.value, err => { + return this.fileService.readFile(resource).then(content => content.value, err => { // launch.json not found: create one by collecting launch configs from debugConfigProviders return this.configurationManager.guessDebugger(type).then(adapter => { if (adapter) { @@ -576,19 +549,17 @@ class Launch extends AbstractLaunch implements ILaunch { } created = true; // pin only if config file is created #8727 - return this.textFileService.write(resource, content).then(() => { - // convert string into IContent; see #32135 - return content; - }); + return this.textFileService.write(resource, content).then(() => content); }); }).then(content => { if (!content) { return { editor: null, created: false }; } - const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`); + const contentValue = content.toString(); + const index = contentValue.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`); let startLineNumber = 1; for (let i = 0; i < index; i++) { - if (content.charAt(i) === '\n') { + if (contentValue.charAt(i) === '\n') { startLineNumber++; } } diff --git a/src/vs/workbench/contrib/debug/electron-browser/debugService.ts b/src/vs/workbench/contrib/debug/electron-browser/debugService.ts index 2e4ef24b083..98980e88909 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugService.ts @@ -392,7 +392,7 @@ export class DebugService implements IDebugService { return this.showError(err.message).then(() => false); } if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { - return this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved on disk and that you have a debug extension installed for that file type.")) + return this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved and that you have a debug extension installed for that file type.")) .then(() => false); } @@ -719,7 +719,7 @@ export class DebugService implements IDebugService { // If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340 let taskStarted = false; - const promise = this.taskService.getActiveTasks().then(tasks => { + const promise: Promise = this.taskService.getActiveTasks().then(tasks => { if (tasks.filter(t => t._id === task._id).length) { // task is already running - nothing to do. return Promise.resolve(null); @@ -733,7 +733,7 @@ export class DebugService implements IDebugService { if (task.configurationProperties.isBackground) { return new Promise((c, e) => once(e => e.kind === TaskEventKind.Inactive && e.taskId === task._id, this.taskService.onDidStateChange)(() => { taskStarted = true; - c(undefined); + c(null); })); } diff --git a/src/vs/workbench/contrib/debug/node/debugger.ts b/src/vs/workbench/contrib/debug/node/debugger.ts index afc2c73688d..c51ff6f872b 100644 --- a/src/vs/workbench/contrib/debug/node/debugger.ts +++ b/src/vs/workbench/contrib/debug/node/debugger.ts @@ -11,11 +11,9 @@ import { isObject } from 'vs/base/common/types'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IConfig, IDebuggerContribution, IDebugAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, ITerminalSettings, IDebugger, IDebugSession, IAdapterDescriptor, IDebugAdapterServer } from 'vs/workbench/contrib/debug/common/debug'; +import { IConfig, IDebuggerContribution, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, ITerminalSettings, IDebugger, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; -import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import * as ConfigurationResolverUtils from 'vs/workbench/services/configurationResolver/common/configurationResolverUtils'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; @@ -38,7 +36,6 @@ export class Debugger implements IDebugger { constructor(private configurationManager: IConfigurationManager, dbgContribution: IDebuggerContribution, extensionDescription: IExtensionDescription, @IConfigurationService private readonly configurationService: IConfigurationService, @ITextResourcePropertiesService private readonly resourcePropertiesService: ITextResourcePropertiesService, - @ICommandService private readonly commandService: ICommandService, @IConfigurationResolverService private readonly configurationResolverService: IConfigurationResolverService, @ITelemetryService private readonly telemetryService: ITelemetryService, ) { @@ -99,95 +96,23 @@ export class Debugger implements IDebugger { public createDebugAdapter(session: IDebugSession, outputService: IOutputService): Promise { return this.configurationManager.activateDebuggers('onDebugAdapterProtocolTracker', this.type).then(_ => { - if (this.inExtHost()) { - const da = this.configurationManager.createDebugAdapter(session); - if (da) { - return Promise.resolve(da); - } - throw new Error(nls.localize('cannot.find.da', "Cannot find debug adapter for type '{0}'.", this.type)); - } else { - return this.getAdapterDescriptor(session).then(adapterDescriptor => { - switch (adapterDescriptor.type) { - case 'executable': - return new ExecutableDebugAdapter(adapterDescriptor, this.type, outputService); - case 'server': - return new SocketDebugAdapter(adapterDescriptor); - case 'implementation': - // TODO@AW: this.inExtHost() should now return true - return Promise.resolve(this.configurationManager.createDebugAdapter(session)); - default: - throw new Error('unknown descriptor type'); - } - }).catch(err => { - if (err && err.message) { - throw new Error(nls.localize('cannot.create.da.with.err', "Cannot create debug adapter ({0}).", err.message)); - } else { - throw new Error(nls.localize('cannot.create.da', "Cannot create debug adapter.")); - } - }); + const da = this.configurationManager.createDebugAdapter(session); + if (da) { + return Promise.resolve(da); } - }); - } - - private getAdapterDescriptor(session: IDebugSession): Promise { - - // a "debugServer" attribute in the launch config takes precedence - if (typeof session.configuration.debugServer === 'number') { - return Promise.resolve({ - type: 'server', - port: session.configuration.debugServer - }); - } - - // try the new "createDebugAdapterDescriptor" and the deprecated "provideDebugAdapter" API - return this.configurationManager.getDebugAdapterDescriptor(session).then(adapter => { - - if (adapter) { - return adapter; - } - - // try deprecated command based extension API "adapterExecutableCommand" to determine the executable - if (this.debuggerContribution.adapterExecutableCommand) { - console.info('debugAdapterExecutable attribute in package.json is deprecated and support for it will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.'); - const rootFolder = session.root ? session.root.uri.toString() : undefined; - return this.commandService.executeCommand(this.debuggerContribution.adapterExecutableCommand, rootFolder).then(ae => { - if (ae) { - return { - type: 'executable', - command: ae.command, - args: ae.args || [] - }; - } - throw new Error('command adapterExecutableCommand did not return proper command.'); - }); - } - - // fallback: use executable information from package.json - const ae = ExecutableDebugAdapter.platformAdapterExecutable(this.mergedExtensionDescriptions, this.type); - if (ae === undefined) { - throw new Error('no executable specified in package.json'); - } - return ae; + throw new Error(nls.localize('cannot.find.da', "Cannot find debug adapter for type '{0}'.", this.type)); }); } substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise { - if (this.inExtHost()) { - return this.configurationManager.substituteVariables(this.type, folder, config).then(config => { - return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables); - }); - } else { + return this.configurationManager.substituteVariables(this.type, folder, config).then(config => { return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables); - } + }); } runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise { const config = this.configurationService.getValue('terminal'); - return this.configurationManager.runInTerminal(this.inExtHost() ? this.type : '*', args, config); - } - - private inExtHost(): boolean { - return true; + return this.configurationManager.runInTerminal(this.type, args, config); } get label(): string { diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index 347387d1a5f..066eca77eed 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -10,6 +10,7 @@ import * as pfs from 'vs/base/node/pfs'; import { assign } from 'vs/base/common/objects'; import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug'; import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal'; const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); @@ -29,35 +30,37 @@ export function getTerminalLauncher() { } let _DEFAULT_TERMINAL_LINUX_READY: Promise | null = null; + export function getDefaultTerminalLinuxReady(): Promise { if (!_DEFAULT_TERMINAL_LINUX_READY) { - _DEFAULT_TERMINAL_LINUX_READY = new Promise(c => { + _DEFAULT_TERMINAL_LINUX_READY = new Promise(resolve => { if (env.isLinux) { Promise.all([pfs.exists('/etc/debian_version'), process.lazyEnv]).then(([isDebian]) => { if (isDebian) { - c('x-terminal-emulator'); + resolve('x-terminal-emulator'); } else if (process.env.DESKTOP_SESSION === 'gnome' || process.env.DESKTOP_SESSION === 'gnome-classic') { - c('gnome-terminal'); + resolve('gnome-terminal'); } else if (process.env.DESKTOP_SESSION === 'kde-plasma') { - c('konsole'); + resolve('konsole'); } else if (process.env.COLORTERM) { - c(process.env.COLORTERM); + resolve(process.env.COLORTERM); } else if (process.env.TERM) { - c(process.env.TERM); + resolve(process.env.TERM); } else { - c('xterm'); + resolve('xterm'); } }); return; } - c('xterm'); + resolve('xterm'); }); } return _DEFAULT_TERMINAL_LINUX_READY; } let _DEFAULT_TERMINAL_WINDOWS: string | null = null; + export function getDefaultTerminalWindows(): string { if (!_DEFAULT_TERMINAL_WINDOWS) { const isWoW64 = !!process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); @@ -82,7 +85,7 @@ class WinTerminalService extends TerminalLauncher { const exec = configuration.external.windowsExec || getDefaultTerminalWindows(); - return new Promise((c, e) => { + return new Promise((resolve, reject) => { const title = `"${dir} - ${TERMINAL_TITLE}"`; const command = `""${args.join('" "')}" & pause"`; // use '|' to only pause on non-zero exit code @@ -104,9 +107,11 @@ class WinTerminalService extends TerminalLauncher { }; const cmd = cp.spawn(WinTerminalService.CMD, cmdArgs, options); - cmd.on('error', e); + cmd.on('error', err => { + reject(improveError(err)); + }); - c(undefined); + resolve(undefined); }); } } @@ -120,7 +125,7 @@ class MacTerminalService extends TerminalLauncher { const terminalApp = configuration.external.osxExec || MacTerminalService.DEFAULT_TERMINAL_OSX; - return new Promise((c, e) => { + return new Promise((resolve, reject) => { if (terminalApp === MacTerminalService.DEFAULT_TERMINAL_OSX || terminalApp === 'iTerm.app') { @@ -156,24 +161,26 @@ class MacTerminalService extends TerminalLauncher { let stderr = ''; const osa = cp.spawn(MacTerminalService.OSASCRIPT, osaArgs); - osa.on('error', e); + osa.on('error', err => { + reject(improveError(err)); + }); osa.stderr.on('data', (data) => { stderr += data.toString(); }); osa.on('exit', (code: number) => { if (code === 0) { // OK - c(undefined); + resolve(undefined); } else { if (stderr) { const lines = stderr.split('\n', 1); - e(new Error(lines[0])); + reject(new Error(lines[0])); } else { - e(new Error(nls.localize('mac.terminal.script.failed', "Script '{0}' failed with exit code {1}", script, code))); + reject(new Error(nls.localize('mac.terminal.script.failed', "Script '{0}' failed with exit code {1}", script, code))); } } }); } else { - e(new Error(nls.localize('mac.terminal.type.not.supported', "'{0}' not supported", terminalApp))); + reject(new Error(nls.localize('mac.terminal.type.not.supported', "'{0}' not supported", terminalApp))); } }); } @@ -188,7 +195,7 @@ class LinuxTerminalService extends TerminalLauncher { const terminalConfig = configuration.external; const execThenable: Promise = terminalConfig.linuxExec ? Promise.resolve(terminalConfig.linuxExec) : getDefaultTerminalLinuxReady(); - return new Promise((c, e) => { + return new Promise((resolve, reject) => { let termArgs: string[] = []; //termArgs.push('--title'); @@ -218,19 +225,21 @@ class LinuxTerminalService extends TerminalLauncher { let stderr = ''; const cmd = cp.spawn(exec, termArgs, options); - cmd.on('error', e); + cmd.on('error', err => { + reject(improveError(err)); + }); cmd.stderr.on('data', (data) => { stderr += data.toString(); }); cmd.on('exit', (code: number) => { if (code === 0) { // OK - c(undefined); + resolve(undefined); } else { if (stderr) { const lines = stderr.split('\n', 1); - e(new Error(lines[0])); + reject(new Error(lines[0])); } else { - e(new Error(nls.localize('linux.term.failed', "'{0}' failed with exit code {1}", exec, code))); + reject(new Error(nls.localize('linux.term.failed', "'{0}' failed with exit code {1}", exec, code))); } } }); @@ -239,6 +248,16 @@ class LinuxTerminalService extends TerminalLauncher { } } +/** + * tries to turn OS errors into more meaningful error messages + */ +function improveError(err: Error): Error { + if (err['errno'] === 'ENOENT' && err['path']) { + return new Error(nls.localize('ext.term.app.not.found', "can't find terminal application '{0}'", err['path'])); + } + return err; +} + /** * Quote args if necessary and combine into a space separated string. */ @@ -296,13 +315,13 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments let shell: string; const shell_config = config.integrated.shell; if (env.isWindows) { - shell = shell_config.windows; + shell = shell_config.windows || getDefaultShell(env.Platform.Windows); shellType = ShellType.cmd; } else if (env.isLinux) { - shell = shell_config.linux; + shell = shell_config.linux || getDefaultShell(env.Platform.Linux); shellType = ShellType.bash; } else if (env.isMacintosh) { - shell = shell_config.osx; + shell = shell_config.osx || getDefaultShell(env.Platform.Mac); shellType = ShellType.bash; } else { throw new Error('Unknown platform'); diff --git a/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts index bc16c9e9178..ab478a64ddf 100644 --- a/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts @@ -394,6 +394,22 @@ suite('Debug - Model', () => { assert.equal(secondStackFrame.getSpecificSourceName(), '.../x/c/d/internalModule.js'); }); + test('stack frame toString()', () => { + const session = createMockSession(model); + const thread = new Thread(session, 'mockthread', 1); + const firstSource = new Source({ + name: 'internalModule.js', + path: 'a/b/c/d/internalModule.js', + sourceReference: 10, + }, 'aDebugSessionId'); + const stackFrame = new StackFrame(thread, 1, firstSource, 'app', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1); + assert.equal(stackFrame.toString(), 'app (internalModule.js:1)'); + + const secondSource = new Source(undefined, 'aDebugSessionId'); + const stackFrame2 = new StackFrame(thread, 2, secondSource, 'module', 'normal', { startLineNumber: undefined!, startColumn: undefined!, endLineNumber: undefined!, endColumn: undefined! }, 2); + assert.equal(stackFrame2.toString(), 'module'); + }); + test('debug child sessions are added in correct order', () => { const session = createMockSession(model); model.addSession(session); diff --git a/src/vs/workbench/contrib/debug/test/node/debugger.test.ts b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts index 72cfc4d9805..b5a5fa9f8bb 100644 --- a/src/vs/workbench/contrib/debug/test/node/debugger.test.ts +++ b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts @@ -130,7 +130,7 @@ suite('Debug - Debugger', () => { const testResourcePropertiesService = new TestTextResourcePropertiesService(configurationService); setup(() => { - _debugger = new Debugger(configurationManager, debuggerContribution, extensionDescriptor0, configurationService, testResourcePropertiesService, undefined!, undefined!, undefined!); + _debugger = new Debugger(configurationManager, debuggerContribution, extensionDescriptor0, configurationService, testResourcePropertiesService, undefined!, undefined!); }); teardown(() => { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index cef986724cb..35af370ca9e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -173,7 +173,7 @@ class OpenExtensionAction extends Action { } } -export class ExtensionsTree extends WorkbenchAsyncDataTree { +export class ExtensionsTree extends WorkbenchAsyncDataTree { constructor( input: IExtensionData, @@ -212,7 +212,9 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree this.setInput(input); this.disposables.push(this.onDidChangeSelection(event => { - extensionsWorkdbenchService.open(event.elements[0], event.browserEvent instanceof MouseEvent && (event.browserEvent.ctrlKey || event.browserEvent.metaKey || event.browserEvent.altKey)); + if (event.browserEvent && event.browserEvent instanceof KeyboardEvent) { + extensionsWorkdbenchService.open(event.elements[0].extension, false); + } })); } } \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index a66dccc55fe..ec1999a2b32 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -83,6 +83,7 @@ export interface IExtensionsWorkbenchService { _serviceBrand: any; onChange: Event; local: IExtension[]; + installed: IExtension[]; outdated: IExtension[]; queryLocal(server?: IExtensionManagementServer): Promise; queryGallery(token: CancellationToken): Promise>; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts index 7357cfa53cc..e87adb8c170 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts @@ -28,7 +28,7 @@ import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/electron-browser/extensionsWidgets'; import { EditorOptions } from 'vs/workbench/common/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, DisabledLabelAction, SystemDisabledWarningAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; +import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, DisabledLabelAction, SystemDisabledWarningAction, LocalInstallAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -249,9 +249,9 @@ export class ExtensionEditor extends BaseEditor { const extensionActions = append(details, $('.actions')); this.extensionActionBar = new ActionBar(extensionActions, { animated: false, - actionItemProvider: (action: Action) => { + actionViewItemProvider: (action: Action) => { if (action instanceof ExtensionEditorDropDownAction) { - return action.createActionItem(); + return action.createActionViewItem(); } return undefined; } @@ -296,7 +296,7 @@ export class ExtensionEditor extends BaseEditor { this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(token))); this.extensionDependencies = new Cache(() => createCancelablePromise(token => this.extensionsWorkbenchService.loadDependencies(extension, token))); - const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, this.iconContainer); + const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, this.iconContainer, true); const onError = Event.once(domEvent(this.icon, 'error')); onError(() => this.icon.src = extension.iconUrlFallback, null, this.transientDisposables); this.icon.src = extension.iconUrl; @@ -378,6 +378,7 @@ export class ExtensionEditor extends BaseEditor { this.instantiationService.createInstance(EnableDropDownAction), this.instantiationService.createInstance(DisableDropDownAction, runningExtensions), this.instantiationService.createInstance(RemoteInstallAction), + this.instantiationService.createInstance(LocalInstallAction), combinedInstallAction, systemDisabledWarningAction, this.instantiationService.createInstance(DisabledLabelAction, systemDisabledWarningAction), @@ -549,7 +550,7 @@ export class ExtensionEditor extends BaseEditor { this.contentDisposables.push(webviewElement.onDidFocus(() => this.fireOnDidFocus())); const removeLayoutParticipant = arrays.insert(this.layoutParticipants, webviewElement); this.contentDisposables.push(toDisposable(removeLayoutParticipant)); - webviewElement.contents = body; + webviewElement.html = body; this.contentDisposables.push(webviewElement.onDidClickLink(link => { if (!link) { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts index e58c5a2e2b9..27f7b93a97e 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts @@ -45,6 +45,7 @@ import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/wo import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { extname } from 'vs/base/common/resources'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; const milliSecondsInADay = 1000 * 60 * 60 * 24; const choiceNever = localize('neverShowAgain', "Don't Show Again"); @@ -108,6 +109,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, @IExperimentService private readonly experimentService: IExperimentService, + @ITextFileService private readonly textFileService: ITextFileService ) { super(); @@ -325,8 +327,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return Promise.resolve(null); } - return Promise.resolve(this.fileService.resolveContent(workspace.configuration) - .then(content => (json.parse(content.value)['extensions']), err => null)); + return Promise.resolve(this.fileService.readFile(workspace.configuration) + .then(content => (json.parse(content.value.toString())['extensions']), err => null)); } /** @@ -336,8 +338,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const extensionsJsonUri = workspaceFolder.toResource(EXTENSIONS_CONFIG); return Promise.resolve(this.fileService.resolve(extensionsJsonUri) - .then(() => this.fileService.resolveContent(extensionsJsonUri)) - .then(content => json.parse(content.value), err => null)); + .then(() => this.fileService.readFile(extensionsJsonUri)) + .then(content => json.parse(content.value.toString()), err => null)); } /** @@ -956,7 +958,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const storageKey = 'extensionsAssistant/dynamicWorkspaceRecommendations'; const workspaceUri = this.contextService.getWorkspace().folders[0].uri; - return Promise.all([getHashedRemotesFromUri(workspaceUri, this.fileService, false), getHashedRemotesFromUri(workspaceUri, this.fileService, true)]).then(([hashedRemotes1, hashedRemotes2]) => { + return Promise.all([getHashedRemotesFromUri(workspaceUri, this.fileService, this.textFileService, false), getHashedRemotesFromUri(workspaceUri, this.fileService, this.textFileService, true)]).then(([hashedRemotes1, hashedRemotes2]) => { const hashedRemotes = (hashedRemotes1 || []).concat(hashedRemotes2 || []); if (!hashedRemotes.length) { return undefined; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index f33351c57a5..900e726c360 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -241,11 +241,6 @@ Registry.as(ConfigurationExtensions.Configuration) type: 'boolean', description: localize('extensionsCloseExtensionDetailsOnViewChange', "When enabled, editors with extension details will be automatically closed upon navigating away from the Extensions View."), default: false - }, - 'extensions.showInstalledExtensionsByDefault': { - type: 'boolean', - description: localize('extensions.showInstalledExtensionsByDefault', "When enabled, extensions view shows installed extensions view by default."), - default: false } } }); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index d5a00880d2b..15e4a5ca265 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -10,19 +10,19 @@ import { Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; -import { ActionItem, Separator, IActionItemOptions } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionViewItem, Separator, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel, IExtensionRecommendation, IGalleryExtension, IExtensionsConfigContent, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, ExtensionIdentifier, IExtensionDescription, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ShowViewletAction } from 'vs/workbench/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; -import { IFileService, IContent } from 'vs/platform/files/common/files'; +import { IFileService, IFileContent } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -164,8 +164,7 @@ export class InstallAction extends ExtensionAction { @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IConfigurationService private readonly configurationService: IConfigurationService, - @ILabelService private readonly labelService: ILabelService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @ILabelService private readonly labelService: ILabelService ) { super(`extensions.install`, InstallAction.INSTALL_LABEL, InstallAction.Class, false); this.update(); @@ -173,14 +172,17 @@ export class InstallAction extends ExtensionAction { } update(): void { - if (!this.extension || this.extension.type === ExtensionType.System) { + if (!this.extension || this.extension.type === ExtensionType.System || this.extension.state === ExtensionState.Installed) { this.enabled = false; this.class = InstallAction.Class; this.label = InstallAction.INSTALL_LABEL; return; } - - this.enabled = this.extensionsWorkbenchService.canInstall(this.extension) && !this.extensionsWorkbenchService.local.some(e => areSameExtensions(e.identifier, this.extension.identifier)); + this.enabled = false; + if (this.extensionsWorkbenchService.canInstall(this.extension)) { + const local = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier))[0]; + this.enabled = !local || (!!local.local && isLanguagePackExtension(local.local.manifest)); + } this.class = this.extension.state === ExtensionState.Installing ? InstallAction.InstallingClass : InstallAction.Class; this.updateLabel(); } @@ -192,12 +194,12 @@ export class InstallAction extends ExtensionAction { } else { if (this._manifest && this.workbenchEnvironmentService.configuration.remoteAuthority) { if (isUIExtension(this._manifest, this.configurationService)) { - this.label = `${InstallAction.INSTALL_LABEL} (${this.extensionManagementServerService.localExtensionManagementServer.label})`; - this.tooltip = `${InstallAction.INSTALL_LABEL} (${this.extensionManagementServerService.localExtensionManagementServer.label})`; + this.label = `${InstallAction.INSTALL_LABEL} ${localize('locally', "Locally")}`; + this.tooltip = `${InstallAction.INSTALL_LABEL} ${localize('locally', "Locally")}`; } else { const host = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.workbenchEnvironmentService.configuration.remoteAuthority) || localize('remote', "Remote"); - this.label = `${InstallAction.INSTALL_LABEL} (${host})`; - this.tooltip = `${InstallAction.INSTALL_LABEL} (${host})`; + this.label = `${InstallAction.INSTALL_LABEL} on ${host}`; + this.tooltip = `${InstallAction.INSTALL_LABEL} on ${host}`; } } else { this.label = InstallAction.INSTALL_LABEL; @@ -301,12 +303,14 @@ export class RemoteInstallAction extends ExtensionAction { private updateLabel(): void { if (this.installing) { this.label = RemoteInstallAction.INSTALLING_LABEL; + this.tooltip = this.label; return; } const remoteAuthority = this.environmentService.configuration.remoteAuthority; if (remoteAuthority) { const host = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.environmentService.configuration.remoteAuthority) || localize('remote', "Remote"); - this.label = `${RemoteInstallAction.INSTALL_LABEL} (${host})`; + this.label = `${RemoteInstallAction.INSTALL_LABEL} on ${host}`; + this.tooltip = this.label; return; } } @@ -324,9 +328,9 @@ export class RemoteInstallAction extends ExtensionAction { // Installed User Extension && this.extension && this.extension.local && this.extension.type === ExtensionType.User && this.extension.state === ExtensionState.Installed // Local Workspace Extension - && this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && !isUIExtension(this.extension.local.manifest, this.configurationService) + && this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || !isUIExtension(this.extension.local.manifest, this.configurationService)) // Extension does not exist in remote - && !this.extensionsWorkbenchService.local.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.remoteExtensionManagementServer) + && !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.remoteExtensionManagementServer) && this.extensionsWorkbenchService.canInstall(this.extension) ) { this.enabled = true; @@ -355,6 +359,85 @@ export class RemoteInstallAction extends ExtensionAction { } } +export class LocalInstallAction extends ExtensionAction { + + private static INSTALL_LABEL = localize('install locally', "Install Locally"); + private static INSTALLING_LABEL = localize('installing', "Installing"); + + private static readonly Class = 'extension-action prominent install'; + private static readonly InstallingClass = 'extension-action install installing'; + + updateWhenCounterExtensionChanges: boolean = true; + private disposables: IDisposable[] = []; + private installing: boolean = false; + + constructor( + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @ILabelService private readonly labelService: ILabelService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @IConfigurationService private readonly configurationService: IConfigurationService, + ) { + super(`extensions.localinstall`, LocalInstallAction.INSTALL_LABEL, LocalInstallAction.Class, false); + this.labelService.onDidChangeFormatters(() => this.updateLabel(), this, this.disposables); + this.updateLabel(); + this.update(); + } + + private updateLabel(): void { + if (this.installing) { + this.label = LocalInstallAction.INSTALLING_LABEL; + this.tooltip = this.label; + return; + } + this.label = `${LocalInstallAction.INSTALL_LABEL}`; + this.tooltip = this.label; + } + + update(): void { + this.enabled = false; + this.class = LocalInstallAction.Class; + if (this.installing) { + this.enabled = true; + this.class = LocalInstallAction.InstallingClass; + this.updateLabel(); + return; + } + if (this.environmentService.configuration.remoteAuthority + // Installed User Extension + && this.extension && this.extension.local && this.extension.type === ExtensionType.User && this.extension.state === ExtensionState.Installed + // Remote UI or Language pack Extension + && this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && (isLanguagePackExtension(this.extension.local.manifest) || isUIExtension(this.extension.local.manifest, this.configurationService)) + // Extension does not exist in local + && !this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.localExtensionManagementServer) + && this.extensionsWorkbenchService.canInstall(this.extension) + ) { + this.enabled = true; + this.updateLabel(); + return; + } + } + + async run(): Promise { + if (!this.installing) { + this.installing = true; + this.update(); + this.extensionsWorkbenchService.open(this.extension); + alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); + if (this.extension.gallery) { + await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(this.extension.gallery); + this.installing = false; + this.update(); + } + } + } + + dispose(): void { + this.disposables = dispose(this.disposables); + super.dispose(); + } +} + export class UninstallAction extends ExtensionAction { private static readonly UninstallLabel = localize('uninstallAction', "Uninstall"); @@ -546,15 +629,15 @@ export class UpdateAction extends ExtensionAction { } } -interface IExtensionActionItemOptions extends IActionItemOptions { +interface IExtensionActionViewItemOptions extends IActionViewItemOptions { tabOnlyOnFocus?: boolean; } -export class ExtensionActionItem extends ActionItem { +export class ExtensionActionViewItem extends ActionViewItem { - protected options: IExtensionActionItemOptions; + protected options: IExtensionActionViewItemOptions; - constructor(context: any, action: IAction, options: IExtensionActionItemOptions = {}) { + constructor(context: any, action: IAction, options: IExtensionActionViewItemOptions = {}) { super(context, action, options); } @@ -597,15 +680,15 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { super(id, label, cssClass, enabled); } - private _actionItem: DropDownMenuActionItem; - createActionItem(): DropDownMenuActionItem { - this._actionItem = this.instantiationService.createInstance(DropDownMenuActionItem, this, this.tabOnlyOnFocus); - return this._actionItem; + private _actionViewItem: DropDownMenuActionViewItem; + createActionViewItem(): DropDownMenuActionViewItem { + this._actionViewItem = this.instantiationService.createInstance(DropDownMenuActionViewItem, this, this.tabOnlyOnFocus); + return this._actionViewItem; } public run({ actionGroups, disposeActionsOnHide }: { actionGroups: IAction[][], disposeActionsOnHide: boolean }): Promise { - if (this._actionItem) { - this._actionItem.showMenu(actionGroups, disposeActionsOnHide); + if (this._actionViewItem) { + this._actionViewItem.showMenu(actionGroups, disposeActionsOnHide); } return Promise.resolve(); } @@ -616,7 +699,7 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { } } -export class DropDownMenuActionItem extends ExtensionActionItem { +export class DropDownMenuActionViewItem extends ExtensionActionViewItem { private disposables: IDisposable[] = []; @@ -1211,12 +1294,19 @@ export class ReloadAction extends ExtensionAction { const isEnabled = this.extensionEnablementService.isEnabled(this.extension.local); if (runningExtension) { // Extension is running - const isSameVersionRunning = isSameExtensionRunning && this.extension.version === runningExtension.version; if (isEnabled) { - if (!isSameVersionRunning && !this.extensionService.canAddExtension(toExtensionDescription(this.extension.local))) { - this.enabled = true; - this.label = localize('reloadRequired', "Reload Required"); - this.tooltip = localize('postUpdateTooltip', "Please reload Visual Studio Code to enable the updated extension."); + if (!this.extensionService.canAddExtension(toExtensionDescription(this.extension.local))) { + if (isSameExtensionRunning) { + if (this.extension.version !== runningExtension.version) { + this.enabled = true; + this.label = localize('reloadRequired', "Reload Required"); + this.tooltip = localize('postUpdateTooltip', "Please reload Visual Studio Code to enable the updated extension."); + } + } else { + this.enabled = true; + this.label = localize('reloadRequired', "Reload Required"); + this.tooltip = localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); + } } } else { if (isSameExtensionRunning) { @@ -1234,18 +1324,31 @@ export class ReloadAction extends ExtensionAction { this.tooltip = localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); return; } - if (this.workbenchEnvironmentService.configuration.remoteAuthority + if (this.workbenchEnvironmentService.configuration.remoteAuthority) { + const uiExtension = isUIExtension(this.extension.local.manifest, this.configurationService); // Local Workspace Extension - && this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && !isUIExtension(this.extension.local.manifest, this.configurationService) - ) { - const remoteExtension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.remoteExtensionManagementServer)[0]; - // Extension exist in remote and enabled - if (remoteExtension && remoteExtension.local && this.extensionEnablementService.isEnabled(remoteExtension.local)) { - this.enabled = true; - this.label = localize('reloadRequired', "Reload Required"); - this.tooltip = localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); - alert(localize('installExtensionComplete', "Installing extension {0} is completed. Please reload Visual Studio Code to enable it.", this.extension.displayName)); - return; + if (!uiExtension && this.extension.server === this.extensionManagementServerService.localExtensionManagementServer) { + const remoteExtension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.remoteExtensionManagementServer)[0]; + // Extension exist in remote and enabled + if (remoteExtension && remoteExtension.local && this.extensionEnablementService.isEnabled(remoteExtension.local)) { + this.enabled = true; + this.label = localize('reloadRequired', "Reload Required"); + this.tooltip = localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); + alert(localize('installExtensionComplete', "Installing extension {0} is completed. Please reload Visual Studio Code to enable it.", this.extension.displayName)); + return; + } + } + // Remote UI Extension + if (uiExtension && this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) { + const localExtension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.localExtensionManagementServer)[0]; + // Extension exist in local and enabled + if (localExtension && localExtension.local && this.extensionEnablementService.isEnabled(localExtension.local)) { + this.enabled = true; + this.label = localize('reloadRequired', "Reload Required"); + this.tooltip = localize('postEnableTooltip', "Please reload Visual Studio Code to enable this extension."); + alert(localize('installExtensionComplete', "Installing extension {0} is completed. Please reload Visual Studio Code to enable it.", this.extension.displayName)); + return; + } } } } @@ -1642,7 +1745,9 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IOpenerService private readonly openerService: IOpenerService, - @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService + @IExtensionsWorkbenchService private readonly extensionWorkbenchService: IExtensionsWorkbenchService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService ) { super(id, label, 'extension-action'); this.recommendations = recommendations; @@ -1659,17 +1764,30 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { let installPromises: Promise[] = []; let model = new PagedModel(pager); for (let i = 0; i < pager.total; i++) { - installPromises.push(model.resolve(i, CancellationToken.None).then(e => { - return this.extensionWorkbenchService.install(e).then(undefined, err => { - console.error(err); - return promptDownloadManually(e.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", e.identifier.id), err, this.instantiationService, this.notificationService, this.openerService); - }); - })); + installPromises.push(model.resolve(i, CancellationToken.None).then(e => this.installExtension(e))); } return Promise.all(installPromises); }); }); } + + private async installExtension(extension: IExtension): Promise { + try { + if (extension.local && extension.gallery) { + if (isUIExtension(extension.local.manifest, this.configurationService)) { + await this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.installFromGallery(extension.gallery); + return; + } else if (this.extensionManagementServerService.remoteExtensionManagementServer) { + await this.extensionManagementServerService.remoteExtensionManagementServer.extensionManagementService.installFromGallery(extension.gallery); + return; + } + } + await this.extensionWorkbenchService.install(extension); + } catch (err) { + console.error(err); + return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService); + } + } } export class InstallRecommendedExtensionAction extends Action { @@ -2031,7 +2149,7 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio protected openWorkspaceConfigurationFile(workspaceConfigurationFile: URI): Promise { return this.getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile) - .then(content => this.getSelectionPosition(content.value, content.resource, ['extensions', 'recommendations'])) + .then(content => this.getSelectionPosition(content.value.toString(), content.resource, ['extensions', 'recommendations'])) .then(selection => this.editorService.openEditor({ resource: workspaceConfigurationFile, options: { @@ -2045,7 +2163,7 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio return this.getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile) .then(content => { const extensionIdLowerCase = extensionId.toLowerCase(); - const workspaceExtensionsConfigContent: IExtensionsConfigContent = (json.parse(content.value) || {})['extensions'] || {}; + const workspaceExtensionsConfigContent: IExtensionsConfigContent = (json.parse(content.value.toString()) || {})['extensions'] || {}; let insertInto = shouldRecommend ? workspaceExtensionsConfigContent.recommendations || [] : workspaceExtensionsConfigContent.unwantedRecommendations || []; let removeFrom = shouldRecommend ? workspaceExtensionsConfigContent.unwantedRecommendations || [] : workspaceExtensionsConfigContent.recommendations || []; @@ -2105,26 +2223,26 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio } protected getWorkspaceExtensionsConfigContent(extensionsFileResource: URI): Promise { - return Promise.resolve(this.fileService.resolveContent(extensionsFileResource)) + return Promise.resolve(this.fileService.readFile(extensionsFileResource)) .then(content => { - return (json.parse(content.value) || {})['extensions'] || {}; + return (json.parse(content.value.toString()) || {})['extensions'] || {}; }, err => ({ recommendations: [], unwantedRecommendations: [] })); } protected getWorkspaceFolderExtensionsConfigContent(extensionsFileResource: URI): Promise { - return Promise.resolve(this.fileService.resolveContent(extensionsFileResource)) + return Promise.resolve(this.fileService.readFile(extensionsFileResource)) .then(content => { - return (json.parse(content.value)); + return (json.parse(content.value.toString())); }, err => ({ recommendations: [], unwantedRecommendations: [] })); } - private getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile: URI): Promise { - return Promise.resolve(this.fileService.resolveContent(workspaceConfigurationFile)) + private getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile: URI): Promise { + return Promise.resolve(this.fileService.readFile(workspaceConfigurationFile)) .then(content => { - const workspaceRecommendations = json.parse(content.value)['extensions']; + const workspaceRecommendations = json.parse(content.value.toString())['extensions']; if (!workspaceRecommendations || !workspaceRecommendations.recommendations) { return this.jsonEditingService.write(workspaceConfigurationFile, { key: 'extensions', value: { recommendations: [] } }, true) - .then(() => this.fileService.resolveContent(workspaceConfigurationFile)); + .then(() => this.fileService.readFile(workspaceConfigurationFile)); } return content; }); @@ -2153,8 +2271,8 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio } private getOrCreateExtensionsFile(extensionsFileResource: URI): Promise<{ created: boolean, extensionsFileResource: URI, content: string }> { - return Promise.resolve(this.fileService.resolveContent(extensionsFileResource)).then(content => { - return { created: false, extensionsFileResource, content: content.value }; + return Promise.resolve(this.fileService.readFile(extensionsFileResource)).then(content => { + return { created: false, extensionsFileResource, content: content.value.toString() }; }, err => { return this.textFileService.write(extensionsFileResource, ExtensionsConfigurationInitialContent).then(() => { return { created: true, extensionsFileResource, content: ExtensionsConfigurationInitialContent }; @@ -2428,7 +2546,8 @@ export class StatusLabelAction extends Action implements IExtensionContainer { } constructor( - @IExtensionService private readonly extensionService: IExtensionService + @IExtensionService private readonly extensionService: IExtensionService, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService ) { super('extensions.action.statusLabel', '', StatusLabelAction.DISABLED_CLASS, false); } @@ -2467,7 +2586,7 @@ export class StatusLabelAction extends Action implements IExtensionContainer { }; const canRemoveExtension = () => { if (this.extension.local) { - if (runningExtensions.every(e => !areSameExtensions({ id: e.identifier.value }, this.extension.identifier))) { + if (runningExtensions.every(e => !(areSameExtensions({ id: e.identifier.value }, this.extension.identifier) && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(e.extensionLocation)))) { return true; } return this.extensionService.canRemoveExtension(toExtensionDescription(this.extension.local)); @@ -2536,30 +2655,43 @@ export class DisabledLabelAction extends ExtensionAction { updateWhenCounterExtensionChanges: boolean = true; private disposables: IDisposable[] = []; + private _runningExtensions: IExtensionDescription[] | null = null; constructor( private readonly warningAction: SystemDisabledWarningAction, @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, + @IExtensionService private readonly extensionService: IExtensionService, ) { super('extensions.disabledLabel', warningAction.tooltip, `${DisabledLabelAction.Class} hide`, false); warningAction.onDidChange(() => this.update(), this, this.disposables); + this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this, this.disposables); + this.updateRunningExtensions(); + } + + private updateRunningExtensions(): void { + this.extensionService.getExtensions().then(runningExtensions => { this._runningExtensions = runningExtensions; this.update(); }); } update(): void { this.class = `${DisabledLabelAction.Class} hide`; this.label = ''; - if (this.warningAction.enabled) { - this.enabled = true; + if (this.warningAction.tooltip) { this.class = DisabledLabelAction.Class; this.label = this.warningAction.tooltip; return; } - if (this.extension && this.extension.local && !this.extensionEnablementService.isEnabled(this.extension.local)) { - this.enabled = true; - this.class = DisabledLabelAction.Class; - this.label = localize('disabled by user', "This extension is disabled by the user."); + if (this.extension && this.extension.local && isLanguagePackExtension(this.extension.local.manifest)) { return; } + if (this.extension && this.extension.local && this._runningExtensions) { + const isEnabled = this.extensionEnablementService.isEnabled(this.extension.local); + const isExtensionRunning = this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier)); + if (!isExtensionRunning && !isEnabled && this.extensionEnablementService.canChangeEnablement(this.extension.local)) { + this.class = DisabledLabelAction.Class; + this.label = localize('disabled by user', "This extension is disabled by the user."); + return; + } + } } run(): Promise { @@ -2574,7 +2706,9 @@ export class DisabledLabelAction extends ExtensionAction { export class SystemDisabledWarningAction extends ExtensionAction { - private static readonly Class = 'disable-warning'; + private static readonly CLASS = 'system-disable'; + private static readonly WARNING_CLASS = `${SystemDisabledWarningAction.CLASS} warning`; + private static readonly INFO_CLASS = `${SystemDisabledWarningAction.CLASS} info`; updateWhenCounterExtensionChanges: boolean = true; private disposables: IDisposable[] = []; @@ -2588,7 +2722,7 @@ export class SystemDisabledWarningAction extends ExtensionAction { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionService private readonly extensionService: IExtensionService, ) { - super('extensions.install', '', `${SystemDisabledWarningAction.Class} hide`, false); + super('extensions.install', '', `${SystemDisabledWarningAction.CLASS} hide`, false); this.labelService.onDidChangeFormatters(() => this.update(), this, this.disposables); this.extensionService.onDidChangeExtensions(this.updateRunningExtensions, this, this.disposables); this.updateRunningExtensions(); @@ -2600,34 +2734,53 @@ export class SystemDisabledWarningAction extends ExtensionAction { } update(): void { - this.enabled = false; - this.class = `${SystemDisabledWarningAction.Class} hide`; + this.class = `${SystemDisabledWarningAction.CLASS} hide`; this.tooltip = ''; - if (this.extension && this.extension.local && this.extension.server && this._runningExtensions && this.workbenchEnvironmentService.configuration.remoteAuthority && this.extensionManagementServerService.remoteExtensionManagementServer) { - if ( - // Local Workspace Extension - this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && !isUIExtension(this.extension.local.manifest, this.configurationService) - ) { - this.enabled = true; - this.class = `${SystemDisabledWarningAction.Class}`; - this.tooltip = localize('disabled workspace Extension', "This extension from {0} server is disabled because it cannot run in a window connected to the remote server.", this.getServerLabel(this.extensionManagementServerService.localExtensionManagementServer)); - if (!this.extensionsWorkbenchService.local.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.extensionManagementServerService.remoteExtensionManagementServer) - && this.extensionsWorkbenchService.canInstall(this.extension) - ) { - // Extension does not exist in remote - this.tooltip = `${this.tooltip} ${localize('Install in remote server', "Install it in {0} server to enable.", this.getServerLabel(this.extensionManagementServerService.remoteExtensionManagementServer))}`; - } + if ( + !this.extension || + !this.extension.local || + !this.extension.server || + !this._runningExtensions || + !this.workbenchEnvironmentService.configuration.remoteAuthority || + !this.extensionManagementServerService.remoteExtensionManagementServer || + this.extension.state !== ExtensionState.Installed + ) { + return; + } + if (isLanguagePackExtension(this.extension.local.manifest)) { + if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server !== this.extension.server)) { + this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; + this.tooltip = this.extension.server === this.extensionManagementServerService.localExtensionManagementServer + ? localize('Install language pack also in remote server', "Install the language pack extension on '{0}' to enable it also there.", this.getServerLabel(this.extensionManagementServerService.remoteExtensionManagementServer)) + : localize('Install language pack also locally', "Install the language pack extension locally to enable it also there."); + } + return; + } + const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier))[0]; + const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation) : null; + const localExtension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier))[0]; + const localExtensionServer = localExtension ? localExtension.server : null; + if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && !isUIExtension(this.extension.local.manifest, this.configurationService)) { + if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { + this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; + this.tooltip = localize('disabled locally', "Extension is enabled on '{0}' and disabled locally.", this.getServerLabel(this.extensionManagementServerService.remoteExtensionManagementServer)); return; } - const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value }, this.extension.identifier))[0]; - const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation) : null; - if ( - // Not same as running extension - runningExtensionServer && this.extension.server !== runningExtensionServer - ) { - this.enabled = true; - this.class = `${SystemDisabledWarningAction.Class}`; - this.tooltip = localize('disabled because running in another server', "This extension from {0} server is disabled because another instance of same extension from {1} server is enabled.", this.getServerLabel(this.extension.server), this.getServerLabel(runningExtensionServer)); + if (localExtensionServer !== this.extensionManagementServerService.remoteExtensionManagementServer) { + this.class = `${SystemDisabledWarningAction.WARNING_CLASS}`; + this.tooltip = localize('Install in remote server', "Install the extension on '{0}' to enable.", this.getServerLabel(this.extensionManagementServerService.remoteExtensionManagementServer)); + return; + } + } + if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && isUIExtension(this.extension.local.manifest, this.configurationService)) { + if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { + this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; + this.tooltip = localize('disabled remotely', "Extension is enabled locally and disabled on '{0}'.", this.getServerLabel(this.extensionManagementServerService.remoteExtensionManagementServer)); + return; + } + if (localExtensionServer !== this.extensionManagementServerService.localExtensionManagementServer) { + this.class = `${SystemDisabledWarningAction.WARNING_CLASS}`; + this.tooltip = localize('Install in local server', "Install the extension locally to enable."); return; } } @@ -2840,7 +2993,7 @@ export class InstallVSIXAction extends Action { for (const extension of extensions) { const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local))); const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Visual Studio Code to complete installing the extension {0}.", extension.identifier.id) - : localize('InstallVSIXAction.success', "Installing the extension {0} is completed.", extension.identifier.id); + : localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.identifier.id); const actions = requireReload ? [{ label: localize('InstallVSIXAction.reloadNow', "Reload Now"), run: () => this.windowService.reloadWindow() diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts index 1e95fb513b3..aac51bff673 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts @@ -13,12 +13,13 @@ import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionItem, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, DisabledLabelAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; +import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionViewItem, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, DisabledLabelAction, LocalInstallAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { Label, RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, TooltipWidget } from 'vs/workbench/contrib/extensions/electron-browser/extensionsWidgets'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; export interface IExtensionsViewState { onFocus: Event; @@ -65,7 +66,7 @@ export class Renderer implements IPagedRenderer { const element = append(root, $('.extension')); const iconContainer = append(element, $('.icon-container')); const icon = append(iconContainer, $('img.icon')); - const badgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, iconContainer); + const iconRemoteBadgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, iconContainer, false); const details = append(element, $('.details')); const headerContainer = append(details, $('.header-container')); const header = append(headerContainer, $('.header')); @@ -73,36 +74,40 @@ export class Renderer implements IPagedRenderer { const version = append(header, $('span.version')); const installCount = append(header, $('span.install-count')); const ratings = append(header, $('span.ratings')); + const headerRemoteBadgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, header, false); const description = append(details, $('.description.ellipsis')); const footer = append(details, $('.footer')); const author = append(footer, $('.author.ellipsis')); const actionbar = new ActionBar(footer, { animated: false, - actionItemProvider: (action: Action) => { + actionViewItemProvider: (action: Action) => { if (action.id === ManageExtensionAction.ID) { - return (action).createActionItem(); + return (action).createActionViewItem(); } - return new ExtensionActionItem(null, action, actionOptions); + return new ExtensionActionViewItem(null, action, actionOptions); } }); actionbar.onDidRun(({ error }) => error && this.notificationService.error(error)); const systemDisabledWarningAction = this.instantiationService.createInstance(SystemDisabledWarningAction); + const reloadAction = this.instantiationService.createInstance(ReloadAction); const actions = [ this.instantiationService.createInstance(StatusLabelAction), this.instantiationService.createInstance(UpdateAction), - this.instantiationService.createInstance(ReloadAction), + reloadAction, this.instantiationService.createInstance(InstallAction), this.instantiationService.createInstance(RemoteInstallAction), + this.instantiationService.createInstance(LocalInstallAction), this.instantiationService.createInstance(MaliciousStatusLabelAction, false), systemDisabledWarningAction, this.instantiationService.createInstance(ManageExtensionAction) ]; const disabledLabelAction = this.instantiationService.createInstance(DisabledLabelAction, systemDisabledWarningAction); - const tooltipWidget = this.instantiationService.createInstance(TooltipWidget, root, disabledLabelAction, recommendationWidget); + const tooltipWidget = this.instantiationService.createInstance(TooltipWidget, root, disabledLabelAction, recommendationWidget, reloadAction); const widgets = [ recommendationWidget, - badgeWidget, + iconRemoteBadgeWidget, + headerRemoteBadgeWidget, tooltipWidget, this.instantiationService.createInstance(Label, version, (e: IExtension) => e.version), this.instantiationService.createInstance(InstallCountWidget, installCount, true), @@ -148,7 +153,7 @@ export class Renderer implements IPagedRenderer { const updateEnablement = async () => { const runningExtensions = await this.extensionService.getExtensions(); - if (extension.local) { + if (extension.local && !isLanguagePackExtension(extension.local.manifest)) { const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value }, extension.identifier))[0]; const isSameExtensionRunning = runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation); toggleClass(data.root, 'disabled', !isSameExtensionRunning); @@ -183,13 +188,13 @@ export class Renderer implements IPagedRenderer { this.extensionViewState.onFocus(e => { if (areSameExtensions(extension.identifier, e.identifier)) { - data.actionbar.items.forEach(item => (item).setFocus(true)); + data.actionbar.viewItems.forEach(item => (item).setFocus(true)); } }, this, data.extensionDisposables); this.extensionViewState.onBlur(e => { if (areSameExtensions(extension.identifier, e.identifier)) { - data.actionbar.items.forEach(item => (item).setFocus(false)); + data.actionbar.viewItems.forEach(item => (item).setFocus(false)); } }, this, data.extensionDisposables); } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts index c985b6c5f7e..5658c7df148 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts @@ -54,6 +54,10 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { RemoteAuthorityContext } from 'vs/workbench/browser/contextkeys'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { ILabelService } from 'vs/platform/label/common/label'; interface SearchInputEvent extends Event { target: HTMLInputElement; @@ -89,7 +93,9 @@ const viewIdNameMappings: { [id: string]: string } = { export class ExtensionsViewletViewsContribution implements IWorkbenchContribution { constructor( - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @ILabelService private readonly labelService: ILabelService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { this.registerViews(); } @@ -137,7 +143,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio id, name: viewIdNameMappings[id], ctorDescriptor: { ctor: EnabledExtensionsView }, - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), ContextKeyExpr.not('config.extensions.showInstalledExtensionsByDefault')), + when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteAuthorityContext.isEqualTo('')), weight: 40, canToggleVisibility: true, order: 1 @@ -152,7 +158,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio id, name: viewIdNameMappings[id], ctorDescriptor: { ctor: DisabledExtensionsView }, - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), ContextKeyExpr.not('config.extensions.showInstalledExtensionsByDefault')), + when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteAuthorityContext.isEqualTo('')), weight: 10, canToggleVisibility: true, order: 3, @@ -175,23 +181,33 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio } private createExtensionsViewDescriptorsForServer(server: IExtensionManagementServer): IViewDescriptor[] { + const getViewName = (viewTitle: string, server: IExtensionManagementServer): string => { + const serverLabel = this.workbenchEnvironmentService.configuration.remoteAuthority === server.authority ? this.labelService.getHostLabel(REMOTE_HOST_SCHEME, server.authority) || server.label : server.label; + if (viewTitle && this.workbenchEnvironmentService.configuration.remoteAuthority) { + return `${serverLabel} - ${viewTitle}`; + } + return viewTitle ? viewTitle : serverLabel; + }; + const getInstalledViewName = (): string => getViewName(localize('installed', "Installed"), server); + const getOutdatedViewName = (): string => getViewName(localize('outdated', "Outdated"), server); + const onDidChangeServerLabel: EventOf = this.workbenchEnvironmentService.configuration.remoteAuthority ? EventOf.map(this.labelService.onDidChangeFormatters, () => undefined) : EventOf.None; return [{ id: `extensions.${server.authority}.installed`, - name: localize('installed', "Installed"), - ctorDescriptor: { ctor: ServerExtensionsView, arguments: [server] }, + get name() { return getInstalledViewName(); }, + ctorDescriptor: { ctor: ServerExtensionsView, arguments: [server, EventOf.map(onDidChangeServerLabel, () => getInstalledViewName())] }, when: ContextKeyExpr.and(ContextKeyExpr.has('searchInstalledExtensions')), weight: 100 }, { id: `extensions.${server.authority}.outdated`, - name: localize('outdated', "Outdated"), - ctorDescriptor: { ctor: ServerExtensionsView, arguments: [server] }, + get name() { return getOutdatedViewName(); }, + ctorDescriptor: { ctor: ServerExtensionsView, arguments: [server, EventOf.map(onDidChangeServerLabel, () => getOutdatedViewName())] }, when: ContextKeyExpr.and(ContextKeyExpr.has('searchOutdatedExtensions')), weight: 100 }, { id: `extensions.${server.authority}.default`, - name: localize('installed', "Installed"), - ctorDescriptor: { ctor: ServerExtensionsView, arguments: [server] }, - when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), ContextKeyExpr.has('config.extensions.showInstalledExtensionsByDefault')), + get name() { return getInstalledViewName(); }, + ctorDescriptor: { ctor: ServerExtensionsView, arguments: [server, EventOf.map(onDidChangeServerLabel, () => getInstalledViewName())] }, + when: ContextKeyExpr.and(ContextKeyExpr.has('defaultExtensionViews'), ContextKeyExpr.has('hasInstalledExtensions'), RemoteAuthorityContext.notEqualsTo('')), weight: 40, order: 1 }]; @@ -223,7 +239,6 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio ctorDescriptor: { ctor: RecommendedExtensionsView }, when: ContextKeyExpr.has('recommendedExtensions'), weight: 50, - canToggleVisibility: true, order: 2 }; } @@ -238,7 +253,6 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio ctorDescriptor: { ctor: WorkspaceRecommendedExtensionsView }, when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), ContextKeyExpr.has('nonEmptyWorkspace')), weight: 50, - canToggleVisibility: true, order: 1 }; } @@ -251,7 +265,6 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio ctorDescriptor: { ctor: EnabledExtensionsView }, when: ContextKeyExpr.and(ContextKeyExpr.has('searchEnabledExtensions')), weight: 40, - canToggleVisibility: true, order: 1 }; } @@ -264,7 +277,6 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio ctorDescriptor: { ctor: DisabledExtensionsView }, when: ContextKeyExpr.and(ContextKeyExpr.has('searchDisabledExtensions')), weight: 10, - canToggleVisibility: true, order: 3, collapsed: true }; @@ -277,8 +289,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio name: viewIdNameMappings[id], ctorDescriptor: { ctor: BuiltInExtensionsView }, when: ContextKeyExpr.has('searchBuiltInExtensions'), - weight: 100, - canToggleVisibility: true + weight: 100 }; } @@ -289,8 +300,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio name: viewIdNameMappings[id], ctorDescriptor: { ctor: BuiltInThemesExtensionsView }, when: ContextKeyExpr.has('searchBuiltInExtensions'), - weight: 100, - canToggleVisibility: true + weight: 100 }; } @@ -301,8 +311,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio name: viewIdNameMappings[id], ctorDescriptor: { ctor: BuiltInBasicsExtensionsView }, when: ContextKeyExpr.has('searchBuiltInExtensions'), - weight: 100, - canToggleVisibility: true + weight: 100 }; } } @@ -506,7 +515,6 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio private doSearch(): Promise { const value = this.normalizedQuery(); - this.defaultViewsContextKey.set(!value); const isRecommendedExtensionsQuery = ExtensionsListView.isRecommendedExtensionsQuery(value); this.searchInstalledExtensionsContextKey.set(ExtensionsListView.isInstalledExtensionsQuery(value)); this.searchOutdatedExtensionsContextKey.set(ExtensionsListView.isOutdatedExtensionsQuery(value)); @@ -516,6 +524,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery); this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery); this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY); + this.defaultViewsContextKey.set(!value); return this.progress(Promise.all(this.panels.map(view => (view).show(this.normalizedQuery()) diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts index 6b78077ce2b..623c29529f2 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts @@ -16,7 +16,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { append, $, toggleClass } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Delegate, Renderer, IExtensionsViewState } from 'vs/workbench/contrib/extensions/electron-browser/extensionsList'; -import { IExtension, IExtensionsWorkbenchService } from '../common/extensions'; +import { IExtension, IExtensionsWorkbenchService, ExtensionState } from '../common/extensions'; import { Query } from '../common/extensionQuery'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -41,13 +41,11 @@ import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IAction } from 'vs/base/common/actions'; -import { ExtensionType, ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, ExtensionIdentifier, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import product from 'vs/platform/product/node/product'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; class ExtensionsViewState extends Disposable implements IExtensionsViewState { @@ -97,7 +95,7 @@ export class ExtensionsListView extends ViewletPanel { @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IExperimentService private readonly experimentService: IExperimentService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, - @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService + @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService); this.server = options.server; @@ -340,7 +338,21 @@ export class ExtensionsListView extends ViewletPanel { const isE1Running = running1 && this.extensionManagementServerService.getExtensionManagementServer(running1.extensionLocation) === e1.server; const running2 = runningExtensionsById.get(ExtensionIdentifier.toKey(e2.identifier.id)); const isE2Running = running2 && this.extensionManagementServerService.getExtensionManagementServer(running2.extensionLocation) === e2.server; - if ((isE1Running && isE2Running) || (!isE1Running && !isE2Running)) { + if ((isE1Running && isE2Running)) { + return e1.displayName.localeCompare(e2.displayName); + } + const isE1LanguagePackExtension = e1.local && isLanguagePackExtension(e1.local.manifest); + const isE2LanguagePackExtension = e2.local && isLanguagePackExtension(e2.local.manifest); + if (!isE1Running && !isE2Running) { + if (isE1LanguagePackExtension) { + return -1; + } + if (isE2LanguagePackExtension) { + return 1; + } + return e1.displayName.localeCompare(e2.displayName); + } + if ((isE1Running && isE2LanguagePackExtension) || (isE2Running && isE1LanguagePackExtension)) { return e1.displayName.localeCompare(e2.displayName); } return isE1Running ? -1 : 1; @@ -816,18 +828,11 @@ export class ExtensionsListView extends ViewletPanel { } } -function getViewTitleForServer(viewTitle: string, server: IExtensionManagementServer, labelService: ILabelService, workbenchEnvironmentService: IWorkbenchEnvironmentService): string { - const serverLabel = workbenchEnvironmentService.configuration.remoteAuthority === server.authority ? labelService.getHostLabel(REMOTE_HOST_SCHEME, server.authority) || server.label : server.label; - if (viewTitle && workbenchEnvironmentService.configuration.remoteAuthority) { - return `${serverLabel} - ${viewTitle}`; - } - return viewTitle ? viewTitle : serverLabel; -} - export class ServerExtensionsView extends ExtensionsListView { constructor( server: IExtensionManagementServer, + onDidChangeTitle: Event, options: ExtensionsListViewOptions, @INotificationService notificationService: INotificationService, @IKeybindingService keybindingService: IKeybindingService, @@ -844,15 +849,11 @@ export class ServerExtensionsView extends ExtensionsListView { @IExperimentService experimentService: IExperimentService, @IWorkbenchThemeService workbenchThemeService: IWorkbenchThemeService, @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @ILabelService labelService: ILabelService, - @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService ) { - const viewTitle = options.title; - options.title = getViewTitleForServer(viewTitle, server, labelService, workbenchEnvironmentService); options.server = server; super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, modeService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService); - this.disposables.push(labelService.onDidChangeFormatters(() => this.updateTitle(getViewTitleForServer(viewTitle, server, labelService, workbenchEnvironmentService)))); + this.disposables.push(onDidChangeTitle(title => this.updateTitle(title))); } async show(query: string): Promise> { @@ -993,6 +994,12 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { private getRecommendationsToInstall(): Promise { return this.tipsService.getWorkspaceRecommendations() - .then(recommendations => recommendations.filter(({ extensionId }) => !this.extensionsWorkbenchService.local.some(i => areSameExtensions({ id: extensionId }, i.identifier)))); + .then(recommendations => recommendations.filter(({ extensionId }) => { + const extension = this.extensionsWorkbenchService.local.filter(i => areSameExtensions({ id: extensionId }, i.identifier))[0]; + if (!extension || !extension.local || extension.state !== ExtensionState.Installed) { + return true; + } + return isUIExtension(extension.local.manifest, this.configurationService) ? extension.server !== this.extensionManagementServerService.localExtensionManagementServer : extension.server !== this.extensionManagementServerService.remoteExtensionManagementServer; + })); } } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsWidgets.ts index 52380d12554..c34a342320f 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsWidgets.ts @@ -5,19 +5,19 @@ import 'vs/css!./media/extensionsWidgets'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; -import { IExtension, IExtensionsWorkbenchService, IExtensionContainer } from '../common/extensions'; +import { IExtension, IExtensionsWorkbenchService, IExtensionContainer, ExtensionState } from '../common/extensions'; import { append, $, addClass } from 'vs/base/browser/dom'; import * as platform from 'vs/base/common/platform'; import { localize } from 'vs/nls'; import { IExtensionManagementServerService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ILabelService } from 'vs/platform/label/common/label'; -import { extensionButtonProminentBackground, extensionButtonProminentForeground, DisabledLabelAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; +import { extensionButtonProminentBackground, extensionButtonProminentForeground, DisabledLabelAction, ReloadAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; -import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { EXTENSION_BADGE_REMOTE_BACKGROUND, EXTENSION_BADGE_REMOTE_FOREGROUND } from 'vs/workbench/common/theme'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { Emitter, Event } from 'vs/base/common/event'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export abstract class ExtensionWidget extends Disposable implements IExtensionContainer { private _extension: IExtension; @@ -147,27 +147,46 @@ export class TooltipWidget extends ExtensionWidget { constructor( private readonly parent: HTMLElement, - private readonly extensionLabelAction: DisabledLabelAction, - private readonly recommendationWidget: RecommendationWidget + private readonly disabledLabelAction: DisabledLabelAction, + private readonly recommendationWidget: RecommendationWidget, + private readonly reloadAction: ReloadAction, + @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, + @ILabelService private readonly labelService: ILabelService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { super(); - this._register(this.extensionLabelAction.onDidChange(() => this.render())); - this._register(this.recommendationWidget.onDidChangeTooltip(() => this.render())); + this._register(Event.any( + this.disabledLabelAction.onDidChange, + this.reloadAction.onDidChange, + this.recommendationWidget.onDidChangeTooltip, + this.labelService.onDidChangeFormatters + )(() => this.render())); } render(): void { this.parent.title = ''; this.parent.removeAttribute('aria-label'); + this.parent.title = this.getTooltip(); if (this.extension) { - const title = this.getTitle(); - this.parent.title = title; - this.parent.setAttribute('aria-label', localize('extension-arialabel', "{0}. {1} Press enter for extension details.", this.extension.displayName)); + this.parent.setAttribute('aria-label', localize('extension-arialabel', "{0}. Press enter for extension details.", this.extension.displayName)); } } - private getTitle(): string { - if (this.extensionLabelAction.enabled) { - return this.extensionLabelAction.label; + private getTooltip(): string { + if (!this.extension) { + return ''; + } + if (this.reloadAction.enabled) { + return this.reloadAction.tooltip; + } + if (this.disabledLabelAction.label) { + return this.disabledLabelAction.label; + } + if (this.extension.local && this.extension.state === ExtensionState.Installed) { + if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) { + return localize('extension enabled on remote', "Extension is enabled on '{0}'", this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.workbenchEnvironmentService.configuration.remoteAuthority)); + } + return localize('extension enabled locally', "Extension is enabled locally."); } return this.recommendationWidget.tooltip; } @@ -235,30 +254,31 @@ export class RecommendationWidget extends ExtensionWidget { } - export class RemoteBadgeWidget extends ExtensionWidget { - private element: HTMLElement | null; + private remoteBadge: RemoteBadge | null; private disposables: IDisposable[] = []; + private element: HTMLElement; + constructor( - private parent: HTMLElement, - @ILabelService private readonly labelService: ILabelService, - @IThemeService private readonly themeService: IThemeService, + parent: HTMLElement, + private readonly tooltip: boolean, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); + this.element = append(parent, $('.extension-remote-badge-container')); this.render(); this._register(toDisposable(() => this.clear())); } private clear(): void { - if (this.element) { - this.parent.removeChild(this.element); + if (this.remoteBadge) { + this.element.removeChild(this.remoteBadge.element); + this.remoteBadge.dispose(); } - this.element = null; + this.remoteBadge = null; this.disposables = dispose(this.disposables); } @@ -268,30 +288,57 @@ export class RemoteBadgeWidget extends ExtensionWidget { return; } if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer) { - this.element = append(this.parent, $('div.extension-remote-badge')); - append(this.element, $('span.octicon.octicon-remote')); + this.remoteBadge = this.instantiationService.createInstance(RemoteBadge, this.tooltip); + append(this.element, this.remoteBadge.element); + } + } - const applyBadgeStyle = () => { - if (!this.element) { - return; - } - const bgColor = this.themeService.getTheme().getColor(STATUS_BAR_HOST_NAME_BACKGROUND); - const fgColor = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY ? this.themeService.getTheme().getColor(STATUS_BAR_NO_FOLDER_FOREGROUND) : this.themeService.getTheme().getColor(STATUS_BAR_FOREGROUND); - this.element.style.backgroundColor = bgColor ? bgColor.toString() : ''; - this.element.style.color = fgColor ? fgColor.toString() : ''; - }; - applyBadgeStyle(); - this.themeService.onThemeChange(applyBadgeStyle, this, this.disposables); - this.workspaceContextService.onDidChangeWorkbenchState(applyBadgeStyle, this, this.disposables); + dispose(): void { + if (this.remoteBadge) { + this.remoteBadge.dispose(); + } + super.dispose(); + } +} +class RemoteBadge extends Disposable { + + readonly element: HTMLElement; + + constructor( + private readonly tooltip: boolean, + @ILabelService private readonly labelService: ILabelService, + @IThemeService private readonly themeService: IThemeService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + ) { + super(); + this.element = $('div.extension-remote-badge'); + this.render(); + } + + private render(): void { + append(this.element, $('span.octicon.octicon-remote')); + + const applyBadgeStyle = () => { + if (!this.element) { + return; + } + const bgColor = this.themeService.getTheme().getColor(EXTENSION_BADGE_REMOTE_BACKGROUND); + const fgColor = this.themeService.getTheme().getColor(EXTENSION_BADGE_REMOTE_FOREGROUND); + this.element.style.backgroundColor = bgColor ? bgColor.toString() : ''; + this.element.style.color = fgColor ? fgColor.toString() : ''; + }; + applyBadgeStyle(); + this._register(this.themeService.onThemeChange(() => applyBadgeStyle())); + + if (this.tooltip) { const updateTitle = () => { if (this.element) { this.element.title = localize('remote extension title', "Extension in {0}", this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.environmentService.configuration.remoteAuthority)); } }; - this.labelService.onDidChangeFormatters(() => updateTitle(), this, this.disposables); + this._register(this.labelService.onDidChangeFormatters(() => updateTitle())); updateTitle(); } } - -} +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionActions.css index 886155dccb4..c41cf354246 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionActions.css @@ -7,6 +7,9 @@ padding: 0 5px; outline-offset: 2px; line-height: initial; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } .monaco-action-bar .action-item .action-label.clear-extensions { @@ -35,7 +38,7 @@ .monaco-action-bar .action-item.disabled .action-label.extension-action.extension-editor-dropdown-action, .monaco-action-bar .action-item.disabled .action-label.extension-action.reload, .monaco-action-bar .action-item.disabled .action-label.disable-status.hide, -.monaco-action-bar .action-item.disabled .action-label.disable-warning.hide, +.monaco-action-bar .action-item.disabled .action-label.system-disable.hide, .monaco-action-bar .action-item.disabled .action-label.extension-status-label.hide, .monaco-action-bar .action-item.disabled .action-label.malicious-status.not-malicious { display: none; @@ -67,24 +70,33 @@ padding-left: 0; } -.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label.disable-warning, -.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.disable-warning { - cursor: default; - margin: 0.1em; +.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .action-label.system-disable, +.extensions-viewlet>.extensions .extension>.details>.footer>.monaco-action-bar .action-item .action-label.system-disable { + margin: 0.15em; } -.monaco-action-bar .action-item .action-label.disable-warning.icon { +.monaco-action-bar .action-item .action-label.system-disable.icon { opacity: 1; height: 18px; width: 10px; - background: url('status-warning.svg') center center no-repeat; - margin-top: 0.15em } -.vs-dark .monaco-action-bar .action-item .action-label.disable-warning.icon { +.monaco-action-bar .action-item .action-label.system-disable.warning.icon { + background: url('status-warning.svg') center center no-repeat; +} + +.vs-dark .monaco-action-bar .action-item .action-label.system-disable.warning.icon { background: url('status-warning-inverse.svg') center center no-repeat; } +.monaco-action-bar .action-item .action-label.system-disable.info.icon { + background: url('status-info.svg') center center no-repeat; +} + +.vs-dark .monaco-action-bar .action-item .action-label.system-disable.info.icon { + background: url('status-info-inverse.svg') center center no-repeat; +} + .extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.extension-status-label, .extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.disable-status, .extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.malicious-status { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionEditor.css index 8f9e710b9dd..3379ec0d6b7 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionEditor.css @@ -35,7 +35,19 @@ .extension-editor > .header > .icon-container .extension-remote-badge { position: absolute; right: 0px; - top: 94px; + top: 88px; + width: 38px; + height: 38px; + line-height: 38px; + border-radius: 20px; + text-align: center; + display: flex; + align-items: center; + justify-content: center; +} + +.extension-editor > .header > .icon-container .extension-remote-badge .octicon { + font-size: 28px; } .extension-editor > .header > .details { @@ -135,6 +147,9 @@ padding: 1px 6px; } +.extension-editor > .header > .details > .actions > .monaco-action-bar > .actions-container > .action-item > .extension-action { + max-width: 300px; +} .extension-editor > .header > .details > .subtext-container { display: block; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsViewlet.css index 020d8a4bcfa..ca9ddecdcbb 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsViewlet.css @@ -102,10 +102,36 @@ object-fit: contain; } -.extensions-viewlet > .extensions .monaco-list-row > .extension > .icon-container > .extension-remote-badge { +.extensions-viewlet > .extensions .monaco-list-row > .extension > .icon-container .extension-remote-badge { position: absolute; right: 5px; bottom: 5px; + width: 22px; + height: 22px; + line-height: 22px; + border-radius: 20px; + display: flex; + align-items: center; + justify-content: center; +} + +.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header > .extension-remote-badge-container { + margin-left: 6px; +} + +.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header .extension-remote-badge { + width: 14px; + height: 14px; + line-height: 14px; + border-radius: 20px; + text-align: center; + display: flex; + align-items: center; + justify-content: center; +} + +.extensions-viewlet > .extensions .monaco-list-row > .extension > .details > .header-container > .header .extension-remote-badge > .octicon { + font-size: 12px; } .extensions-viewlet.narrow > .extensions .extension > .icon-container, @@ -146,10 +172,13 @@ opacity: 0.85; font-size: 80%; padding-left: 6px; - flex: 1; min-width: fit-content; } +.extensions-viewlet:not(.narrow) > .extensions .extension > .details > .header-container > .header > .version { + flex: 1; +} + .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count:not(:empty) { font-size: 80%; margin: 0 6px; @@ -164,6 +193,7 @@ text-align: right; } +.extensions-viewlet:not(.narrow) > .extensions .extension > .details > .header-container > .header > .extension-remote-badge-container, .extensions-viewlet.narrow > .extensions .extension > .details > .header-container > .header > .ratings, .extensions-viewlet.narrow > .extensions .extension > .details > .header-container > .header > .install-count { display: none; @@ -198,6 +228,14 @@ flex-wrap: wrap-reverse; } +.extensions-viewlet > .extensions .extension > .details > .footer > .monaco-action-bar > .actions-container .extension-action { + max-width: 150px; +} + +.extensions-viewlet.narrow > .extensions .extension > .details > .footer > .monaco-action-bar > .actions-container .extension-action { + max-width: 100px; +} + .extensions-viewlet > .extensions .extension > .details > .footer > .monaco-action-bar .action-label { margin-top: 0.3em; margin-left: 0.3em; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsWidgets.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsWidgets.css index 7146eb87f31..7ddd1d4c880 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsWidgets.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsWidgets.css @@ -46,12 +46,4 @@ .extension-ratings.small > .count { margin-left: 2px; -} - -.extension-remote-badge { - width: 22px; - height: 22px; - line-height: 22px; - border-radius: 20px; - text-align: center; } \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/status-info-inverse.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/status-info-inverse.svg new file mode 100644 index 00000000000..d38c363e0e4 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/status-info-inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/status-info.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/status-info.svg new file mode 100644 index 00000000000..6e2e22f67bc --- /dev/null +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/status-info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 5784b159f3f..d5666b2f5f8 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -379,12 +379,12 @@ export class RuntimeExtensionsEditor extends BaseEditor { if (element.description.extensionLocation.scheme !== 'file') { const el = $('span'); - el.innerHTML = renderOcticons(`$(rss) ${element.description.extensionLocation.authority}`); + el.innerHTML = renderOcticons(`$(remote) ${element.description.extensionLocation.authority}`); data.msgContainer.appendChild(el); const hostLabel = this._labelService.getHostLabel(REMOTE_HOST_SCHEME, this._environmentService.configuration.remoteAuthority); if (hostLabel) { - el.innerHTML = renderOcticons(`$(rss) ${hostLabel}`); + el.innerHTML = renderOcticons(`$(remote) ${hostLabel}`); } } diff --git a/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts index 417ca8bfd76..25c72b03649 100644 --- a/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts @@ -34,7 +34,7 @@ import * as resources from 'vs/base/common/resources'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IFileService } from 'vs/platform/files/common/files'; -import { IExtensionManifest, ExtensionType, ExtensionIdentifierWithVersion, IExtension as IPlatformExtension } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest, ExtensionType, ExtensionIdentifierWithVersion, IExtension as IPlatformExtension, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; interface IExtensionStateProvider { (extension: Extension): T; @@ -236,7 +236,7 @@ class Extension implements IExtension { } if (this.local && this.local.readmeUrl) { - return this.fileService.resolveContent(this.local.readmeUrl, { encoding: 'utf8' }).then(content => content.value); + return this.fileService.readFile(this.local.readmeUrl).then(content => content.value.toString()); } if (this.type === ExtensionType.System) { @@ -277,7 +277,7 @@ ${this.description} return Promise.reject(new Error('not available')); } - return this.fileService.resolveContent(changelogUrl, { encoding: 'utf8' }).then(content => content.value); + return this.fileService.readFile(changelogUrl).then(content => content.value.toString()); } get dependencies(): string[] { @@ -584,15 +584,19 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } get local(): IExtension[] { - const result = [...this.localExtensions.local]; - if (!this.remoteExtensions) { - return result; - } - result.push(...this.remoteExtensions.local); + const result = [...this.installed]; const byId = groupByExtension(result, r => r.identifier); return byId.reduce((result, extensions) => { result.push(this.getPrimaryExtension(extensions)); return result; }, []); } + get installed(): IExtension[] { + const result = [...this.localExtensions.local]; + if (this.remoteExtensions) { + result.push(...this.remoteExtensions.local); + } + return result; + } + get outdated(): IExtension[] { const allLocal = [...this.localExtensions.local]; if (this.remoteExtensions) { @@ -812,7 +816,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension } return this.installWithProgress(async () => { - await this.extensionService.installFromGallery(gallery); + const extensionService = extension.server && extension.local && !isLanguagePackExtension(extension.local.manifest) ? extension.server.extensionManagementService : this.extensionService; + await extensionService.installFromGallery(gallery); this.checkAndEnableDisabledDependencies(gallery.identifier); return this.local.filter(local => areSameExtensions(local.identifier, gallery.identifier))[0]; }, gallery.displayName); @@ -854,7 +859,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return Promise.reject(new Error(nls.localize('incompatible', "Unable to install extension '{0}' with version '{1}' as it is not compatible with VS Code.", extension.gallery!.identifier.id, version))); } return this.installWithProgress(async () => { - await this.extensionService.installFromGallery(gallery); + const extensionService = extension.server && extension.local && !isLanguagePackExtension(extension.local.manifest) ? extension.server.extensionManagementService : this.extensionService; + await extensionService.installFromGallery(gallery); if (extension.latestVersion !== version) { this.ignoreAutoUpdate(new ExtensionIdentifierWithVersion(gallery.identifier, version)); } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index ac00b73e0f9..32d569b0b57 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -21,12 +21,11 @@ import { Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestTextResourceConfigurationService, TestContextService, TestLifecycleService, TestEnvironmentService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestLifecycleService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; -import { LegacyFileService } from 'vs/workbench/services/files/node/fileService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IPager } from 'vs/base/common/paging'; import { assign } from 'vs/base/common/objects'; @@ -47,10 +46,10 @@ import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/ele import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { FileService } from 'vs/workbench/services/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; -import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; import { IFileService } from 'vs/platform/files/common/files'; const mockExtensionGallery: IGalleryExtension[] = [ @@ -283,14 +282,8 @@ suite('ExtensionsTipsService Test', () => { const myWorkspace = testWorkspace(URI.from({ scheme: 'file', path: folderDir })); workspaceService = new TestContextService(myWorkspace); instantiationService.stub(IWorkspaceContextService, workspaceService); - const fileService = new FileService2(new NullLogService()); + const fileService = new FileService(new NullLogService()); fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.setLegacyService(new LegacyFileService( - fileService, - workspaceService, - TestEnvironmentService, - new TestTextResourceConfigurationService() - )); instantiationService.stub(IFileService, fileService); } diff --git a/src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.contribution.ts index f380af8cd5a..4facc9d8566 100644 --- a/src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.contribution.ts @@ -94,7 +94,7 @@ CommandsRegistry.registerCommand({ const directoriesToOpen = distinct(stats.filter(data => data.success).map(({ stat }) => stat!.isDirectory ? stat!.resource.fsPath : paths.dirname(stat!.resource.fsPath))); return directoriesToOpen.map(dir => { if (configurationService.getValue().terminal.explorerKind === 'integrated') { - const instance = integratedTerminalService.createTerminal({ cwd: dir }, true); + const instance = integratedTerminalService.createTerminal({ cwd: dir }); if (instance && (resources.length === 1 || !resource || dir === resource.fsPath || dir === paths.dirname(resource.fsPath))) { integratedTerminalService.setActiveInstance(instance); integratedTerminalService.showPanel(true); diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index 854da7251dc..09d92f8bf77 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -12,9 +12,10 @@ import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { URI } from 'vs/base/common/uri'; import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; -import { IFileService } from 'vs/platform/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; /** * An implementation of editor for binary files like images. @@ -26,10 +27,11 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { constructor( @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, - @IFileService fileService: IFileService, @IWindowsService private readonly windowsService: IWindowsService, @IEditorService private readonly editorService: IEditorService, - @IStorageService storageService: IStorageService + @IStorageService storageService: IStorageService, + @ITextFileService textFileService: ITextFileService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, ) { super( BinaryFileEditor.ID, @@ -39,8 +41,9 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { }, telemetryService, themeService, - fileService, - storageService + textFileService, + environmentService, + storageService, ); } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index d3df2932d1a..174c803f20e 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -10,7 +10,7 @@ import { isValidBasename } from 'vs/base/common/extpath'; import { basename } from 'vs/base/common/resources'; import { Action } from 'vs/base/common/actions'; import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; -import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileEditorModel, ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; import { EditorOptions, TextEditorOptions, IEditorCloseEvent } from 'vs/workbench/common/editor'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; @@ -170,7 +170,7 @@ export class TextFileEditor extends BaseTextEditor { // In case we tried to open a file inside the text editor and the response // indicates that this is not a text file, reopen the file through the binary // editor. - if ((error).fileOperationResult === FileOperationResult.FILE_IS_BINARY) { + if ((error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY) { return this.openAsBinary(input, options); } diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index e605875da76..97733dddb2c 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -34,6 +34,7 @@ import { IEditorInput, IEditor } from 'vs/workbench/common/editor'; import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; +import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -42,18 +43,21 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor constructor( @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @IProgressService2 progressService: IProgressService2 ) { super(); - this.registerViews(); + progressService.withProgress({ location: ProgressLocation.Explorer }, () => workspaceContextService.getCompleteWorkspace()).finally(() => { + this.registerViews(); - this.openEditorsVisibleContextKey = OpenEditorsVisibleContext.bindTo(contextKeyService); - this.updateOpenEditorsVisibility(); + this.openEditorsVisibleContextKey = OpenEditorsVisibleContext.bindTo(contextKeyService); + this.updateOpenEditorsVisibility(); - this._register(workspaceContextService.onDidChangeWorkbenchState(() => this.registerViews())); - this._register(workspaceContextService.onDidChangeWorkspaceFolders(() => this.registerViews())); - this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); + this._register(workspaceContextService.onDidChangeWorkbenchState(() => this.registerViews())); + this._register(workspaceContextService.onDidChangeWorkspaceFolders(() => this.registerViews())); + this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); + }); } private registerViews(): void { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index cfaeef24118..5ef1b8eb032 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -23,7 +23,7 @@ import { ResourceContextKey } from 'vs/workbench/common/resources'; import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { SupportsWorkspacesContext } from 'vs/workbench/common/contextkeys'; +import { SupportsWorkspacesContext } from 'vs/workbench/browser/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; // Contribute Global Actions @@ -160,11 +160,11 @@ function appendEditorTitleContextMenuItem(id: string, title: string, when: Conte } // Editor Title Menu for Conflict Resolution -appendSaveConflictEditorTitleAction('workbench.files.action.acceptLocalChanges', nls.localize('acceptLocalChanges', "Use your changes and overwrite disk contents"), { +appendSaveConflictEditorTitleAction('workbench.files.action.acceptLocalChanges', nls.localize('acceptLocalChanges', "Use your changes and overwrite file contents"), { light: URI.parse(require.toUrl(`vs/workbench/contrib/files/browser/media/check.svg`)), dark: URI.parse(require.toUrl(`vs/workbench/contrib/files/browser/media/check-inverse.svg`)) }, -10, acceptLocalChangesCommand); -appendSaveConflictEditorTitleAction('workbench.files.action.revertLocalChanges', nls.localize('revertLocalChanges', "Discard your changes and revert to content on disk"), { +appendSaveConflictEditorTitleAction('workbench.files.action.revertLocalChanges', nls.localize('revertLocalChanges', "Discard your changes and revert to file contents"), { light: URI.parse(require.toUrl(`vs/workbench/contrib/files/browser/media/undo.svg`)), dark: URI.parse(require.toUrl(`vs/workbench/contrib/files/browser/media/undo-inverse.svg`)) }, -9, revertLocalChangesCommand); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 91d42cf1c3c..ede015a2dc1 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -17,7 +17,7 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService, AutoSaveConfiguration } from 'vs/platform/files/common/files'; -import { toResource, ITextEditor, SideBySideEditor } from 'vs/workbench/common/editor'; +import { toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; @@ -155,7 +155,7 @@ export class GlobalNewUntitledFileAction extends Action { } } -function deleteFiles(serviceAccesor: ServicesAccessor, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { +function deleteFiles(textFileService: ITextFileService, dialogService: IDialogService, configurationService: IConfigurationService, fileService: IFileService, elements: ExplorerItem[], useTrash: boolean, skipConfirm = false): Promise { let primaryButton: string; if (useTrash) { primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash"); @@ -164,10 +164,6 @@ function deleteFiles(serviceAccesor: ServicesAccessor, elements: ExplorerItem[], } const distinctElements = resources.distinctParents(elements, e => e.resource); - const textFileService = serviceAccesor.get(ITextFileService); - const dialogService = serviceAccesor.get(IDialogService); - const configurationService = serviceAccesor.get(IConfigurationService); - const fileService = serviceAccesor.get(IFileService); // Handle dirty let confirmDirtyPromise: Promise = Promise.resolve(true); @@ -285,7 +281,7 @@ function deleteFiles(serviceAccesor: ServicesAccessor, elements: ExplorerItem[], skipConfirm = true; - return deleteFiles(serviceAccesor, elements, useTrash, skipConfirm); + return deleteFiles(textFileService, dialogService, configurationService, fileService, elements, useTrash, skipConfirm); } return Promise.resolve(); @@ -345,64 +341,6 @@ function containsBothDirectoryAndFile(distinctElements: ExplorerItem[]): boolean return directories.length > 0 && files.length > 0; } -let pasteShouldMove = false; -// Paste File/Folder -class PasteFileAction extends Action { - - public static readonly ID = 'filesExplorer.paste'; - - constructor( - private element: ExplorerItem, - @IFileService private fileService: IFileService, - @INotificationService private notificationService: INotificationService, - @IEditorService private readonly editorService: IEditorService, - @IExplorerService private readonly explorerService: IExplorerService - ) { - super(PasteFileAction.ID, PASTE_FILE_LABEL); - - if (!this.element) { - this.element = this.explorerService.roots[0]; - } - } - - public run(fileToPaste: URI): Promise { - - // Check if target is ancestor of pasted folder - if (this.element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(this.element.resource, fileToPaste, !isLinux /* ignorecase */)) { - throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder")); - } - - return this.fileService.resolve(fileToPaste).then(fileToPasteStat => { - - // Find target - let target: ExplorerItem; - if (this.element.resource.toString() === fileToPaste.toString()) { - target = this.element.parent!; - } else { - target = this.element.isDirectory ? this.element : this.element.parent!; - } - - const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwirte: pasteShouldMove }); - - // Copy File - const promise = pasteShouldMove ? this.fileService.move(fileToPaste, targetFile) : this.fileService.copy(fileToPaste, targetFile); - return promise.then(stat => { - if (pasteShouldMove) { - // Cut is done. Make sure to clear cut state. - this.explorerService.setToCopy([], false); - } - if (!stat.isDirectory) { - return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true, preserveFocus: true } }) - .then(types.withNullAsUndefined); - } - - return undefined; - }, e => onError(this.notificationService, e)); - }, error => { - onError(this.notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile"))); - }); - } -} export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean, allowOverwirte: boolean }): URI { let name = resources.basenameOrAuthority(fileToPaste.resource); @@ -922,7 +860,7 @@ class ClipboardContentProvider implements ITextModelContentProvider { ) { } provideTextContent(resource: URI): Promise { - const model = this.modelService.createModel(this.clipboardService.readText(), this.modeService.create('text/plain'), resource); + const model = this.modelService.createModel(this.clipboardService.readText(), this.modeService.createByFilepathOrFirstLine(resource.path), resource); return Promise.resolve(model); } @@ -1058,7 +996,7 @@ export const moveFileToTrashHandler = (accessor: ServicesAccessor) => { const explorerContext = getContext(listService.lastFocusedList); const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat!]; - return deleteFiles(accessor, stats, true); + return deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, true); }; export const deleteFileHandler = (accessor: ServicesAccessor) => { @@ -1069,9 +1007,10 @@ export const deleteFileHandler = (accessor: ServicesAccessor) => { const explorerContext = getContext(listService.lastFocusedList); const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat!]; - return deleteFiles(accessor, stats, false); + return deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, false); }; +let pasteShouldMove = false; export const copyFileHandler = (accessor: ServicesAccessor) => { const listService = accessor.get(IListService); if (!listService.lastFocusedList) { @@ -1101,16 +1040,50 @@ export const cutFileHandler = (accessor: ServicesAccessor) => { }; export const pasteFileHandler = (accessor: ServicesAccessor) => { - const instantiationService = accessor.get(IInstantiationService); const listService = accessor.get(IListService); const clipboardService = accessor.get(IClipboardService); - if (!listService.lastFocusedList) { - return Promise.resolve(); - } - const explorerContext = getContext(listService.lastFocusedList); + const explorerService = accessor.get(IExplorerService); + const fileService = accessor.get(IFileService); + const notificationService = accessor.get(INotificationService); + const editorService = accessor.get(IEditorService); - return sequence(resources.distinctParents(clipboardService.readResources(), r => r).map(toCopy => { - const pasteFileAction = instantiationService.createInstance(PasteFileAction, explorerContext.stat); - return () => pasteFileAction.run(toCopy); - })); + if (listService.lastFocusedList) { + const explorerContext = getContext(listService.lastFocusedList); + const toPaste = resources.distinctParents(clipboardService.readResources(), r => r); + const element = explorerContext.stat || explorerService.roots[0]; + + // Check if target is ancestor of pasted folder + sequence(toPaste.map(fileToPaste => () => { + + if (element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(element.resource, fileToPaste, !isLinux /* ignorecase */)) { + throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder")); + } + + return fileService.resolve(fileToPaste).then(fileToPasteStat => { + + // Find target + let target: ExplorerItem; + if (element.resource.toString() === fileToPaste.toString()) { + target = element.parent!; + } else { + target = element.isDirectory ? element : element.parent!; + } + + const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwirte: pasteShouldMove }); + + // Copy File + return pasteShouldMove ? fileService.move(fileToPaste, targetFile) : fileService.copy(fileToPaste, targetFile); + }, error => { + onError(notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile"))); + }); + })).then((stat) => { + if (pasteShouldMove) { + // Cut is done. Make sure to clear cut state. + explorerService.setToCopy([], false); + } + if (stat.length === 1 && !stat[0].isDirectory) { + editorService.openEditor({ resource: stat[0].resource, options: { pinned: true, preserveFocus: true } }).then(undefined, onUnexpectedError); + } + }); + } }; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 5f47ad10d5a..be91bc74ffe 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -6,11 +6,11 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { toResource, IEditorCommandsContext, SideBySideEditor } from 'vs/workbench/common/editor'; -import { IWindowsService, IWindowService, IURIToOpen, IOpenSettings, INewWindowOptions } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IWindowService, IURIToOpen, IOpenSettings, INewWindowOptions, isWorkspaceToOpen } from 'vs/platform/windows/common/windows'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ExplorerFocusCondition, FileOnDiskContentProvider, VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; @@ -38,9 +38,12 @@ import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ILabelService } from 'vs/platform/label/common/label'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { basename, toLocalResource } from 'vs/base/common/resources'; +import { basename, toLocalResource, joinPath } from 'vs/base/common/resources'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { UNTITLED_WORKSPACE_NAME } from 'vs/platform/workspaces/common/workspaces'; +import { withUndefinedAsNull } from 'vs/base/common/types'; // Commands @@ -81,6 +84,19 @@ export const REMOVE_ROOT_FOLDER_LABEL = nls.localize('removeFolderFromWorkspace' export const openWindowCommand = (accessor: ServicesAccessor, urisToOpen: IURIToOpen[], options?: IOpenSettings) => { if (Array.isArray(urisToOpen)) { const windowService = accessor.get(IWindowService); + const environmentService = accessor.get(IEnvironmentService); + + // rewrite untitled: workspace URIs to the absolute path on disk + urisToOpen = urisToOpen.map(uriToOpen => { + if (isWorkspaceToOpen(uriToOpen) && uriToOpen.workspaceUri.scheme === Schemas.untitled) { + return { + workspaceUri: joinPath(environmentService.untitledWorkspacesHome, uriToOpen.workspaceUri.path, UNTITLED_WORKSPACE_NAME) + }; + } + + return uriToOpen; + }); + windowService.openWindow(urisToOpen, options); } }; @@ -304,7 +320,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ if (providerDisposables.length === 0) { registerEditorListener = true; - const provider = instantiationService.createInstance(FileOnDiskContentProvider); + const provider = instantiationService.createInstance(TextFileContentProvider); providerDisposables.push(provider); providerDisposables.push(textModelService.registerTextModelContentProvider(COMPARE_WITH_SAVED_SCHEMA, provider)); } @@ -313,9 +329,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const uri = getResourceForCommand(resource, accessor.get(IListService), editorService); if (uri && fileService.canHandleResource(uri)) { const name = basename(uri); - const editorLabel = nls.localize('modifiedLabel', "{0} (on disk) ↔ {1}", name, name); + const editorLabel = nls.localize('modifiedLabel', "{0} (in file) ↔ {1}", name, name); - editorService.openEditor({ leftResource: uri.with({ scheme: COMPARE_WITH_SAVED_SCHEMA }), rightResource: uri, label: editorLabel }).then(() => { + TextFileContentProvider.open(uri, COMPARE_WITH_SAVED_SCHEMA, editorLabel, editorService).then(() => { // Dispose once no more diff editor is opened with the scheme if (registerEditorListener) { @@ -334,7 +350,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -let globalResourceToCompare: URI | null; +let globalResourceToCompare: URI | undefined; let resourceSelectedForCompareContext: IContextKey; CommandsRegistry.registerCommand({ id: SELECT_FOR_COMPARE_COMMAND_ID, @@ -509,9 +525,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const editorService = accessor.get(IEditorService); let resource: URI | null = null; if (resourceOrObject && 'from' in resourceOrObject && resourceOrObject.from === 'menu') { - resource = toResource(editorService.activeEditor); + resource = withUndefinedAsNull(toResource(editorService.activeEditor)); } else { - resource = getResourceForCommand(resourceOrObject, accessor.get(IListService), editorService); + resource = withUndefinedAsNull(getResourceForCommand(resourceOrObject, accessor.get(IListService), editorService)); } return save(resource, true, undefined, editorService, accessor.get(IFileService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupsService), accessor.get(IWorkbenchEnvironmentService)); diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 4083f4a6e95..65c3e55c4a3 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -13,7 +13,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; -import { AutoSaveConfiguration, HotExitConfiguration, SUPPORTED_ENCODINGS } from 'vs/platform/files/common/files'; +import { AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; import { VIEWLET_ID, SortOrderConfiguration, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; import { SaveErrorHandler } from 'vs/workbench/contrib/files/browser/saveErrorHandler'; @@ -37,11 +37,13 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExplorerService } from 'vs/workbench/contrib/files/common/explorerService'; +import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles'; +import { Schemas } from 'vs/base/common/network'; // Viewlet Action export class OpenExplorerViewletAction extends ShowViewletAction { - public static readonly ID = VIEWLET_ID; - public static readonly LABEL = nls.localize('showExplorerViewlet', "Show Explorer"); + static readonly ID = VIEWLET_ID; + static readonly LABEL = nls.localize('showExplorerViewlet', "Show Explorer"); constructor( id: string, @@ -58,7 +60,7 @@ class FileUriLabelContribution implements IWorkbenchContribution { constructor(@ILabelService labelService: ILabelService) { labelService.registerFormatter({ - scheme: 'file', + scheme: Schemas.file, formatting: { label: '${authority}${path}', separator: sep, @@ -122,8 +124,8 @@ Registry.as(EditorExtensions.Editors).registerEditor( // Register default file input factory Registry.as(EditorInputExtensions.EditorInputFactories).registerFileInputFactory({ - createFileInput: (resource, encoding, instantiationService): IFileEditorInput => { - return instantiationService.createInstance(FileEditorInput, resource, encoding); + createFileInput: (resource, encoding, mode, instantiationService): IFileEditorInput => { + return instantiationService.createInstance(FileEditorInput, resource, encoding, mode); }, isFileInput: (obj): obj is IFileEditorInput => { @@ -135,6 +137,7 @@ interface ISerializedFileInput { resource: string; resourceJSON: object; encoding?: string; + modeId?: string; } // Register Editor Input Factory @@ -142,25 +145,27 @@ class FileEditorInputFactory implements IEditorInputFactory { constructor() { } - public serialize(editorInput: EditorInput): string { + serialize(editorInput: EditorInput): string { const fileEditorInput = editorInput; const resource = fileEditorInput.getResource(); const fileInput: ISerializedFileInput = { resource: resource.toString(), // Keep for backwards compatibility resourceJSON: resource.toJSON(), - encoding: fileEditorInput.getEncoding() + encoding: fileEditorInput.getEncoding(), + modeId: fileEditorInput.getPreferredMode() // only using the preferred user associated mode here if available to not store redundant data }; return JSON.stringify(fileInput); } - public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): FileEditorInput { + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): FileEditorInput { return instantiationService.invokeFunction(accessor => { const fileInput: ISerializedFileInput = JSON.parse(serializedEditorInput); const resource = !!fileInput.resourceJSON ? URI.revive(fileInput.resourceJSON) : URI.parse(fileInput.resource); const encoding = fileInput.encoding; + const mode = fileInput.modeId; - return accessor.get(IEditorService).createInput({ resource, encoding, forceFile: true }) as FileEditorInput; + return accessor.get(IEditorService).createInput({ resource, encoding, mode, forceFile: true }) as FileEditorInput; }); } } @@ -304,6 +309,7 @@ configurationRegistry.registerConfiguration({ }, 'files.hotExit': { 'type': 'string', + 'scope': ConfigurationScope.APPLICATION, 'enum': [HotExitConfiguration.OFF, HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE], 'default': HotExitConfiguration.ON_EXIT, 'markdownEnumDescriptions': [ @@ -321,6 +327,11 @@ configurationRegistry.registerConfiguration({ 'type': 'number', 'default': 4096, 'markdownDescription': nls.localize('maxMemoryForLargeFilesMB', "Controls the memory available to VS Code after restart when trying to open large files. Same effect as specifying `--max-memory=NEWSIZE` on the command line.") + }, + 'files.simpleDialog.enable': { + 'type': 'boolean', + 'description': nls.localize('files.simpleDialog.enable', "Enables the simple file dialog. The simple file dialog replaces the system file dialog when enabled."), + 'default': false, } } }); diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index 5f23f110a1a..85147dc5a43 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -14,7 +14,7 @@ import { coalesce } from 'vs/base/common/arrays'; // Commands can get exeucted from a command pallete, from a context menu or from some list using a keybinding // To cover all these cases we need to properly compute the resource on which the command is being executed -export function getResourceForCommand(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): URI | null { +export function getResourceForCommand(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): URI | undefined { if (URI.isUri(resource)) { return resource; } @@ -41,7 +41,7 @@ export function getResourceForCommand(resource: URI | object | undefined, listSe } } - return editorService.activeEditor ? toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : null; + return editorService.activeEditor ? toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : undefined; } export function getMultiSelectedResources(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): Array { diff --git a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts index f15328da05a..a6ca3697b60 100644 --- a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts @@ -9,7 +9,7 @@ import { basename } from 'vs/base/common/resources'; import { Action } from 'vs/base/common/actions'; import { URI } from 'vs/base/common/uri'; import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { ITextFileService, ISaveErrorHandler, ITextFileEditorModel, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ISaveErrorHandler, ITextFileEditorModel, IResolvedTextFileEditorModel, IWriteTextFileOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -19,7 +19,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { FileOnDiskContentProvider } from 'vs/workbench/contrib/files/common/files'; +import { TextFileContentProvider } from 'vs/workbench/contrib/files/common/files'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IModelService } from 'vs/editor/common/services/modelService'; import { SAVE_FILE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL } from 'vs/workbench/contrib/files/browser/fileCommands'; @@ -39,7 +39,7 @@ export const CONFLICT_RESOLUTION_SCHEME = 'conflictResolution'; const LEARN_MORE_DIRTY_WRITE_IGNORE_KEY = 'learnMoreDirtyWriteError'; -const conflictEditorHelp = nls.localize('userGuide', "Use the actions in the editor tool bar to either undo your changes or overwrite the content on disk with your changes."); +const conflictEditorHelp = nls.localize('userGuide', "Use the actions in the editor tool bar to either undo your changes or overwrite the content of the file with your changes."); // A handler for save error happening with conflict resolution actions export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, IWorkbenchContribution { @@ -61,7 +61,7 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I this.messages = new ResourceMap(); this.conflictResolutionContext = new RawContextKey(CONFLICT_RESOLUTION_CONTEXT, false).bindTo(contextKeyService); - const provider = this._register(instantiationService.createInstance(FileOnDiskContentProvider)); + const provider = this._register(instantiationService.createInstance(TextFileContentProvider)); this._register(textModelService.registerTextModelContentProvider(CONFLICT_RESOLUTION_SCHEME, provider)); // Hook into model @@ -125,7 +125,7 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I // Otherwise show the message that will lead the user into the save conflict editor. else { - message = nls.localize('staleSaveError', "Failed to save '{0}': The content on disk is newer. Please compare your version with the one on disk.", basename(resource)); + message = nls.localize('staleSaveError', "Failed to save '{0}': The content of the file is newer. Please compare your version with the file contents.", basename(resource)); actions.primary!.push(this.instantiationService.createInstance(ResolveSaveConflictAction, model)); } @@ -134,16 +134,17 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I // Any other save error else { const isReadonly = fileOperationError.fileOperationResult === FileOperationResult.FILE_READ_ONLY; - const triedToMakeWriteable = isReadonly && fileOperationError.options && fileOperationError.options.overwriteReadonly; + const triedToMakeWriteable = isReadonly && fileOperationError.options && (fileOperationError.options as IWriteTextFileOptions).overwriteReadonly; const isPermissionDenied = fileOperationError.fileOperationResult === FileOperationResult.FILE_PERMISSION_DENIED; + const canHandlePermissionOrReadonlyErrors = resource.scheme === Schemas.file; // https://github.com/Microsoft/vscode/issues/48659 - // Save Elevated (TODO@remote cannot write elevated https://github.com/Microsoft/vscode/issues/48659) - if (resource.scheme === Schemas.file && (isPermissionDenied || triedToMakeWriteable)) { + // Save Elevated + if (canHandlePermissionOrReadonlyErrors && (isPermissionDenied || triedToMakeWriteable)) { actions.primary!.push(this.instantiationService.createInstance(SaveElevatedAction, model, triedToMakeWriteable)); } - // Overwrite (TODO@remote cannot overwrite readonly https://github.com/Microsoft/vscode/issues/48659) - else if (resource.scheme === Schemas.file && isReadonly) { + // Overwrite + else if (canHandlePermissionOrReadonlyErrors && isReadonly) { actions.primary!.push(this.instantiationService.createInstance(OverwriteReadonlyAction, model)); } @@ -158,13 +159,14 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I // Discard actions.primary!.push(this.instantiationService.createInstance(ExecuteCommandAction, REVERT_FILE_COMMAND_ID, nls.localize('discard', "Discard"))); - if (isReadonly) { + // Message + if (canHandlePermissionOrReadonlyErrors && isReadonly) { if (triedToMakeWriteable) { message = isWindows ? nls.localize('readonlySaveErrorAdmin', "Failed to save '{0}': File is read-only. Select 'Overwrite as Admin' to retry as administrator.", basename(resource)) : nls.localize('readonlySaveErrorSudo', "Failed to save '{0}': File is read-only. Select 'Overwrite as Sudo' to retry as superuser.", basename(resource)); } else { message = nls.localize('readonlySaveError', "Failed to save '{0}': File is read-only. Select 'Overwrite' to attempt to make it writeable.", basename(resource)); } - } else if (isPermissionDenied) { + } else if (canHandlePermissionOrReadonlyErrors && isPermissionDenied) { message = isWindows ? nls.localize('permissionDeniedSaveError', "Failed to save '{0}': Insufficient permissions. Select 'Retry as Admin' to retry as administrator.", basename(resource)) : nls.localize('permissionDeniedSaveErrorSudo', "Failed to save '{0}': Insufficient permissions. Select 'Retry as Sudo' to retry as superuser.", basename(resource)); } else { message = nls.localize('genericSaveError', "Failed to save '{0}': {1}", basename(resource), toErrorMessage(error, false)); @@ -242,16 +244,9 @@ class ResolveSaveConflictAction extends Action { if (!this.model.isDisposed()) { const resource = this.model.getResource(); const name = basename(resource); - const editorLabel = nls.localize('saveConflictDiffLabel', "{0} (on disk) ↔ {1} (in {2}) - Resolve save conflict", name, name, this.environmentService.appNameLong); + const editorLabel = nls.localize('saveConflictDiffLabel', "{0} (in file) ↔ {1} (in {2}) - Resolve save conflict", name, name, this.environmentService.appNameLong); - return this.editorService.openEditor( - { - leftResource: URI.from({ scheme: CONFLICT_RESOLUTION_SCHEME, path: resource.fsPath }), - rightResource: resource, - label: editorLabel, - options: { pinned: true } - } - ).then(() => { + return TextFileContentProvider.open(resource, CONFLICT_RESOLUTION_SCHEME, editorLabel, this.editorService, { pinned: true }).then(() => { if (this.storageService.getBoolean(LEARN_MORE_DIRTY_WRITE_IGNORE_KEY, StorageScope.GLOBAL)) { return; // return if this message is ignored } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index d7ed18b21c9..466ee3bb498 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -35,7 +35,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; -import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -511,8 +511,10 @@ export class ExplorerView extends ViewletPanel { return; } - // Expand all stats in the parent chain - let item: ExplorerItem | undefined = this.explorerService.roots.filter(i => isEqualOrParent(resource, i.resource))[0]; + // Expand all stats in the parent chain. + let item: ExplorerItem | undefined = this.explorerService.roots.filter(i => isEqualOrParent(resource, i.resource)) + // Take the root that is the closest to the stat #72299 + .sort((first, second) => second.resource.path.length - first.resource.path.length)[0]; while (item && item.resource.toString() !== resource.toString()) { await this.tree.expand(item); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index d43b8701d19..6a46cb17e53 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -46,6 +46,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -441,7 +442,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop { @IInstantiationService private instantiationService: IInstantiationService, @ITextFileService private textFileService: ITextFileService, @IWindowService private windowService: IWindowService, - @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService + @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, + @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService ) { this.toDispose = []; @@ -604,6 +606,11 @@ export class FileDragAndDrop implements ITreeDragAndDrop { private handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { const droppedResources = extractResources(originalEvent, true); + if (this.environmentService.configuration.remoteAuthority) { + if (droppedResources.some(r => r.resource.authority !== this.environmentService.configuration.remoteAuthority)) { + return Promise.resolve(); + } + } // Check for dropped external files to be folders return this.fileService.resolveAll(droppedResources).then(result => { diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 02842a2c222..80704ef56f2 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -30,7 +30,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { DirtyEditorContext, OpenEditorsGroupContext } from 'vs/workbench/contrib/files/browser/fileCommands'; import { ResourceContextKey } from 'vs/workbench/common/resources'; @@ -41,7 +41,7 @@ import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { memoize } from 'vs/base/common/decorators'; import { ElementsDragAndDropData, DesktopDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { URI } from 'vs/base/common/uri'; -import { withNullAsUndefined } from 'vs/base/common/types'; +import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; const $ = dom.$; @@ -245,7 +245,7 @@ export class OpenEditorsView extends ViewletPanel { const element = e.elements.length ? e.elements[0] : undefined; if (element instanceof OpenEditor) { this.dirtyEditorFocusedContext.set(this.textFileService.isDirty(withNullAsUndefined(element.getResource()))); - this.resourceContext.set(element.getResource()); + this.resourceContext.set(withUndefinedAsNull(element.getResource())); } else if (!!element) { this.groupFocusedContext.set(true); } diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 89fee97ad47..ecd630e356a 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -12,7 +12,7 @@ import { EncodingMode, ConfirmResult, EditorInput, IFileEditorInput, ITextEditor import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { ITextFileService, AutoSaveMode, ModelState, TextFileModelChangeEvent, LoadReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, AutoSaveMode, ModelState, TextFileModelChangeEvent, LoadReason, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IReference } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -24,8 +24,11 @@ import { ILabelService } from 'vs/platform/label/common/label'; */ export class FileEditorInput extends EditorInput implements IFileEditorInput { private preferredEncoding: string; + private preferredMode: string; + private forceOpenAsBinary: boolean; private forceOpenAsText: boolean; + private textModelReference: Promise> | null; private name: string; @@ -35,6 +38,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { constructor( private resource: URI, preferredEncoding: string | undefined, + preferredMode: string | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITextFileService private readonly textFileService: ITextFileService, @ITextModelService private readonly textModelResolverService: ITextModelService, @@ -46,6 +50,10 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { this.setPreferredEncoding(preferredEncoding); } + if (preferredMode) { + this.setPreferredMode(preferredMode); + } + this.registerListeners(); } @@ -89,7 +97,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { } setEncoding(encoding: string, mode: EncodingMode): void { - this.preferredEncoding = encoding; + this.setPreferredEncoding(encoding); const textModel = this.textFileService.models.get(this.resource); if (textModel) { @@ -102,6 +110,24 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { this.forceOpenAsText = true; // encoding is a good hint to open the file as text } + getPreferredMode(): string | undefined { + return this.preferredMode; + } + + setMode(mode: string): void { + this.setPreferredMode(mode); + + const textModel = this.textFileService.models.get(this.resource); + if (textModel) { + textModel.setMode(mode); + } + } + + setPreferredMode(mode: string): void { + this.preferredMode = mode; + this.forceOpenAsText = true; // mode is a good hint to open the file as text + } + setForceOpenAsText(): void { this.forceOpenAsText = true; this.forceOpenAsBinary = false; @@ -193,7 +219,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { private decorateLabel(label: string): string { const model = this.textFileService.models.get(this.resource); if (model && model.hasState(ModelState.ORPHAN)) { - return localize('orphanedFile', "{0} (deleted from disk)", label); + return localize('orphanedFile', "{0} (deleted)", label); } if (model && model.isReadonly()) { @@ -251,6 +277,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { // Resolve as text return this.textFileService.models.loadOrCreate(this.resource, { + mode: this.preferredMode, encoding: this.preferredEncoding, reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model allowBinary: this.forceOpenAsText, @@ -269,7 +296,10 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { }, error => { // In case of an error that indicates that the file is binary or too large, just return with the binary editor model - if ((error).fileOperationResult === FileOperationResult.FILE_IS_BINARY || (error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) { + if ( + (error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY || + (error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE + ) { return this.doResolveAsBinary(); } diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 0e3b5d1dc1d..6bec4dbd5da 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -155,7 +155,7 @@ export class ExplorerService implements IExplorerService { } // Stat needs to be resolved first and then revealed - const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: false }; + const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this.sortOrder === 'modified' }; const workspaceFolder = this.contextService.getWorkspaceFolder(resource); const rootUri = workspaceFolder ? workspaceFolder.uri : this.roots[0].resource; const root = this.roots.filter(r => r.resource.toString() === rootUri.toString()).pop()!; diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index ab377e99bc8..9b1d56da125 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -23,8 +23,8 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { once } from 'vs/base/common/functional'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { toLocalResource } from 'vs/base/common/resources'; +import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; /** * Explorer viewlet id. @@ -60,8 +60,8 @@ export interface IExplorerService { isCut(stat: ExplorerItem): boolean; /** - * Selects and reveal the file element provided by the given resource if its found in the explorer. Will try to - * resolve the path from the disk in case the explorer is not yet expanded to the file yet. + * Selects and reveal the file element provided by the given resource if its found in the explorer. + * Will try to resolve the path in case the explorer is not yet expanded to the file yet. */ select(resource: URI, reveal?: boolean): Promise; } @@ -133,25 +133,42 @@ export const SortOrderConfiguration = { export type SortOrder = 'default' | 'mixed' | 'filesFirst' | 'type' | 'modified'; -export class FileOnDiskContentProvider implements ITextModelContentProvider { +export class TextFileContentProvider implements ITextModelContentProvider { private fileWatcherDisposable: IDisposable | undefined; constructor( @ITextFileService private readonly textFileService: ITextFileService, @IFileService private readonly fileService: IFileService, @IModeService private readonly modeService: IModeService, - @IModelService private readonly modelService: IModelService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService - ) { + @IModelService private readonly modelService: IModelService + ) { } + + static open(resource: URI, scheme: string, label: string, editorService: IEditorService, options?: ITextEditorOptions): Promise { + return editorService.openEditor( + { + leftResource: TextFileContentProvider.resourceToTextFile(scheme, resource), + rightResource: resource, + label, + options + } + ).then(); + } + + private static resourceToTextFile(scheme: string, resource: URI): URI { + return resource.with({ scheme, query: JSON.stringify({ scheme: resource.scheme }) }); + } + + private static textFileToResource(resource: URI): URI { + return resource.with({ scheme: JSON.parse(resource.query)['scheme'], query: null }); } provideTextContent(resource: URI): Promise { - const savedFileResource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority); + const savedFileResource = TextFileContentProvider.textFileToResource(resource); - // Make sure our file from disk is resolved up to date + // Make sure our text file is resolved up to date return this.resolveEditorModel(resource).then(codeEditorModel => { - // Make sure to keep contents on disk up to date when it changes + // Make sure to keep contents up to date when it changes if (!this.fileWatcherDisposable) { this.fileWatcherDisposable = this.fileService.onFileChanges(changes => { if (changes.contains(savedFileResource, FileChangeType.UPDATED)) { @@ -174,20 +191,20 @@ export class FileOnDiskContentProvider implements ITextModelContentProvider { private resolveEditorModel(resource: URI, createAsNeeded?: true): Promise; private resolveEditorModel(resource: URI, createAsNeeded?: boolean): Promise; private resolveEditorModel(resource: URI, createAsNeeded: boolean = true): Promise { - const savedFileResource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority); + const savedFileResource = TextFileContentProvider.textFileToResource(resource); - return this.textFileService.resolve(savedFileResource).then(content => { + return this.textFileService.readStream(savedFileResource).then(content => { let codeEditorModel = this.modelService.getModel(resource); if (codeEditorModel) { this.modelService.updateModel(codeEditorModel, content.value); } else if (createAsNeeded) { - const fileOnDiskModel = this.modelService.getModel(savedFileResource); + const textFileModel = this.modelService.getModel(savedFileResource); let languageSelector: ILanguageSelection; - if (fileOnDiskModel) { - languageSelector = this.modeService.create(fileOnDiskModel.getModeId()); + if (textFileModel) { + languageSelector = this.modeService.create(textFileModel.getModeId()); } else { - languageSelector = this.modeService.createByFilepathOrFirstLine(savedFileResource.fsPath); + languageSelector = this.modeService.createByFilepathOrFirstLine(savedFileResource.path); } codeEditorModel = this.modelService.createModel(content.value, languageSelector, resource); @@ -241,7 +258,7 @@ export class OpenEditor implements IEditorIdentifier { return this.editor.isDirty(); } - public getResource(): URI | null { + public getResource(): URI | undefined { return toResource(this.editor, { supportSideBySide: SideBySideEditor.MASTER }); } } diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index d8ee8911f8b..674d1f2a1e4 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -11,11 +11,12 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EncodingMode, Verbosity } from 'vs/workbench/common/editor'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout } from 'vs/base/common/async'; +import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; class ServiceAccessor { constructor( @@ -36,10 +37,10 @@ suite('Files - FileEditorInput', () => { accessor = instantiationService.createInstance(ServiceAccessor); }); - test('Basics', function () { - let input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined); - const otherInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/otherfile.js'), undefined); - const otherInputSame = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/file.js'), undefined); + test('Basics', async function () { + let input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined); + const otherInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/otherfile.js'), undefined, undefined); + const otherInputSame = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/file.js'), undefined, undefined); assert(input.matches(input)); assert(input.matches(otherInputSame)); @@ -54,52 +55,65 @@ suite('Files - FileEditorInput', () => { assert.strictEqual(toResource.call(this, '/foo/bar/file.js').fsPath, input.getResource().fsPath); assert(input.getResource() instanceof URI); - input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar.html'), undefined); + input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar.html'), undefined, undefined); - const inputToResolve: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined); - const sameOtherInput: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined); + const inputToResolve: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined); + const sameOtherInput: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined); - return inputToResolve.resolve().then(resolved => { - assert.ok(inputToResolve.isResolved()); + let resolved = await inputToResolve.resolve(); + assert.ok(inputToResolve.isResolved()); - const resolvedModelA = resolved; - return inputToResolve.resolve().then(resolved => { - assert(resolvedModelA === resolved); // OK: Resolved Model cached globally per input + const resolvedModelA = resolved; + resolved = await inputToResolve.resolve(); + assert(resolvedModelA === resolved); // OK: Resolved Model cached globally per input - return sameOtherInput.resolve().then(otherResolved => { - assert(otherResolved === resolvedModelA); // OK: Resolved Model cached globally per input + const otherResolved = await sameOtherInput.resolve(); + assert(otherResolved === resolvedModelA); // OK: Resolved Model cached globally per input + inputToResolve.dispose(); - inputToResolve.dispose(); + resolved = await inputToResolve.resolve(); + assert(resolvedModelA === resolved); // Model is still the same because we had 2 clients + inputToResolve.dispose(); + sameOtherInput.dispose(); + resolvedModelA.dispose(); - return inputToResolve.resolve().then(resolved => { - assert(resolvedModelA === resolved); // Model is still the same because we had 2 clients + resolved = await inputToResolve.resolve(); + assert(resolvedModelA !== resolved); // Different instance, because input got disposed - inputToResolve.dispose(); - sameOtherInput.dispose(); + const stat = (resolved as TextFileEditorModel).getStat(); + resolved = await inputToResolve.resolve(); + await timeout(0); + assert(stat !== (resolved as TextFileEditorModel).getStat()); // Different stat, because resolve always goes to the server for refresh + }); - resolvedModelA.dispose(); - - return inputToResolve.resolve().then(resolved => { - assert(resolvedModelA !== resolved); // Different instance, because input got disposed - - let stat = (resolved as TextFileEditorModel).getStat(); - return inputToResolve.resolve().then(resolved => { - return timeout(0).then(() => { // due to file editor input using `reload: { async: true }` - assert(stat !== (resolved as TextFileEditorModel).getStat()); // Different stat, because resolve always goes to the server for refresh - }); - }); - }); - }); - }); - }); + test('preferred mode', async function () { + const mode = 'file-input-test'; + ModesRegistry.registerLanguage({ + id: mode, }); + + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, mode); + assert.equal(input.getPreferredMode(), mode); + + const model = await input.resolve() as TextFileEditorModel; + assert.equal(model.textEditorModel!.getModeId(), mode); + + input.setMode('text'); + assert.equal(input.getPreferredMode(), 'text'); + assert.equal(model.textEditorModel!.getModeId(), PLAINTEXT_MODE_ID); + + const input2 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined, undefined); + input2.setPreferredMode(mode); + + const model2 = await input2.resolve() as TextFileEditorModel; + assert.equal(model2.textEditorModel!.getModeId(), mode); }); test('matches', function () { - const input1 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); - const input2 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); - const input3 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/other.js'), undefined); - const input2Upper = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/UPDATEFILE.js'), undefined); + const input1 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined); + const input2 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined); + const input3 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/other.js'), undefined, undefined); + const input2Upper = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/UPDATEFILE.js'), undefined, undefined); assert.strictEqual(input1.matches(null), false); assert.strictEqual(input1.matches(input1), true); @@ -109,70 +123,58 @@ suite('Files - FileEditorInput', () => { assert.strictEqual(input1.matches(input2Upper), false); }); - test('getEncoding/setEncoding', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); + test('getEncoding/setEncoding', async function () { + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined); input.setEncoding('utf16', EncodingMode.Encode); assert.equal(input.getEncoding(), 'utf16'); - return input.resolve().then((resolved: TextFileEditorModel) => { - assert.equal(input.getEncoding(), resolved.getEncoding()); - - resolved.dispose(); - }); + const resolved = await input.resolve() as TextFileEditorModel; + assert.equal(input.getEncoding(), resolved.getEncoding()); + resolved.dispose(); }); - test('save', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); + test('save', async function () { + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined); - return input.resolve().then((resolved: TextFileEditorModel) => { - resolved.textEditorModel!.setValue('changed'); - assert.ok(input.isDirty()); + const resolved = await input.resolve() as TextFileEditorModel; + resolved.textEditorModel!.setValue('changed'); + assert.ok(input.isDirty()); - return input.save().then(() => { - assert.ok(!input.isDirty()); - - resolved.dispose(); - }); - }); + await input.save(); + assert.ok(!input.isDirty()); + resolved.dispose(); }); - test('revert', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); + test('revert', async function () { + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined); - return input.resolve().then((resolved: TextFileEditorModel) => { - resolved.textEditorModel!.setValue('changed'); - assert.ok(input.isDirty()); + const resolved = await input.resolve() as TextFileEditorModel; + resolved.textEditorModel!.setValue('changed'); + assert.ok(input.isDirty()); - return input.revert().then(() => { - assert.ok(!input.isDirty()); - - resolved.dispose(); - }); - }); + await input.revert(); + assert.ok(!input.isDirty()); + resolved.dispose(); }); - test('resolve handles binary files', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); + test('resolve handles binary files', async function () { + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined); - accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_IS_BINARY)); + accessor.textFileService.setResolveTextContentErrorOnce(new TextFileOperationError('error', TextFileOperationResult.FILE_IS_BINARY)); - return input.resolve().then(resolved => { - assert.ok(resolved); - - resolved.dispose(); - }); + const resolved = await input.resolve(); + assert.ok(resolved); + resolved.dispose(); }); - test('resolve handles too large files', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); + test('resolve handles too large files', async function () { + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined); accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_TOO_LARGE)); - return input.resolve().then(resolved => { - assert.ok(resolved); - - resolved.dispose(); - }); + const resolved = await input.resolve(); + assert.ok(resolved); + resolved.dispose(); }); }); diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts index 505e3192b4e..b607e711aa7 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts @@ -9,8 +9,8 @@ import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { workbenchInstantiationService, TestTextFileService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ITextFileService, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; -import { FileChangesEvent, FileChangeType, IFileService, snapshotToString } from 'vs/platform/files/common/files'; +import { ITextFileService, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { timeout } from 'vs/base/common/async'; diff --git a/src/vs/workbench/contrib/files/test/common/fileOnDiskProvider.test.ts b/src/vs/workbench/contrib/files/test/common/fileOnDiskProvider.test.ts new file mode 100644 index 00000000000..9c2064028d2 --- /dev/null +++ b/src/vs/workbench/contrib/files/test/common/fileOnDiskProvider.test.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { URI } from 'vs/base/common/uri'; +import { workbenchInstantiationService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TextFileContentProvider } from 'vs/workbench/contrib/files/common/files'; +import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { IFileService } from 'vs/platform/files/common/files'; + +class ServiceAccessor { + constructor( + @IFileService public fileService: TestFileService + ) { + } +} + +suite('Files - FileOnDiskContentProvider', () => { + + let instantiationService: IInstantiationService; + let accessor: ServiceAccessor; + + setup(() => { + instantiationService = workbenchInstantiationService(); + accessor = instantiationService.createInstance(ServiceAccessor); + }); + + test('provideTextContent', async () => { + const provider = instantiationService.createInstance(TextFileContentProvider); + const uri = URI.parse('testFileOnDiskContentProvider://foo'); + + const content = await provider.provideTextContent(uri.with({ scheme: 'conflictResolution', query: JSON.stringify({ scheme: uri.scheme }) })); + + assert.equal(snapshotToString(content.createSnapshot()), 'Hello Html'); + assert.equal(accessor.fileService.getLastReadFileUri().toString(), uri.toString()); + }); +}); diff --git a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts index 0b6ef5a245a..37762b7c66f 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts @@ -201,19 +201,34 @@ async function showFormatterPick(accessor: ServicesAccessor, model: ITextModel, const overrides = { resource: model.uri, overrideIdentifier: model.getModeId() }; const defaultFormatter = configService.getValue(DefaultFormatter.configName, overrides); + let defaultFormatterPick: IIndexedPick | undefined; + const picks = formatters.map((provider, index) => { - return { + const isDefault = ExtensionIdentifier.equals(provider.extensionId, defaultFormatter); + const pick = { index, label: provider.displayName || '', - description: ExtensionIdentifier.equals(provider.extensionId, defaultFormatter) ? nls.localize('def', "(default)") : undefined, + description: isDefault ? nls.localize('def', "(default)") : undefined, }; + + if (isDefault) { + // autofocus default pick + defaultFormatterPick = pick; + } + + return pick; }); const configurePick: IQuickPickItem = { label: nls.localize('config', "Configure Default Formatter...") }; - const pick = await quickPickService.pick([...picks, { type: 'separator' }, configurePick], { placeHolder: nls.localize('format.placeHolder', "Select a formatter") }); + const pick = await quickPickService.pick([...picks, { type: 'separator' }, configurePick], + { + placeHolder: nls.localize('format.placeHolder', "Select a formatter"), + activeItem: defaultFormatterPick + } + ); if (!pick) { // dismissed return undefined; diff --git a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts index 9b66367af6b..6bce6be08ef 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts @@ -50,7 +50,7 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction { return commandService.executeCommand('editor.action.formatDocument'); } else { const langName = model.getLanguageIdentifier().language; - const message = nls.localize('no.rovider', "There is no formatter for '{0}'-files installed.", langName); + const message = nls.localize('no.provider', "There is no formatter for '{0}'-files installed.", langName); const choice = { label: nls.localize('install.formatter', "Install Formatter..."), run: () => showExtensionQuery(viewletService, `category:formatters ${langName}`) diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index a26bb074b17..bad977ca281 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -7,14 +7,14 @@ import 'vs/css!./media/markers'; import { URI } from 'vs/base/common/uri'; import * as dom from 'vs/base/browser/dom'; -import { IAction, IActionItem, Action } from 'vs/base/common/actions'; +import { IAction, IActionViewItem, Action } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Panel } from 'vs/workbench/browser/panel'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { Marker, ResourceMarkers, RelatedInformation, MarkersModel } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersFilterActionItem, MarkersFilterAction, IMarkersFilterActionChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; +import { MarkersFilterActionViewItem, MarkersFilterAction, IMarkersFilterActionChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; @@ -34,7 +34,7 @@ import { deepClone } from 'vs/base/common/objects'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider, MarkersViewModel, ResourceDragAndDrop } from 'vs/workbench/contrib/markers/browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { Separator, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Separator, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -74,7 +74,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private actions: IAction[]; private collapseAllAction: IAction; private filterAction: MarkersFilterAction; - private filterInputActionItem: MarkersFilterActionItem; + private filterInputActionViewItem: MarkersFilterActionViewItem; private treeContainer: HTMLElement; private messageBoxContainer: HTMLElement; @@ -152,8 +152,8 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { public layout(dimension: dom.Dimension): void { this.treeContainer.style.height = `${dimension.height}px`; this.tree.layout(dimension.height, dimension.width); - if (this.filterInputActionItem) { - this.filterInputActionItem.toggleLayout(dimension.width < 1200); + if (this.filterInputActionViewItem) { + this.filterInputActionViewItem.toggleLayout(dimension.width < 1200); } } @@ -166,8 +166,8 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } public focusFilter(): void { - if (this.filterInputActionItem) { - this.filterInputActionItem.focus(); + if (this.filterInputActionViewItem) { + this.filterInputActionViewItem.focus(); } } @@ -358,8 +358,8 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { // move focus to input, whenever a key is pressed in the panel container this._register(domEvent(parent, 'keydown')(e => { - if (this.filterInputActionItem && this.keybindingService.mightProducePrintableCharacter(new StandardKeyboardEvent(e))) { - this.filterInputActionItem.focus(); + if (this.filterInputActionViewItem && this.keybindingService.mightProducePrintableCharacter(new StandardKeyboardEvent(e))) { + this.filterInputActionViewItem.focus(); } })); @@ -624,10 +624,10 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor!, getActions: () => this.getMenuActions(element), - getActionItem: (action) => { + getActionViewItem: (action) => { const keybinding = this.keybindingService.lookupKeybinding(action.id); if (keybinding) { - return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() }); + return new ActionViewItem(action, action, { label: true, keybinding: keybinding.getLabel() }); } return undefined; }, @@ -671,12 +671,12 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { return this.tree.getFocus()[0]; } - public getActionItem(action: IAction): IActionItem | undefined { + public getActionViewItem(action: IAction): IActionViewItem | undefined { if (action.id === MarkersFilterAction.ID) { - this.filterInputActionItem = this.instantiationService.createInstance(MarkersFilterActionItem, this.filterAction, this); - return this.filterInputActionItem; + this.filterInputActionViewItem = this.instantiationService.createInstance(MarkersFilterActionViewItem, this.filterAction, this); + return this.filterInputActionViewItem; } - return super.getActionItem(action); + return super.getActionViewItem(action); } getFilterOptions(): FilterOptions { diff --git a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts index 80b5ccd81a2..3d633a1686b 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts @@ -19,7 +19,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachStylerCallback, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; import { IMarkersWorkbenchService } from 'vs/workbench/contrib/markers/browser/markers'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; -import { BaseActionItem, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { BaseActionViewItem, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; @@ -115,7 +115,7 @@ export interface IMarkerFilterController { getFilterStats(): { total: number, filtered: number }; } -export class MarkersFilterActionItem extends BaseActionItem { +export class MarkersFilterActionViewItem extends BaseActionViewItem { private delayedFilterUpdate: Delayer; private container: HTMLElement; @@ -331,7 +331,7 @@ export class QuickFixAction extends Action { } } -export class QuickFixActionItem extends ActionItem { +export class QuickFixActionViewItem extends ActionViewItem { constructor(action: QuickFixAction, @IContextMenuService private readonly contextMenuService: IContextMenuService, diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 0f9144e224b..235154cc1ff 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -17,7 +17,7 @@ import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { QuickFixAction, QuickFixActionItem } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; +import { QuickFixAction, QuickFixActionViewItem } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; import { ILabelService } from 'vs/platform/label/common/label'; import { dirname, basename, isEqual } from 'vs/base/common/resources'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; @@ -253,7 +253,7 @@ class MarkerWidget extends Disposable { ) { super(); this.actionBar = this._register(new ActionBar(dom.append(parent, dom.$('.actions')), { - actionItemProvider: (action) => action.id === QuickFixAction.ID ? instantiationService.createInstance(QuickFixActionItem, action) : undefined + actionViewItemProvider: (action) => action.id === QuickFixAction.ID ? instantiationService.createInstance(QuickFixActionViewItem, action) : undefined })); this.icon = dom.append(parent, dom.$('.icon')); this.multilineActionbar = this._register(new ActionBar(dom.append(parent, dom.$('.multiline-actions')))); @@ -290,9 +290,9 @@ class MarkerWidget extends Disposable { } }, this, this.disposables); quickFixAction.onShowQuickFixes(() => { - const quickFixActionItem = this.actionBar.items[0]; - if (quickFixActionItem) { - quickFixActionItem.showQuickFixes(); + const quickFixActionViewItem = this.actionBar.viewItems[0]; + if (quickFixActionViewItem) { + quickFixActionViewItem.showQuickFixes(); } }, this, this.disposables); } diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts index a34f2eebbdf..6c835eb5c06 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts @@ -456,7 +456,7 @@ export class OutlinePanel extends ViewletPanel { } if (!editor || !editor.hasModel() || !DocumentSymbolProviderRegistry.has(editor.getModel())) { - return this._showMessage(localize('no-editor', "There are no editors open that can provide outline information.")); + return this._showMessage(localize('no-editor', "The active editor cannot provide outline information.")); } let textModel = editor.getModel(); diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index c33c399e893..c1bbbd2f7c0 100644 --- a/src/vs/workbench/contrib/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -28,7 +28,7 @@ export class LogViewerInput extends ResourceEditorInput { constructor(private outputChannelDescriptor: IFileOutputChannelDescriptor, @ITextModelService textModelResolverService: ITextModelService ) { - super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), textModelResolverService); + super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), undefined, textModelResolverService); } public getTypeId(): string { diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index 02bf8197c34..a7d2ae2d3cc 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { IAction, Action } from 'vs/base/common/actions'; import { IOutputService, OUTPUT_PANEL_ID, IOutputChannelRegistry, Extensions as OutputExt, IOutputChannelDescriptor, IFileOutputChannelDescriptor } from 'vs/workbench/contrib/output/common/output'; -import { SelectActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; @@ -125,7 +125,7 @@ export class SwitchOutputAction extends Action { } } -export class SwitchOutputActionItem extends SelectActionItem { +export class SwitchOutputActionViewItem extends SelectActionViewItem { private static readonly SEPARATOR = '─────────'; @@ -168,7 +168,7 @@ export class SwitchOutputActionItem extends SelectActionItem { this.logChannels = groups[1] || []; const showSeparator = this.outputChannels.length && this.logChannels.length; const separatorIndex = showSeparator ? this.outputChannels.length : -1; - const options: string[] = [...this.outputChannels.map(c => c.label), ...(showSeparator ? [SwitchOutputActionItem.SEPARATOR] : []), ...this.logChannels.map(c => nls.localize('logChannel', "Log ({0})", c.label))]; + const options: string[] = [...this.outputChannels.map(c => c.label), ...(showSeparator ? [SwitchOutputActionViewItem.SEPARATOR] : []), ...this.logChannels.map(c => nls.localize('logChannel', "Log ({0})", c.label))]; let selected = 0; const activeChannel = this.outputService.getActiveChannel(); if (activeChannel) { diff --git a/src/vs/workbench/contrib/output/browser/outputPanel.ts b/src/vs/workbench/contrib/output/browser/outputPanel.ts index c626a799910..7537b9381b6 100644 --- a/src/vs/workbench/contrib/output/browser/outputPanel.ts +++ b/src/vs/workbench/contrib/output/browser/outputPanel.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/output'; import * as nls from 'vs/nls'; import { Action, IAction } from 'vs/base/common/actions'; -import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -18,7 +18,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { AbstractTextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; import { OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT } from 'vs/workbench/contrib/output/common/output'; -import { SwitchOutputAction, SwitchOutputActionItem, ClearOutputAction, ToggleOrSetOutputScrollLockAction, OpenLogOutputFile } from 'vs/workbench/contrib/output/browser/outputActions'; +import { SwitchOutputAction, SwitchOutputActionViewItem, ClearOutputAction, ToggleOrSetOutputScrollLockAction, OpenLogOutputFile } from 'vs/workbench/contrib/output/browser/outputActions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -75,12 +75,12 @@ export class OutputPanel extends AbstractTextResourceEditor { return this.actions; } - public getActionItem(action: Action): IActionItem | undefined { + public getActionViewItem(action: Action): IActionViewItem | undefined { if (action.id === SwitchOutputAction.ID) { - return this.instantiationService.createInstance(SwitchOutputActionItem, action); + return this.instantiationService.createInstance(SwitchOutputActionViewItem, action); } - return super.getActionItem(action); + return super.getActionViewItem(action); } protected getConfigurationOverrides(): IEditorOptions { @@ -95,7 +95,7 @@ export class OutputPanel extends AbstractTextResourceEditor { options.renderLineHighlight = 'none'; options.minimap = { enabled: false }; - const outputConfig = this.baseConfigurationService.getValue('[Log]'); + const outputConfig = this.baseConfigurationService.getValue<{}>('[Log]'); if (outputConfig) { if (outputConfig['editor.minimap.enabled']) { options.minimap = { enabled: true }; diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index cb5a3e64108..eb8ef512efd 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -244,7 +244,7 @@ export class OutputService extends Disposable implements IOutputService, ITextMo private createInput(channel: IOutputChannel): ResourceEditorInput { const resource = URI.from({ scheme: OUTPUT_SCHEME, path: channel.id }); - return this.instantiationService.createInstance(ResourceEditorInput, nls.localize('output', "{0} - Output", channel.label), nls.localize('channel', "Output channel for '{0}'", channel.label), resource); + return this.instantiationService.createInstance(ResourceEditorInput, nls.localize('output', "{0} - Output", channel.label), nls.localize('channel', "Output channel for '{0}'", channel.label), resource, undefined); } private saveState(): void { diff --git a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index a6267a9a2d4..7152a8c871b 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -52,6 +52,7 @@ export class PerfviewInput extends ResourceEditorInput { localize('name', "Startup Performance"), null, PerfviewInput.Uri, + undefined, textModelResolverService ); } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index faf67123d19..aa8da6134ca 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -9,7 +9,7 @@ import { Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; import { OS } from 'vs/base/common/platform'; import { dispose, Disposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; -import { CheckboxActionItem } from 'vs/base/browser/ui/checkbox/checkbox'; +import { CheckboxActionViewItem } from 'vs/base/browser/ui/checkbox/checkbox'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { IAction, Action } from 'vs/base/common/actions'; @@ -372,12 +372,12 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.actionBar = this._register(new ActionBar(this.actionsContainer, { animated: false, - actionItemProvider: (action: Action) => { + actionViewItemProvider: (action: Action) => { if (action.id === this.sortByPrecedenceAction.id) { - return new CheckboxActionItem(null, action); + return new CheckboxActionViewItem(null, action); } if (action.id === this.recordKeysAction.id) { - return new CheckboxActionItem(null, action); + return new CheckboxActionViewItem(null, action); } return undefined; } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index edc231b62a1..c8eacc925a2 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -154,14 +154,14 @@ export class PreferencesEditor extends BaseEditor { this.preferencesRenderers.editFocusedPreference(); } - setInput(newInput: PreferencesEditorInput, options: SettingsEditorOptions, token: CancellationToken): Promise { + setInput(newInput: EditorInput, options: SettingsEditorOptions, token: CancellationToken): Promise { this.defaultSettingsEditorContextKey.set(true); this.defaultSettingsJSONEditorContextKey.set(true); if (options && options.query) { this.focusSearch(options.query); } - return super.setInput(newInput, options, token).then(() => this.updateInput(newInput, options, token)); + return super.setInput(newInput, options, token).then(() => this.updateInput(newInput as PreferencesEditorInput, options, token)); } layout(dimension: DOM.Dimension): void { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index 8faa20df1ce..f93d3624048 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -5,7 +5,7 @@ import * as DOM from 'vs/base/browser/dom'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { ActionBar, ActionsOrientation, BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, ActionsOrientation, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IInputOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { Action, IAction } from 'vs/base/common/actions'; @@ -291,7 +291,7 @@ export class SettingsGroupTitleWidget extends Widget implements IViewZone { } } -export class FolderSettingsActionItem extends BaseActionItem { +export class FolderSettingsActionViewItem extends BaseActionViewItem { private _folder: IWorkspaceFolder | null; private _folderSettingCounts = new Map(); @@ -427,7 +427,7 @@ export class FolderSettingsActionItem extends BaseActionItem { this.contextMenuService.showContextMenu({ getAnchor: () => this.container, getActions: () => this.getDropdownMenuActions(), - getActionItem: () => undefined, + getActionViewItem: () => undefined, onHide: () => { this.anchorElement.blur(); } @@ -479,7 +479,7 @@ export class SettingsTargetsWidget extends Widget { private userLocalSettings: Action; private userRemoteSettings: Action; private workspaceSettings: Action; - private folderSettings: FolderSettingsActionItem; + private folderSettings: FolderSettingsActionViewItem; private options: ISettingsTargetsWidgetOptions; private _settingsTarget: SettingsTarget; @@ -508,24 +508,24 @@ export class SettingsTargetsWidget extends Widget { orientation: ActionsOrientation.HORIZONTAL, ariaLabel: localize('settingsSwitcherBarAriaLabel', "Settings Switcher"), animated: false, - actionItemProvider: (action: Action) => action.id === 'folderSettings' ? this.folderSettings : undefined + actionViewItemProvider: (action: Action) => action.id === 'folderSettings' ? this.folderSettings : undefined })); - this.userLocalSettings = new Action('userSettings', localize('userSettings', "User Settings"), '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL)); + this.userLocalSettings = new Action('userSettings', localize('userSettings', "User"), '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL)); this.userLocalSettings.tooltip = this.userLocalSettings.label; const remoteAuthority = this.environmentService.configuration.remoteAuthority; const hostLabel = remoteAuthority && this.labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAuthority); - const remoteSettingsLabel = localize('userSettingsRemote', "Remote Settings") + + const remoteSettingsLabel = localize('userSettingsRemote', "Remote") + (hostLabel ? ` (${hostLabel})` : ''); this.userRemoteSettings = new Action('userSettingsRemote', remoteSettingsLabel, '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_REMOTE)); this.userRemoteSettings.tooltip = this.userRemoteSettings.label; - this.workspaceSettings = new Action('workspaceSettings', localize('workspaceSettings', "Workspace Settings"), '.settings-tab', false, () => this.updateTarget(ConfigurationTarget.WORKSPACE)); + this.workspaceSettings = new Action('workspaceSettings', localize('workspaceSettings', "Workspace"), '.settings-tab', false, () => this.updateTarget(ConfigurationTarget.WORKSPACE)); this.workspaceSettings.tooltip = this.workspaceSettings.label; - const folderSettingsAction = new Action('folderSettings', localize('folderSettings', "Folder Settings"), '.settings-tab', false, (folder: IWorkspaceFolder) => this.updateTarget(folder.uri)); - this.folderSettings = this.instantiationService.createInstance(FolderSettingsActionItem, folderSettingsAction); + const folderSettingsAction = new Action('folderSettings', localize('folderSettings', "Folder"), '.settings-tab', false, (folder: IWorkspaceFolder) => this.updateTarget(folder.uri)); + this.folderSettings = this.instantiationService.createInstance(FolderSettingsActionViewItem, folderSettingsAction); this.update(); @@ -551,14 +551,14 @@ export class SettingsTargetsWidget extends Widget { setResultCount(settingsTarget: SettingsTarget, count: number): void { if (settingsTarget === ConfigurationTarget.WORKSPACE) { - let label = localize('workspaceSettings', "Workspace Settings"); + let label = localize('workspaceSettings', "Workspace"); if (count) { label += ` (${count})`; } this.workspaceSettings.label = label; } else if (settingsTarget === ConfigurationTarget.USER_LOCAL) { - let label = localize('userSettings', "User Settings"); + let label = localize('userSettings', "User"); if (count) { label += ` (${count})`; } diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index 8749fd5dbac..3a0f28506c6 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -159,6 +159,11 @@ export const tocData: ITOCEntry = { id: 'features/comments', label: localize('comments', "Comments"), settings: ['comments.*'] + }, + { + id: 'features/remote', + label: localize('remote', "Remote"), + settings: ['remote.*'] } ] }, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index ef164507896..8e51995e8aa 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -12,8 +12,8 @@ import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configur import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { ITOCEntry, knownAcronyms } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; -import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; +import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; export const ONLINE_SERVICES_SETTING_TAG = 'usesOnlineServices'; @@ -142,11 +142,15 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { const displayValue = isConfigured ? inspected[targetSelector] : inspected.default; const overriddenScopeList: string[] = []; - if (targetSelector === 'user' && typeof inspected.workspace !== 'undefined') { + if (targetSelector !== 'workspace' && typeof inspected.workspace !== 'undefined') { overriddenScopeList.push(localize('workspace', "Workspace")); } - if (targetSelector === 'workspace' && typeof inspected.user !== 'undefined') { + if (targetSelector !== 'userRemote' && typeof inspected.userRemote !== 'undefined') { + overriddenScopeList.push(localize('remote', "Remote")); + } + + if (targetSelector !== 'userLocal' && typeof inspected.userLocal !== 'undefined') { overriddenScopeList.push(localize('user', "User")); } @@ -228,6 +232,10 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { return this.setting.scope === ConfigurationScope.WINDOW || this.setting.scope === ConfigurationScope.RESOURCE; } + if (configTarget === ConfigurationTarget.USER_REMOTE) { + return this.setting.scope === ConfigurationScope.MACHINE || this.setting.scope === ConfigurationScope.WINDOW || this.setting.scope === ConfigurationScope.RESOURCE; + } + return true; } @@ -350,8 +358,17 @@ export class SettingsTreeModel { interface IInspectResult { isConfigured: boolean; - inspected: any; - targetSelector: string; + inspected: { + default: any, + user: any, + userLocal?: any, + userRemote?: any, + workspace?: any, + workspaceFolder?: any, + memory?: any, + value: any, + }; + targetSelector: 'userLocal' | 'userRemote' | 'workspace' | 'workspaceFolder'; } function inspectSetting(key: string, target: SettingsTarget, configurationService: IConfigurationService): IInspectResult { @@ -388,10 +405,10 @@ export function settingKeyToDisplayFormat(key: string, groupId = ''): { category function wordifyKey(key: string): string { return key - .replace(/\.([a-z])/g, (match, p1) => ` › ${p1.toUpperCase()}`) - .replace(/([a-z])([A-Z])/g, '$1 $2') // fooBar => foo Bar - .replace(/^[a-z]/g, match => match.toUpperCase()) // foo => Foo - .replace(/\b\w+\b/g, match => { + .replace(/\.([a-z0-9])/g, (match, p1) => ` › ${p1.toUpperCase()}`) // Replace dot with spaced '>' + .replace(/([a-z0-9])([A-Z])/g, '$1 $2') // Camel case to spacing, fooBar => foo Bar + .replace(/^[a-z]/g, match => match.toUpperCase()) // Upper casing all first letters, foo => Foo + .replace(/\b\w+\b/g, match => { // Upper casing known acronyms return knownAcronyms.has(match.toLowerCase()) ? match.toUpperCase() : match; diff --git a/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts index 724b7b76cbf..c5890ae1ee1 100644 --- a/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts @@ -13,7 +13,7 @@ import * as nls from 'vs/nls'; import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { WorkbenchStateContext, RemoteAuthorityContext } from 'vs/workbench/common/contextkeys'; +import { WorkbenchStateContext, RemoteAuthorityContext } from 'vs/workbench/browser/contextkeys'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -418,14 +418,14 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon .then(() => { const remoteAuthority = environmentService.configuration.remoteAuthority; const hostLabel = labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAuthority) || remoteAuthority; - const label = nls.localize('openRemoteSettings', "Open User Settings ({0})", hostLabel); + const label = nls.localize('openRemoteSettings', "Open Remote Settings ({0})", hostLabel); CommandsRegistry.registerCommand(OpenRemoteSettingsAction.ID, serviceAccessor => { serviceAccessor.get(IInstantiationService).createInstance(OpenRemoteSettingsAction, OpenRemoteSettingsAction.ID, label).run(); }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: OpenRemoteSettingsAction.ID, - title: { value: label, original: `Preferences: Open User Settings (${hostLabel})` }, + title: { value: label, original: `Preferences: Open Remote Settings (${hostLabel})` }, category: nls.localize('preferencesCategory', "Preferences") }, when: RemoteAuthorityContext.notEqualsTo('') diff --git a/src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts index 1a9d2831e0d..7ce3a1e0211 100644 --- a/src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts @@ -16,7 +16,7 @@ import { isArray, withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/settingsEditor2'; import { localize } from 'vs/nls'; -import { ConfigurationTarget, ConfigurationTargetToString, IConfigurationOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -36,7 +36,7 @@ import { AbstractSettingRenderer, ISettingLinkClickEvent, ISettingOverrideClickE import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { settingsTextInputBorder } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, EXTENSION_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, EXTENSION_SETTING_TAG, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingsEditorOptions, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; @@ -205,28 +205,28 @@ export class SettingsEditor2 extends BaseEditor { this.updateStyles(); } - setInput(input: SettingsEditor2Input, options: SettingsEditorOptions, token: CancellationToken): Promise { + setInput(input: SettingsEditor2Input, options: SettingsEditorOptions | null, token: CancellationToken): Promise { this.inSettingsEditorContextKey.set(true); return super.setInput(input, options, token) .then(() => new Promise(process.nextTick)) // Force setInput to be async .then(() => { - if (!options) { - if (!this.viewState.settingsTarget) { - // Persist? - options = SettingsEditorOptions.create({ target: ConfigurationTarget.USER_LOCAL }); + return this.render(token); + }) + .then(() => { + options = options || SettingsEditorOptions.create({}); + + if (!this.viewState.settingsTarget) { + if (!options.target) { + options.target = ConfigurationTarget.USER_LOCAL; } - } else if (!options.target) { - options.target = ConfigurationTarget.USER_LOCAL; } + this._setOptions(options); this._register(input.onDispose(() => { this.searchWidget.setValue(''); })); - return this.render(token); - }) - .then(() => { // Init TOC selection this.updateTreeScrollSync(); @@ -255,17 +255,15 @@ export class SettingsEditor2 extends BaseEditor { } private _setOptions(options: SettingsEditorOptions): void { - if (!options) { - return; - } - if (options.query) { this.searchWidget.setValue(options.query); } const target: SettingsTarget = options.folderUri || options.target; - this.settingsTargetsWidget.settingsTarget = target; - this.viewState.settingsTarget = target; + if (target) { + this.settingsTargetsWidget.settingsTarget = target; + this.viewState.settingsTarget = target; + } } clearInput(): void { @@ -616,11 +614,11 @@ export class SettingsEditor2 extends BaseEditor { this.settingsTree.reveal(element); })); this._register(this.settingRenderers.onDidClickOverrideElement((element: ISettingOverrideClickEvent) => { - if (ConfigurationTargetToString(ConfigurationTarget.WORKSPACE) === element.scope.toUpperCase()) { + if (element.scope.toLowerCase() === 'workspace') { this.settingsTargetsWidget.updateTarget(ConfigurationTarget.WORKSPACE); - } else if (ConfigurationTargetToString(ConfigurationTarget.USER_LOCAL) === element.scope.toUpperCase()) { + } else if (element.scope.toLowerCase() === 'user') { this.settingsTargetsWidget.updateTarget(ConfigurationTarget.USER_LOCAL); - } else if (ConfigurationTargetToString(ConfigurationTarget.USER_REMOTE) === element.scope.toUpperCase()) { + } else if (element.scope.toLowerCase() === 'remote') { this.settingsTargetsWidget.updateTarget(ConfigurationTarget.USER_REMOTE); } @@ -1067,6 +1065,7 @@ export class SettingsEditor2 extends BaseEditor { this.searchInProgress = null; } + this.tocTree.setFocus([]); this.viewState.filterToCategory = undefined; this.tocTreeModel.currentSearchModel = this.searchResultModel; this.onSearchModeToggled(); @@ -1208,7 +1207,7 @@ export class SettingsEditor2 extends BaseEditor { this.tocTreeModel.update(); } - this.tocTree.setSelection([]); + this.tocTree.setFocus([]); this.viewState.filterToCategory = undefined; this.tocTree.expandAll(); @@ -1219,11 +1218,14 @@ export class SettingsEditor2 extends BaseEditor { } private renderResultCountMessages() { - if (!this.currentSettingsModel || !this.searchResultModel) { - this.countElement.style.display = 'none'; + if (!this.currentSettingsModel) { return; } + if (!this.searchResultModel) { + this.countElement.style.display = 'none'; + } + if (this.tocTreeModel && this.tocTreeModel.settingsTreeRoot) { const count = this.tocTreeModel.settingsTreeRoot.count; switch (count) { diff --git a/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts b/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts index 2b7c77431c9..35b36215171 100644 --- a/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts +++ b/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts @@ -101,6 +101,20 @@ suite('SettingsTree', () => { category: 'Something Else', label: 'Etc' }); + + assert.deepEqual( + settingKeyToDisplayFormat('foo.1leading.number'), + { + category: 'Foo › 1leading', + label: 'Number' + }); + + assert.deepEqual( + settingKeyToDisplayFormat('foo.1Leading.number'), + { + category: 'Foo › 1 Leading', + label: 'Number' + }); }); test('parseQuery', () => { diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index 4226adc935a..5ee73f788d1 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -192,7 +192,7 @@ class CommandPaletteEditorAction extends EditorAction { id: ShowAllCommandsAction.ID, label: nls.localize('showCommands.label', "Command Palette..."), alias: 'Command Palette', - precondition: null, + precondition: undefined, menuOpts: { group: 'z_commands', order: 1 diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts index 6af674415aa..724509b2d05 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts @@ -139,8 +139,8 @@ class GotoLineEntry extends EditorQuickOpenEntry { return this.runPreview(); } - getInput(): IEditorInput | null { - return types.withUndefinedAsNull(this.editorService.activeEditor); + getInput(): IEditorInput | undefined { + return this.editorService.activeEditor; } getOptions(pinned?: boolean): ITextEditorOptions { diff --git a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts index e81454d8db5..97fba151082 100644 --- a/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts @@ -288,8 +288,8 @@ class SymbolEntry extends EditorQuickOpenEntryGroup { return this.range; } - getInput(): IEditorInput | null { - return types.withUndefinedAsNull(this.editorService.activeEditor); + getInput(): IEditorInput | undefined { + return this.editorService.activeEditor; } getOptions(pinned?: boolean): ITextEditorOptions { diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index adec1c2ae4b..2a22287760a 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -35,11 +35,11 @@ import { peekViewBorder, peekViewTitleBackground, peekViewTitleForeground, peekV import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Action, IAction, ActionRunner } from 'vs/base/common/actions'; -import { IActionBarOptions, ActionsOrientation, IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionBarOptions, ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { basename } from 'vs/base/common/resources'; import { MenuId, IMenuService, IMenu, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; -import { MenuItemActionItem, fillInActionBarActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { fillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IChange, IEditorModel, ScrollType, IEditorContribution, IDiffEditorModel } from 'vs/editor/common/editorCommon'; import { OverviewRulerLane, ITextModel, IModelDecorationOptions } from 'vs/editor/common/model'; import { sortedDiff, firstIndex } from 'vs/base/common/arrays'; @@ -50,21 +50,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { INotificationService } from 'vs/platform/notification/common/notification'; import { createStyleSheet } from 'vs/base/browser/dom'; -// TODO@Joao -// Need to subclass MenuItemActionItem in order to respect -// the action context coming from any action bar, without breaking -// existing users -class DiffMenuItemActionItem extends MenuItemActionItem { - - onClick(event: MouseEvent): void { - event.preventDefault(); - event.stopPropagation(); - - this.actionRunner.run(this._commandAction, this._context) - .then(undefined, err => this._notificationService.error(err)); - } -} - class DiffActionRunner extends ActionRunner { runAction(action: IAction, context: any): Promise { @@ -289,17 +274,17 @@ class DirtyDiffWidget extends PeekViewWidget { return { actionRunner, - actionItemProvider: action => this.getActionItem(action), + actionViewItemProvider: action => this.getActionViewItem(action), orientation: ActionsOrientation.HORIZONTAL_REVERSE }; } - getActionItem(action: IAction): IActionItem | undefined { + getActionViewItem(action: IAction): IActionViewItem | undefined { if (!(action instanceof MenuItemAction)) { return undefined; } - return new DiffMenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + return new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); } protected _fillBody(container: HTMLElement): void { @@ -382,7 +367,7 @@ export class ShowPreviousChangeAction extends EditorAction { id: 'editor.action.dirtydiff.previous', label: nls.localize('show previous change', "Show Previous Change"), alias: 'Show Previous Change', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.F3, weight: KeybindingWeight.EditorContrib } }); } @@ -416,7 +401,7 @@ export class ShowNextChangeAction extends EditorAction { id: 'editor.action.dirtydiff.next', label: nls.localize('show next change', "Show Next Change"), alias: 'Show Next Change', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Alt | KeyCode.F3, weight: KeybindingWeight.EditorContrib } }); } @@ -469,7 +454,7 @@ export class MoveToPreviousChangeAction extends EditorAction { id: 'workbench.action.editor.previousChange', label: nls.localize('move to previous change', "Move to Previous Change"), alias: 'Move to Previous Change', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.F5, weight: KeybindingWeight.EditorContrib } }); } @@ -511,7 +496,7 @@ export class MoveToNextChangeAction extends EditorAction { id: 'workbench.action.editor.nextChange', label: nls.localize('move to next change', "Move to Next Change"), alias: 'Move to Next Change', - precondition: null, + precondition: undefined, kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Alt | KeyCode.F5, weight: KeybindingWeight.EditorContrib } }); } diff --git a/src/vs/workbench/contrib/scm/browser/scmMenus.ts b/src/vs/workbench/contrib/scm/browser/scmMenus.ts index cac3550b561..6234e5b1eff 100644 --- a/src/vs/workbench/contrib/scm/browser/scmMenus.ts +++ b/src/vs/workbench/contrib/scm/browser/scmMenus.ts @@ -9,7 +9,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { IAction } from 'vs/base/common/actions'; -import { fillInContextMenuActions, fillInActionBarActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { fillInContextMenuActions, fillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ISCMProvider, ISCMResource, ISCMResourceGroup } from 'vs/workbench/contrib/scm/common/scm'; import { isSCMResource } from './scmUtil'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 0b0cba6c374..caa82d3520f 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -24,10 +24,10 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { MenuItemAction, IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; -import { IAction, Action, IActionItem, ActionRunner } from 'vs/base/common/actions'; -import { fillInContextMenuActions, ContextAwareMenuItemActionItem, fillInActionBarActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { IAction, Action, IActionViewItem, ActionRunner } from 'vs/base/common/actions'; +import { fillInContextMenuActions, ContextAwareMenuEntryActionViewItem, fillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { SCMMenus } from './scmMenus'; -import { ActionBar, IActionItemProvider, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; import { isSCMResource } from './scmUtil'; import { attachBadgeStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; @@ -94,7 +94,7 @@ class StatusBarAction extends Action { } } -class StatusBarActionItem extends ActionItem { +class StatusBarActionViewItem extends ActionViewItem { constructor(action: StatusBarAction) { super(null, action, {}); @@ -160,7 +160,7 @@ class ProviderRenderer implements IListRenderer new StatusBarActionItem(a as StatusBarAction) }); + const actionBar = new ActionBar(provider, { actionViewItemProvider: a => new StatusBarActionViewItem(a as StatusBarAction) }); const disposable = Disposable.None; const templateDisposable = combinedDisposable([actionBar, badgeStyler]); @@ -366,7 +366,7 @@ class ResourceGroupRenderer implements IListRenderer constructor( private labels: ResourceLabels, - private actionItemProvider: IActionItemProvider, + private actionViewItemProvider: IActionViewItemProvider, private getSelectedResources: () => ISCMResource[], private themeService: IThemeService, private menus: SCMMenus @@ -466,7 +466,7 @@ class ResourceRenderer implements IListRenderer const fileLabel = this.labels.create(name); const actionsContainer = append(fileLabel.element, $('.actions')); const actionBar = new ActionBar(actionsContainer, { - actionItemProvider: this.actionItemProvider, + actionViewItemProvider: this.actionViewItemProvider, actionRunner: new MultipleSelectionActionRunner(this.getSelectedResources) }); @@ -827,14 +827,14 @@ export class RepositoryPanel extends ViewletPanel { const delegate = new ProviderListDelegate(); - const actionItemProvider = (action: IAction) => this.getActionItem(action); + const actionViewItemProvider = (action: IAction) => this.getActionViewItem(action); this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); this.disposables.push(this.listLabels); const renderers = [ - new ResourceGroupRenderer(actionItemProvider, this.themeService, this.menus), - new ResourceRenderer(this.listLabels, actionItemProvider, () => this.getSelectedResources(), this.themeService, this.menus) + new ResourceGroupRenderer(actionViewItemProvider, this.themeService, this.menus), + new ResourceRenderer(this.listLabels, actionViewItemProvider, () => this.getSelectedResources(), this.themeService, this.menus) ]; this.list = this.instantiationService.createInstance(WorkbenchList, this.listContainer, delegate, renderers, { @@ -918,12 +918,12 @@ export class RepositoryPanel extends ViewletPanel { return this.menus.getTitleSecondaryActions(); } - getActionItem(action: IAction): IActionItem | undefined { + getActionViewItem(action: IAction): IActionViewItem | undefined { if (!(action instanceof MenuItemAction)) { return undefined; } - return new ContextAwareMenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + return new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); } getActionsContext(): any { @@ -1227,12 +1227,12 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { } } - getActionItem(action: IAction): IActionItem | undefined { + getActionViewItem(action: IAction): IActionViewItem | undefined { if (!(action instanceof MenuItemAction)) { return undefined; } - return new ContextAwareMenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + return new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); } getActions(): IAction[] { diff --git a/src/vs/workbench/contrib/search/browser/openFileHandler.ts b/src/vs/workbench/contrib/search/browser/openFileHandler.ts index 8746f212497..933e47b2e73 100644 --- a/src/vs/workbench/contrib/search/browser/openFileHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openFileHandler.ts @@ -203,7 +203,7 @@ export class OpenFileHandler extends QuickOpenHandler { const queryOptions: IFileQueryBuilderOptions = { _reason: 'openFileHandler', extraFileResources: getOutOfWorkspaceEditorResources(this.editorService, this.contextService), - filePattern: query.value, + filePattern: query.original, cacheKey }; diff --git a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts index 32f606bb2ca..6599e4982e7 100644 --- a/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts @@ -25,7 +25,6 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Schemas } from 'vs/base/common/network'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { withUndefinedAsNull } from 'vs/base/common/types'; class SymbolEntry extends EditorQuickOpenEntry { private bearingResolve: Promise; @@ -49,7 +48,7 @@ class SymbolEntry extends EditorQuickOpenEntry { return nls.localize('entryAriaLabel', "{0}, symbols picker", this.getLabel()); } - getDescription(): string | null { + getDescription(): string | undefined { const containerName = this.bearing.containerName; if (this.bearing.location.uri) { if (containerName) { @@ -59,7 +58,7 @@ class SymbolEntry extends EditorQuickOpenEntry { return this.labelService.getUriLabel(this.bearing.location.uri, { relative: true }); } - return withUndefinedAsNull(containerName); + return containerName; } getIcon(): string { diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 3be91632807..f675087eeb4 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -478,6 +478,14 @@ Registry.as(ViewletExtensions.Viewlets).registerViewlet(new Vie 1 )); +Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( + SearchPanel, + PANEL_ID, + nls.localize('name', "Search"), + 'search', + 10 +)); + class RegisterSearchViewContribution implements IWorkbenchContribution { constructor( diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 2a602bafd96..6b13724e61b 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -23,7 +23,7 @@ import 'vs/css!./media/searchview'; import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import * as nls from 'vs/nls'; -import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -682,11 +682,7 @@ export class SearchView extends ViewletPanel { })); } - private onContextMenu(e: ITreeContextMenuEvent): void { - if (!e.element) { - return; - } - + private onContextMenu(e: ITreeContextMenuEvent): void { if (!this.contextMenu) { this.contextMenu = this._register(this.menuService.createMenu(MenuId.SearchContext, this.contextKeyService)); } @@ -1327,8 +1323,6 @@ export class SearchView extends ViewletPanel { // Indicate as status to ARIA aria.status(message); - dom.hide(this.resultsElement); - const messageEl = this.clearMessage(); const p = dom.append(messageEl, $('p', undefined, message)); @@ -1363,6 +1357,7 @@ export class SearchView extends ViewletPanel { if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.showSearchWithoutFolderMessage(); } + this.reLayout(); } else { this.viewModel.searchResult.toggleHighlights(this.isVisible()); // show highlights diff --git a/src/vs/workbench/contrib/search/common/queryBuilder.ts b/src/vs/workbench/contrib/search/common/queryBuilder.ts index fbb2e30e685..15b93dff778 100644 --- a/src/vs/workbench/contrib/search/common/queryBuilder.ts +++ b/src/vs/workbench/contrib/search/common/queryBuilder.ts @@ -274,7 +274,7 @@ export class QueryBuilder { * Split search paths (./ or ../ or absolute paths in the includePatterns) into absolute paths and globs applied to those paths */ private expandSearchPathPatterns(searchPaths: string[]): ISearchPathPattern[] { - if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY || !searchPaths || !searchPaths.length) { + if (!searchPaths || !searchPaths.length) { // No workspace => ignore search paths return []; } diff --git a/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts index 12d5a85cafb..0c14fd010f7 100644 --- a/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts @@ -11,7 +11,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IFolderQuery, IPatternInfo, QueryType, ITextQuery, IFileQuery } from 'vs/workbench/services/search/common/search'; -import { IWorkspaceContextService, toWorkspaceFolders, Workspace } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, toWorkspaceFolder, Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { ISearchPathsInfo, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { TestContextService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; @@ -24,6 +24,7 @@ suite('QueryBuilder', () => { const PATTERN_INFO: IPatternInfo = { pattern: 'a' }; const ROOT_1 = fixPath('/foo/root1'); const ROOT_1_URI = getUri(ROOT_1); + const WS_CONFIG_PATH = getUri('/bar/test.code-workspace'); // location of the workspace file (not important except that it is a file URI) let instantiationService: TestInstantiationService; let queryBuilder: QueryBuilder; @@ -40,7 +41,7 @@ suite('QueryBuilder', () => { instantiationService.stub(IConfigurationService, mockConfigService); mockContextService = new TestContextService(); - mockWorkspace = new Workspace('workspace', toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }])); + mockWorkspace = new Workspace('workspace', [toWorkspaceFolder(ROOT_1_URI)]); mockContextService.setWorkspace(mockWorkspace); instantiationService.stub(IWorkspaceContextService, mockContextService); @@ -277,7 +278,7 @@ suite('QueryBuilder', () => { const ROOT_2_URI = getUri(ROOT_2); const ROOT_3 = fixPath('/project/root3'); const ROOT_3_URI = getUri(ROOT_3); - mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: ROOT_2_URI.fsPath }, { path: ROOT_3_URI.fsPath }]); + mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: ROOT_2_URI.fsPath }, { path: ROOT_3_URI.fsPath }], WS_CONFIG_PATH); mockWorkspace.configuration = uri.file(fixPath('/config')); mockConfigService.setUserConfiguration('search', { @@ -689,7 +690,7 @@ suite('QueryBuilder', () => { test('relative includes w/two root folders', () => { const ROOT_2 = '/project/root2'; - mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }]); + mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }], WS_CONFIG_PATH); mockWorkspace.configuration = uri.file(fixPath('config')); const cases: [string, ISearchPathsInfo][] = [ @@ -730,7 +731,7 @@ suite('QueryBuilder', () => { test('include ./foldername', () => { const ROOT_2 = '/project/root2'; const ROOT_1_FOLDERNAME = 'foldername'; - mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath, name: ROOT_1_FOLDERNAME }, { path: getUri(ROOT_2).fsPath }]); + mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath, name: ROOT_1_FOLDERNAME }, { path: getUri(ROOT_2).fsPath }], WS_CONFIG_PATH); mockWorkspace.configuration = uri.file(fixPath('config')); const cases: [string, ISearchPathsInfo][] = [ @@ -758,7 +759,7 @@ suite('QueryBuilder', () => { test('relative includes w/multiple ambiguous root folders', () => { const ROOT_2 = '/project/rootB'; const ROOT_3 = '/otherproject/rootB'; - mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }, { path: getUri(ROOT_3).fsPath }]); + mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }, { path: getUri(ROOT_3).fsPath }], WS_CONFIG_PATH); mockWorkspace.configuration = uri.file(fixPath('/config')); const cases: [string, ISearchPathsInfo][] = [ diff --git a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts index 6887801c023..7340ad5820b 100644 --- a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts @@ -7,10 +7,8 @@ import * as nls from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IWindowService } from 'vs/platform/windows/common/windows'; -import { join, basename, dirname, extname } from 'vs/base/common/path'; +import { join, basename, extname } from 'vs/base/common/path'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { timeout } from 'vs/base/common/async'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; @@ -19,19 +17,20 @@ import { IQuickPickItem, IQuickInputService, QuickPickInput } from 'vs/platform/ import { SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IFileService } from 'vs/platform/files/common/files'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { isValidBasename } from 'vs/base/common/extpath'; +import { joinPath } from 'vs/base/common/resources'; const id = 'workbench.action.openSnippets'; namespace ISnippetPick { export function is(thing: object): thing is ISnippetPick { - return thing && typeof (thing).filepath === 'string'; + return thing && URI.isUri((thing).filepath); } } interface ISnippetPick extends IQuickPickItem { - filepath: string; + filepath: URI; hint?: true; } @@ -71,7 +70,7 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir existing.push({ label: basename(file.location.fsPath), - filepath: file.location.fsPath, + filepath: file.location, description: names.size === 0 ? nls.localize('global.scope', "(global)") : nls.localize('global.1', "({0})", values(names).join(', ')) @@ -83,7 +82,7 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir existing.push({ label: basename(file.location.fsPath), description: `(${modeService.getLanguageName(mode)})`, - filepath: file.location.fsPath + filepath: file.location }); seen.add(mode); } @@ -96,15 +95,15 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir future.push({ label: mode, description: `(${label})`, - filepath: join(dir, `${mode}.json`), + filepath: URI.file(join(dir, `${mode}.json`)), hint: true }); } } existing.sort((a, b) => { - let a_ext = extname(a.filepath); - let b_ext = extname(b.filepath); + let a_ext = extname(a.filepath.path); + let b_ext = extname(b.filepath.path); if (a_ext === b_ext) { return a.label.localeCompare(b.label); } else if (a_ext === '.code-snippets') { @@ -121,24 +120,39 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir return { existing, future }; } -async function createSnippetFile(scope: string, defaultPath: URI, windowService: IWindowService, notificationService: INotificationService, fileService: IFileService, textFileService: ITextFileService, opener: IOpenerService) { +async function createSnippetFile(scope: string, defaultPath: URI, quickInputService: IQuickInputService, fileService: IFileService, textFileService: ITextFileService, opener: IOpenerService) { + + function createSnippetUri(input: string) { + const filename = extname(input) !== '.code-snippets' + ? `${input}.code-snippets` + : input; + return joinPath(defaultPath, filename); + } await fileService.createFolder(defaultPath); - await timeout(100); // ensure quick pick closes... - const path = await windowService.showSaveDialog({ - defaultPath: defaultPath.fsPath, - filters: [{ name: 'Code Snippets', extensions: ['code-snippets'] }] + const input = await quickInputService.input({ + placeHolder: nls.localize('name', "Type snippet file name"), + async validateInput(input) { + if (!input) { + return nls.localize('bad_name1', "Invalid file name"); + } + if (!isValidBasename(input)) { + return nls.localize('bad_name2', "'{0}' is not a valid file name", input); + } + if (await fileService.exists(createSnippetUri(input))) { + return nls.localize('bad_name3', "'{0}' already exists", input); + } + return undefined; + } }); - if (!path) { - return undefined; - } - const resource = URI.file(path); - if (dirname(resource.fsPath) !== defaultPath.fsPath) { - notificationService.error(nls.localize('badPath', "Snippets must be inside this folder: '{0}'. ", defaultPath.fsPath)); + + if (!input) { return undefined; } + const resource = createSnippetUri(input); + await textFileService.write(resource, [ '{', '\t// Place your ' + scope + ' snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and ', @@ -165,7 +179,7 @@ async function createSnippetFile(scope: string, defaultPath: URI, windowService: } async function createLanguageSnippetFile(pick: ISnippetPick, fileService: IFileService, textFileService: ITextFileService) { - if (await fileService.exists(URI.file(pick.filepath))) { + if (await fileService.exists(pick.filepath)) { return; } const contents = [ @@ -185,7 +199,7 @@ async function createLanguageSnippetFile(pick: ISnippetPick, fileService: IFileS '\t// }', '}' ].join('\n'); - await textFileService.write(URI.file(pick.filepath), contents); + await textFileService.write(pick.filepath, contents); } CommandsRegistry.registerCommand(id, async (accessor): Promise => { @@ -193,10 +207,8 @@ CommandsRegistry.registerCommand(id, async (accessor): Promise => { const snippetService = accessor.get(ISnippetsService); const quickInputService = accessor.get(IQuickInputService); const opener = accessor.get(IOpenerService); - const windowService = accessor.get(IWindowService); const modeService = accessor.get(IModeService); const envService = accessor.get(IEnvironmentService); - const notificationService = accessor.get(INotificationService); const workspaceService = accessor.get(IWorkspaceContextService); const fileService = accessor.get(IFileService); const textFileService = accessor.get(ITextFileService); @@ -233,14 +245,14 @@ CommandsRegistry.registerCommand(id, async (accessor): Promise => { }); if (globalSnippetPicks.indexOf(pick as SnippetPick) >= 0) { - return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, windowService, notificationService, fileService, textFileService, opener); + return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); } else if (workspaceSnippetPicks.indexOf(pick as SnippetPick) >= 0) { - return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, windowService, notificationService, fileService, textFileService, opener); + return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); } else if (ISnippetPick.is(pick)) { if (pick.hint) { await createLanguageSnippetFile(pick, fileService, textFileService); } - return opener.open(URI.file(pick.filepath)); + return opener.open(pick.filepath); } }); diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index dc3d82ca9fd..9b298b9d560 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -198,7 +198,7 @@ export class SnippetFile { load(): Promise { if (!this._loadPromise) { - this._loadPromise = Promise.resolve(this._fileService.resolveContent(this.location, { encoding: 'utf8' })).then(content => { + this._loadPromise = Promise.resolve(this._fileService.readFile(this.location)).then(content => { const data = jsonParse(content.value.toString()); if (typeof data === 'object') { forEach(data, entry => { diff --git a/src/vs/workbench/contrib/stats/node/workspaceStats.ts b/src/vs/workbench/contrib/stats/node/workspaceStats.ts index da188140e8f..c160f33c9bd 100644 --- a/src/vs/workbench/contrib/stats/node/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/node/workspaceStats.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import * as crypto from 'crypto'; import { onUnexpectedError } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; -import { IFileService, IFileStat, IResolveFileResult, IContent } from 'vs/platform/files/common/files'; +import { IFileService, IFileStat, IResolveFileResult } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -20,6 +20,7 @@ import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspa import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { joinPath } from 'vs/base/common/resources'; +import { ITextFileService, ITextFileContent } from 'vs/workbench/services/textfile/common/textfiles'; const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/; const SshUrlMatcher = /^([^@:]+@)?([^:]+):(.+)$/; @@ -193,14 +194,14 @@ export function getHashedRemotesFromConfig(text: string, stripEndingDotGit: bool }); } -export function getHashedRemotesFromUri(workspaceUri: URI, fileService: IFileService, stripEndingDotGit: boolean = false): Promise { +export function getHashedRemotesFromUri(workspaceUri: URI, fileService: IFileService, textFileService: ITextFileService, stripEndingDotGit: boolean = false): Promise { const path = workspaceUri.path; const uri = workspaceUri.with({ path: `${path !== '/' ? path : ''}/.git/config` }); return fileService.exists(uri).then(exists => { if (!exists) { return []; } - return fileService.resolveContent(uri, { acceptTextOnly: true }).then( + return textFileService.read(uri, { acceptTextOnly: true }).then( content => getHashedRemotesFromConfig(content.value, stripEndingDotGit), err => [] // ignore missing or binary file ); @@ -221,7 +222,8 @@ export class WorkspaceStats implements IWorkbenchContribution { @IWindowService private readonly windowService: IWindowService, @INotificationService private readonly notificationService: INotificationService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @IStorageService private readonly storageService: IStorageService + @IStorageService private readonly storageService: IStorageService, + @ITextFileService private readonly textFileService: ITextFileService ) { this.report(); } @@ -244,8 +246,7 @@ export class WorkspaceStats implements IWorkbenchContribution { /* __GDPR__FRAGMENT__ "WorkspaceTags" : { - "workbench.filesToOpen" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbench.filesToCreate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workbench.filesToOpenOrCreate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workbench.filesToDiff" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "workspace.roots" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -350,9 +351,8 @@ export class WorkspaceStats implements IWorkbenchContribution { tags['workspace.id'] = workspaceId; - const { filesToOpen, filesToCreate, filesToDiff } = configuration; - tags['workbench.filesToOpen'] = filesToOpen && filesToOpen.length || 0; - tags['workbench.filesToCreate'] = filesToCreate && filesToCreate.length || 0; + const { filesToOpenOrCreate, filesToDiff } = configuration; + tags['workbench.filesToOpenOrCreate'] = filesToOpenOrCreate && filesToOpenOrCreate.length || 0; tags['workbench.filesToDiff'] = filesToDiff && filesToDiff.length || 0; const isEmpty = state === WorkbenchState.EMPTY; @@ -434,7 +434,7 @@ export class WorkspaceStats implements IWorkbenchContribution { tags['workspace.android.cpp'] = true; } - function getFilePromises(filename: string, fileService: IFileService, contentHandler: (content: IContent) => void): Promise[] { + function getFilePromises(filename: string, fileService: IFileService, textFileService: ITextFileService, contentHandler: (content: ITextFileContent) => void): Promise[] { return !nameSet.has(filename) ? [] : (folders as URI[]).map(workspaceUri => { const uri = workspaceUri.with({ path: `${workspaceUri.path !== '/' ? workspaceUri.path : ''}/${filename}` }); return fileService.exists(uri).then(exists => { @@ -442,7 +442,7 @@ export class WorkspaceStats implements IWorkbenchContribution { return undefined; } - return fileService.resolveContent(uri, { acceptTextOnly: true }).then(contentHandler); + return textFileService.read(uri, { acceptTextOnly: true }).then(contentHandler); }, err => { // Ignore missing file }); @@ -468,7 +468,7 @@ export class WorkspaceStats implements IWorkbenchContribution { } } - const requirementsTxtPromises = getFilePromises('requirements.txt', this.fileService, content => { + const requirementsTxtPromises = getFilePromises('requirements.txt', this.fileService, this.textFileService, content => { const dependencies: string[] = content.value.split(/\r\n|\r|\n/); for (let dependency of dependencies) { // Dependencies in requirements.txt can have 3 formats: `foo==3.1, foo>=3.1, foo` @@ -479,7 +479,7 @@ export class WorkspaceStats implements IWorkbenchContribution { } }); - const pipfilePromises = getFilePromises('pipfile', this.fileService, content => { + const pipfilePromises = getFilePromises('pipfile', this.fileService, this.textFileService, content => { let dependencies: string[] = content.value.split(/\r\n|\r|\n/); // We're only interested in the '[packages]' section of the Pipfile @@ -499,7 +499,7 @@ export class WorkspaceStats implements IWorkbenchContribution { }); - const packageJsonPromises = getFilePromises('package.json', this.fileService, content => { + const packageJsonPromises = getFilePromises('package.json', this.fileService, this.textFileService, content => { try { const packageJsonContents = JSON.parse(content.value); if (packageJsonContents['dependencies']) { @@ -584,11 +584,9 @@ export class WorkspaceStats implements IWorkbenchContribution { return folder && [folder]; } - private findFolder({ filesToOpen, filesToCreate, filesToDiff }: IWindowConfiguration): URI | undefined { - if (filesToOpen && filesToOpen.length) { - return this.parentURI(filesToOpen[0].fileUri); - } else if (filesToCreate && filesToCreate.length) { - return this.parentURI(filesToCreate[0].fileUri); + private findFolder({ filesToOpenOrCreate, filesToDiff }: IWindowConfiguration): URI | undefined { + if (filesToOpenOrCreate && filesToOpenOrCreate.length) { + return this.parentURI(filesToOpenOrCreate[0].fileUri); } else if (filesToDiff && filesToDiff.length) { return this.parentURI(filesToDiff[0].fileUri); } @@ -624,7 +622,7 @@ export class WorkspaceStats implements IWorkbenchContribution { if (!exists) { return []; } - return this.fileService.resolveContent(uri, { acceptTextOnly: true }).then( + return this.textFileService.read(uri, { acceptTextOnly: true }).then( content => getDomainsOfRemotes(content.value, SecondLevelDomainWhitelist), err => [] // ignore missing or binary file ); @@ -644,7 +642,7 @@ export class WorkspaceStats implements IWorkbenchContribution { private reportRemotes(workspaceUris: URI[]): void { Promise.all(workspaceUris.map(workspaceUri => { - return getHashedRemotesFromUri(workspaceUri, this.fileService, true); + return getHashedRemotesFromUri(workspaceUri, this.fileService, this.textFileService, true); })).then(hashedRemotes => { /* __GDPR__ "workspace.hashedRemotes" : { @@ -693,7 +691,7 @@ export class WorkspaceStats implements IWorkbenchContribution { if (!exists) { return false; } - return this.fileService.resolveContent(uri, { acceptTextOnly: true }).then( + return this.textFileService.read(uri, { acceptTextOnly: true }).then( content => !!content.value.match(/azure/i), err => false ); diff --git a/src/vs/workbench/contrib/tasks/browser/quickOpen.ts b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts index 8d7b2e358de..ea5960b4eab 100644 --- a/src/vs/workbench/contrib/tasks/browser/quickOpen.ts +++ b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts @@ -28,13 +28,13 @@ export class TaskEntry extends Model.QuickOpenEntry { return this.task._label; } - public getDescription(): string | null { + public getDescription(): string | undefined { if (!this.taskService.needsFolderQualification()) { - return null; + return undefined; } let workspaceFolder = this.task.getWorkspaceFolder(); if (!workspaceFolder) { - return null; + return undefined; } return `${workspaceFolder.name}`; } diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts index 75c271d9a42..eac6a87eadb 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts @@ -100,16 +100,16 @@ const presentation: IJSONSchema = { default: false, description: nls.localize('JsonSchema.tasks.presentation.focus', 'Controls whether the panel takes focus. Default is false. If set to true the panel is revealed as well.') }, - revealProblem: { + revealProblems: { type: 'string', enum: ['always', 'onProblem', 'never'], enumDescriptions: [ - nls.localize('JsonSchema.tasks.presentation.revealProblem.always', 'Always reveals the problems panel when this task is executed.'), - nls.localize('JsonSchema.tasks.presentation.revealProblem.onProblem', 'Only reveals the problems panel if a problem is found.'), - nls.localize('JsonSchema.tasks.presentation.revealProblem.never', 'Never reveals the problems panel when this task is executed.'), + nls.localize('JsonSchema.tasks.presentation.revealProblems.always', 'Always reveals the problems panel when this task is executed.'), + nls.localize('JsonSchema.tasks.presentation.revealProblems.onProblem', 'Only reveals the problems panel if a problem is found.'), + nls.localize('JsonSchema.tasks.presentation.revealProblems.never', 'Never reveals the problems panel when this task is executed.'), ], default: 'never', - description: nls.localize('JsonSchema.tasks.presentation.revealProblem', 'Controls whether the problems panel is revealed when running this task or not. Takes precedence over option \"reveal\". Default is \"never\".') + description: nls.localize('JsonSchema.tasks.presentation.revealProblems', 'Controls whether the problems panel is revealed when running this task or not. Takes precedence over option \"reveal\". Default is \"never\".') }, reveal: { type: 'string', @@ -120,7 +120,7 @@ const presentation: IJSONSchema = { nls.localize('JsonSchema.tasks.presentation.reveal.never', 'Never reveals the terminal when this task is executed.'), ], default: 'always', - description: nls.localize('JsonSchema.tasks.presentation.reveals', 'Controls whether the panel running the task is revealed or not. May be overridden by option \"revealProblem\". Default is \"always\".') + description: nls.localize('JsonSchema.tasks.presentation.reveal', 'Controls whether the terminal running the task is revealed or not. May be overridden by option \"revealProblems\". Default is \"always\".') }, panel: { type: 'string', diff --git a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index 8d9c2184962..2f0a4cc5acc 100644 --- a/src/vs/workbench/contrib/tasks/common/problemMatcher.ts +++ b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -265,7 +265,7 @@ abstract class AbstractLineMatcher implements ILineMatcher { if (trim) { value = Strings.trim(value)!; } - data[property] += endOfLine + value; + (data as any)[property] += endOfLine + value; } } @@ -277,7 +277,7 @@ abstract class AbstractLineMatcher implements ILineMatcher { if (trim) { value = Strings.trim(value)!; } - data[property] = value; + (data as any)[property] = value; } } } @@ -894,9 +894,9 @@ export class ProblemPatternParser extends Parser { } function copyProperty(result: ProblemPattern, source: Config.ProblemPattern, resultKey: keyof ProblemPattern, sourceKey: keyof Config.ProblemPattern) { - let value = source[sourceKey]; + const value = source[sourceKey]; if (typeof value === 'number') { - result[resultKey] = value; + (result as any)[resultKey] = value; } } copyProperty(result, value, 'file', 'file'); diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 11721961001..5132339f26d 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -95,7 +95,7 @@ export interface PresentationOptionsConfig { * Controls whether the problems panel is revealed when running this task or not. * Defaults to `RevealKind.Never`. */ - revealProblem?: string; + revealProblems?: string; /** * Controls whether the executed command is printed to the output window or terminal as well. @@ -802,7 +802,7 @@ namespace CommandOptions { namespace CommandConfiguration { export namespace PresentationOptions { - const properties: MetaData[] = [{ property: 'echo' }, { property: 'reveal' }, { property: 'revealProblem' }, { property: 'focus' }, { property: 'panel' }, { property: 'showReuseMessage' }, { property: 'clear' }, { property: 'group' }]; + const properties: MetaData[] = [{ property: 'echo' }, { property: 'reveal' }, { property: 'revealProblems' }, { property: 'focus' }, { property: 'panel' }, { property: 'showReuseMessage' }, { property: 'clear' }, { property: 'group' }]; interface PresentationOptionsShape extends LegacyCommandProperties { presentation?: PresentationOptionsConfig; @@ -811,7 +811,7 @@ namespace CommandConfiguration { export function from(this: void, config: PresentationOptionsShape, context: ParseContext): Tasks.PresentationOptions | undefined { let echo: boolean; let reveal: Tasks.RevealKind; - let revealProblem: Tasks.RevealProblemKind; + let revealProblems: Tasks.RevealProblemKind; let focus: boolean; let panel: Tasks.PanelKind; let showReuseMessage: boolean; @@ -834,8 +834,8 @@ namespace CommandConfiguration { if (Types.isString(presentation.reveal)) { reveal = Tasks.RevealKind.fromString(presentation.reveal); } - if (Types.isString(presentation.revealProblem)) { - revealProblem = Tasks.RevealProblemKind.fromString(presentation.revealProblem); + if (Types.isString(presentation.revealProblems)) { + revealProblems = Tasks.RevealProblemKind.fromString(presentation.revealProblems); } if (Types.isBoolean(presentation.focus)) { focus = presentation.focus; @@ -857,7 +857,7 @@ namespace CommandConfiguration { if (!hasProps) { return undefined; } - return { echo: echo!, reveal: reveal!, revealProblem: revealProblem!, focus: focus!, panel: panel!, showReuseMessage: showReuseMessage!, clear: clear!, group }; + return { echo: echo!, reveal: reveal!, revealProblems: revealProblems!, focus: focus!, panel: panel!, showReuseMessage: showReuseMessage!, clear: clear!, group }; } export function assignProperties(target: Tasks.PresentationOptions, source: Tasks.PresentationOptions | undefined): Tasks.PresentationOptions | undefined { @@ -870,7 +870,7 @@ namespace CommandConfiguration { export function fillDefaults(value: Tasks.PresentationOptions, context: ParseContext): Tasks.PresentationOptions | undefined { let defaultEcho = context.engine === Tasks.ExecutionEngine.Terminal ? true : false; - return _fillDefaults(value, { echo: defaultEcho, reveal: Tasks.RevealKind.Always, revealProblem: Tasks.RevealProblemKind.Never, focus: false, panel: Tasks.PanelKind.Shared, showReuseMessage: true, clear: false }, properties, context); + return _fillDefaults(value, { echo: defaultEcho, reveal: Tasks.RevealKind.Always, revealProblems: Tasks.RevealProblemKind.Never, focus: false, panel: Tasks.PanelKind.Shared, showReuseMessage: true, clear: false }, properties, context); } export function freeze(value: Tasks.PresentationOptions): Readonly | undefined { diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index c1f97913eb9..4eadfa8c4fe 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -228,7 +228,7 @@ export interface PresentationOptions { * Controls whether the problems pane is revealed when running this task or not. * Defaults to `RevealProblemKind.Never`. */ - revealProblem: RevealProblemKind; + revealProblems: RevealProblemKind; /** * Controls whether the command associated with the task is echoed @@ -266,7 +266,7 @@ export interface PresentationOptions { export namespace PresentationOptions { export const defaults: PresentationOptions = { - echo: true, reveal: RevealKind.Always, revealProblem: RevealProblemKind.Never, focus: false, panel: PanelKind.Shared, showReuseMessage: true, clear: false + echo: true, reveal: RevealKind.Always, revealProblems: RevealProblemKind.Never, focus: false, panel: PanelKind.Shared, showReuseMessage: true, clear: false }; } diff --git a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts index 688ffaf6fe8..2a9a0b42a15 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts @@ -1973,7 +1973,7 @@ class TaskService extends Disposable implements ITaskService { } private showQuickPick(tasks: Promise | Task[], placeHolder: string, defaultEntry?: TaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry, additionalEntries?: TaskQuickPickEntry[]): Promise { - let _createEntries = (): Promise => { + let _createEntries = (): Promise[]> => { if (Array.isArray(tasks)) { return Promise.resolve(this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry)); } else { @@ -1983,8 +1983,8 @@ class TaskService extends Disposable implements ITaskService { return this.quickInputService.pick(_createEntries().then((entries) => { if ((entries.length === 0) && defaultEntry) { entries.push(defaultEntry); - } - else if (entries.length > 1 && additionalEntries && additionalEntries.length > 0) { + } else if (entries.length > 1 && additionalEntries && additionalEntries.length > 0) { + entries.push({ type: 'separator', label: '' }); entries.push(additionalEntries[0]); } return entries; @@ -2012,7 +2012,7 @@ class TaskService extends Disposable implements ITaskService { Severity.Info, nls.localize('TaskService.ignoredFolder', 'The following workspace folders are ignored since they use task version 0.1.0: {0}', this.ignoredWorkspaceFolders.map(f => f.name).join(', ')), [{ - label: nls.localize('TaskService.notAgain', 'Don\'t Show Again'), + label: nls.localize('TaskService.notAgain', "Don't Show Again"), isSecondary: true, run: () => { this.storageService.store(TaskService.IgnoreTask010DonotShowAgain_key, true, StorageScope.WORKSPACE); @@ -2218,7 +2218,7 @@ class TaskService extends Disposable implements ITaskService { } let runQuickPick = (promise?: Promise) => { this.showQuickPick(promise || this.getActiveTasks(), - nls.localize('TaskService.taskToTerminate', 'Select task to terminate'), + nls.localize('TaskService.taskToTerminate', 'Select a task to terminate'), { label: nls.localize('TaskService.noTaskRunning', 'No task is currently running'), task: undefined @@ -2226,7 +2226,7 @@ class TaskService extends Disposable implements ITaskService { false, true, undefined, [{ - label: nls.localize('TaskService.terminateAllRunningTasks', 'All running tasks'), + label: nls.localize('TaskService.terminateAllRunningTasks', 'All Running Tasks'), id: 'terminateAll', task: undefined }] diff --git a/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts index 0b727f13ce5..4c647da9057 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts @@ -171,7 +171,7 @@ export class TerminalTaskSystem implements ITaskSystem { private contextService: IWorkspaceContextService, private environmentService: IWorkbenchEnvironmentService, private outputChannelId: string, - taskSystemInfoResolver: TaskSystemInfoResovler + taskSystemInfoResolver: TaskSystemInfoResovler, ) { this.activeTasks = Object.create(null); @@ -523,8 +523,8 @@ export class TerminalTaskSystem implements ITaskSystem { if ((watchingProblemMatcher.numberOfMatches > 0) && watchingProblemMatcher.maxMarkerSeverity && (watchingProblemMatcher.maxMarkerSeverity >= MarkerSeverity.Error)) { let reveal = task.command.presentation!.reveal; - let revealProblem = task.command.presentation!.revealProblem; - if (revealProblem === RevealProblemKind.OnProblem) { + let revealProblems = task.command.presentation!.revealProblems; + if (revealProblems === RevealProblemKind.OnProblem) { this.panelService.openPanel(Constants.MARKERS_PANEL_ID, true); } else if (reveal === RevealKind.Silent) { this.terminalService.setActiveInstance(terminal!); @@ -653,8 +653,8 @@ export class TerminalTaskSystem implements ITaskSystem { } } let reveal = task.command.presentation!.reveal; - let revealProblem = task.command.presentation!.revealProblem; - let revealProblemPanel = terminal && (revealProblem === RevealProblemKind.OnProblem) && (startStopProblemMatcher.numberOfMatches > 0); + let revealProblems = task.command.presentation!.revealProblems; + let revealProblemPanel = terminal && (revealProblems === RevealProblemKind.OnProblem) && (startStopProblemMatcher.numberOfMatches > 0); if (revealProblemPanel) { this.panelService.openPanel(Constants.MARKERS_PANEL_ID); } else if (terminal && (reveal === RevealKind.Silent) && ((exitCode !== 0) || (startStopProblemMatcher.numberOfMatches > 0) && startStopProblemMatcher.maxMarkerSeverity && @@ -688,7 +688,7 @@ export class TerminalTaskSystem implements ITaskSystem { if (!terminal) { return Promise.reject(new Error(`Failed to create terminal for task ${task._label}`)); } - let showProblemPanel = task.command.presentation && (task.command.presentation.revealProblem === RevealProblemKind.Always); + let showProblemPanel = task.command.presentation && (task.command.presentation.revealProblems === RevealProblemKind.Always); if (showProblemPanel) { this.panelService.openPanel(Constants.MARKERS_PANEL_ID); } else if (task.command.presentation && (task.command.presentation.reveal === RevealKind.Always)) { @@ -754,7 +754,7 @@ export class TerminalTaskSystem implements ITaskSystem { let originalCommand = task.command.name; if (isShellCommand) { shellLaunchConfig = { name: terminalName, executable: undefined, args: undefined, waitOnExit }; - this.terminalService.configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig, platform); + this.terminalService.configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig, this.terminalService.getDefaultShell(platform), platform); let shellSpecified: boolean = false; let shellOptions: ShellConfiguration | undefined = task.command.options && task.command.options.shell; if (shellOptions) { diff --git a/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts b/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts index 191adbb0ee4..9a81fcf9441 100644 --- a/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts @@ -83,7 +83,7 @@ class PresentationBuilder { public result: Tasks.PresentationOptions; constructor(public parent: CommandConfigurationBuilder) { - this.result = { echo: false, reveal: Tasks.RevealKind.Always, revealProblem: Tasks.RevealProblemKind.Never, focus: false, panel: Tasks.PanelKind.Shared, showReuseMessage: true, clear: false }; + this.result = { echo: false, reveal: Tasks.RevealKind.Always, revealProblems: Tasks.RevealProblemKind.Never, focus: false, panel: Tasks.PanelKind.Shared, showReuseMessage: true, clear: false }; } public echo(value: boolean): PresentationBuilder { diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index e09cb24fbab..3e73903b7d2 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -36,7 +36,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr ) { super(); - const { filesToOpen, filesToCreate, filesToDiff } = environmentService.configuration; + const { filesToOpenOrCreate, filesToDiff } = environmentService.configuration; const activeViewlet = viewletService.getActiveViewlet(); /* __GDPR__ @@ -47,8 +47,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr "windowSize.outerHeight": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "windowSize.outerWidth": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "emptyWorkbench": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbench.filesToOpen": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbench.filesToCreate": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workbench.filesToOpenOrCreate": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workbench.filesToDiff": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "customKeybindingsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "theme": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -64,8 +63,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr userAgent: navigator.userAgent, windowSize: { innerHeight: window.innerHeight, innerWidth: window.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth }, emptyWorkbench: contextService.getWorkbenchState() === WorkbenchState.EMPTY, - 'workbench.filesToOpen': filesToOpen && filesToOpen.length || 0, - 'workbench.filesToCreate': filesToCreate && filesToCreate.length || 0, + 'workbench.filesToOpenOrCreate': filesToOpenOrCreate && filesToOpenOrCreate.length || 0, 'workbench.filesToDiff': filesToDiff && filesToDiff.length || 0, customKeybindingsCount: keybindingsService.customKeybindingsCount(), theme: themeService.getColorTheme().id, diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 379e443e5e8..0466fb5a593 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -260,7 +260,7 @@ configurationRegistry.registerConfiguration({ default: 'inherited' }, 'terminal.integrated.windowsEnableConpty': { - description: nls.localize('terminal.integrated.windowsEnableConpty', "Whether to use ConPTY for Windows terminal process communication (requires Windows 10 build number 18309+). Winpty will be used if this is false."), + description: nls.localize('terminal.integrated.windowsEnableConpty', "Whether to use ConPTY for Windows terminal process communication. Winpty will be used if this is false. Note that ConPTY will be disabled regardless of this setting when the Windows 10 build number is lower than 18309 or when you're running the 32-bit VS Code client under 64-bit Windows."), type: 'boolean', default: true }, @@ -502,7 +502,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, Fi const sendSequenceTerminalCommand = new SendSequenceTerminalCommand({ id: SendSequenceTerminalCommand.ID, - precondition: null, + precondition: undefined, description: { description: `Send Custom Sequence To Terminal`, args: [{ diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index ff7f6b95f5b..07d8a21d414 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -6,7 +6,7 @@ import { Terminal as XTermTerminal } from 'vscode-xterm'; import { ITerminalInstance, IWindowsShellHelper, ITerminalProcessManager, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IProcessEnvironment } from 'vs/base/common/platform'; +import { IProcessEnvironment, Platform } from 'vs/base/common/platform'; export const ITerminalInstanceService = createDecorator('terminalInstanceService'); @@ -17,6 +17,7 @@ export interface ITerminalInstanceService { createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper; createTerminalProcessManager(id: number, configHelper: ITerminalConfigHelper): ITerminalProcessManager; createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess; + getDefaultShell(p: Platform): string; } export interface IBrowserTerminalConfigHelper extends ITerminalConfigHelper { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 24d58ff101a..3fe9c77fdc6 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -8,7 +8,7 @@ import { Action, IAction } from 'vs/base/common/actions'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, Direction, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; -import { SelectActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -35,6 +35,7 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { isWindows } from 'vs/base/common/platform'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export const TERMINAL_PICKER_PREFIX = 'term '; @@ -85,7 +86,7 @@ export class ToggleTerminalAction extends TogglePanelAction { if (this.terminalService.terminalInstances.length === 0) { // If there is not yet an instance attempt to create it here so that we can suggest a // new shell on Windows (and not do so when the panel is restored on reload). - const newTerminalInstance = this.terminalService.createTerminal(undefined, true); + const newTerminalInstance = this.terminalService.createTerminal(undefined); const toDispose = newTerminalInstance.onProcessIdReady(() => { newTerminalInstance.focus(); toDispose.dispose(); @@ -329,7 +330,7 @@ export class CreateNewTerminalAction extends Action { if (folders.length <= 1) { // Allow terminal service to handle the path when there is only a // single root - instancePromise = Promise.resolve(this.terminalService.createTerminal(undefined, true)); + instancePromise = Promise.resolve(this.terminalService.createTerminal(undefined)); } else { const options: IPickOptions = { placeHolder: nls.localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal") @@ -339,7 +340,7 @@ export class CreateNewTerminalAction extends Action { // Don't create the instance if the workspace picker was canceled return null; } - return this.terminalService.createTerminal({ cwd: workspace.uri.fsPath }, true); + return this.terminalService.createTerminal({ cwd: workspace.uri }); }); } @@ -366,7 +367,7 @@ export class CreateNewInActiveWorkspaceTerminalAction extends Action { } public run(event?: any): Promise { - const instance = this.terminalService.createTerminal(undefined, true); + const instance = this.terminalService.createTerminal(undefined); if (!instance) { return Promise.resolve(undefined); } @@ -720,19 +721,30 @@ export class SwitchTerminalAction extends Action { if (!item || !item.split) { return Promise.resolve(null); } + if (item === SwitchTerminalActionViewItem.SEPARATOR) { + this.terminalService.refreshActiveTab(); + return Promise.resolve(null); + } + if (item === SelectDefaultShellWindowsTerminalAction.LABEL) { + this.terminalService.refreshActiveTab(); + return this.terminalService.selectDefaultWindowsShell(); + } const selectedTabIndex = parseInt(item.split(':')[0], 10) - 1; this.terminalService.setActiveTabByIndex(selectedTabIndex); return this.terminalService.showPanel(true); } } -export class SwitchTerminalActionItem extends SelectActionItem { +export class SwitchTerminalActionViewItem extends SelectActionViewItem { + + public static readonly SEPARATOR = '─────────'; constructor( action: IAction, @ITerminalService private readonly terminalService: ITerminalService, @IThemeService themeService: IThemeService, - @IContextViewService contextViewService: IContextViewService + @IContextViewService contextViewService: IContextViewService, + @IWorkbenchEnvironmentService private workbenchEnvironmentService: IWorkbenchEnvironmentService ) { super(null, action, terminalService.getTabLabels().map(label => { text: label }), terminalService.activeTabIndex, contextViewService, { ariaLabel: nls.localize('terminals', 'Open Terminals.') }); @@ -743,7 +755,13 @@ export class SwitchTerminalActionItem extends SelectActionItem { } private _updateItems(): void { - this.setOptions(this.terminalService.getTabLabels().map(label => { text: label }), this.terminalService.activeTabIndex); + const items = this.terminalService.getTabLabels().map(label => { text: label }); + let enableSelectDefaultShell = this.workbenchEnvironmentService.configuration.remoteAuthority ? false : isWindows; + if (enableSelectDefaultShell) { + items.push({ text: SwitchTerminalActionViewItem.SEPARATOR }); + items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL }); + } + this.setOptions(items, this.terminalService.activeTabIndex); } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts b/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts index c7857624a7d..a3189db77c4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts @@ -30,7 +30,7 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa constructor( private _xterm: Terminal ) { - this._xterm.on('key', key => this._onKey(key)); + this._xterm.onKey(e => this._onKey(e.key)); } public dispose(): void { @@ -48,7 +48,7 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa } private _onEnter(): void { - if (this._xterm._core.buffer.x >= MINIMUM_PROMPT_LENGTH) { + if (this._xterm.buffer.cursorX >= MINIMUM_PROMPT_LENGTH) { this._xterm.addMarker(0); } } @@ -175,7 +175,7 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa private _getLine(marker: IMarker | Boundary): number { // Use the _second last_ row as the last row is likely the prompt if (marker === Boundary.Bottom) { - return this._xterm._core.buffer.ybase + this._xterm.rows - 1; + return this._xterm.buffer.baseY + this._xterm.rows - 1; } if (marker === Boundary.Top) { @@ -235,10 +235,10 @@ export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposa if (this._currentMarker === Boundary.Bottom) { return 0; } else if (this._currentMarker === Boundary.Top) { - return 0 - (this._xterm._core.buffer.ybase + this._xterm._core.buffer.y); + return 0 - (this._xterm.buffer.baseY + this._xterm.buffer.cursorY); } else { let offset = this._getLine(this._currentMarker); - offset -= this._xterm._core.buffer.ybase + this._xterm._core.buffer.y; + offset -= this._xterm.buffer.baseY + this._xterm.buffer.cursorY; return offset; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 506ceef0bf0..716e1f872cb 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -14,6 +13,7 @@ import Severity from 'vs/base/common/severity'; import { Terminal as XTermTerminal } from 'vscode-xterm'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { mergeDefaultShellPathAndArgs } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; const MINIMUM_FONT_SIZE = 6; const MAXIMUM_FONT_SIZE = 25; @@ -167,9 +167,9 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { return this._storageService.getBoolean(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, StorageScope.WORKSPACE, defaultValue); } - public checkWorkspaceShellPermissions(platformOverride: platform.Platform = platform.platform): boolean { + public checkWorkspaceShellPermissions(osOverride: platform.OperatingSystem = platform.OS): boolean { // Check whether there is a workspace setting - const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux'; + const platformKey = osOverride === platform.OperatingSystem.Windows ? 'windows' : osOverride === platform.OperatingSystem.Macintosh ? 'osx' : 'linux'; const shellConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.shell.${platformKey}`); const shellArgsConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.shellArgs.${platformKey}`); const envConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.env.${platformKey}`); @@ -227,29 +227,9 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { return !!isWorkspaceShellAllowed; } - public mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, platformOverride: platform.Platform = platform.platform): void { - const isWorkspaceShellAllowed = this.checkWorkspaceShellPermissions(platformOverride); - const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux'; - const shellConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.shell.${platformKey}`); - const shellArgsConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.shellArgs.${platformKey}`); - - shell.executable = (isWorkspaceShellAllowed ? shellConfigValue.value : shellConfigValue.user) || shellConfigValue.default; - shell.args = (isWorkspaceShellAllowed ? shellArgsConfigValue.value : shellArgsConfigValue.user) || shellArgsConfigValue.default; - - // Change Sysnative to System32 if the OS is Windows but NOT WoW64. It's - // safe to assume that this was used by accident as Sysnative does not - // exist and will break the terminal in non-WoW64 environments. - if ((platformOverride === platform.Platform.Windows) && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') && process.env.windir) { - const sysnativePath = path.join(process.env.windir, 'Sysnative').toLowerCase(); - if (shell.executable.toLowerCase().indexOf(sysnativePath) === 0) { - shell.executable = path.join(process.env.windir, 'System32', shell.executable.substr(sysnativePath.length)); - } - } - - // Convert / to \ on Windows for convenience - if (platformOverride === platform.Platform.Windows) { - shell.executable = shell.executable.replace(/\//g, '\\'); - } + public mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, defaultShell: string, platformOverride: platform.Platform = platform.platform): void { + const isWorkspaceShellAllowed = this.checkWorkspaceShellPermissions(platformOverride === platform.Platform.Windows ? platform.OperatingSystem.Windows : (platformOverride === platform.Platform.Mac ? platform.OperatingSystem.Macintosh : platform.OperatingSystem.Linux)); + mergeDefaultShellPathAndArgs(shell, (key) => this._workspaceConfigurationService.inspect(key), isWorkspaceShellAllowed, defaultShell, platformOverride); } private _toInteger(source: any, minimum: number, maximum: number, fallback: number): number { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 735c23982e8..f9f4b106efd 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -25,14 +25,14 @@ import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderB import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; -import { IShellLaunchConfig, ITerminalDimensions, ITerminalInstance, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalDimensions, ITerminalInstance, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal'; import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ISearchOptions, Terminal as XTermTerminal } from 'vscode-xterm'; +import { ISearchOptions, Terminal as XTermTerminal, IBuffer } from 'vscode-xterm'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -371,7 +371,7 @@ export class TerminalInstance implements ITerminalInstance { // it gets removed and then added back to the DOM (resetting scrollTop to 0). // Upstream issue: https://github.com/sourcelair/xterm.js/issues/291 if (this._xterm) { - this._xterm.emit('scroll', this._xterm._core.buffer.ydisp); + this._xterm._core._onScroll.fire(this._xterm.buffer.viewportY); } } @@ -416,18 +416,17 @@ export class TerminalInstance implements ITerminalInstance { // TODO: Guess whether to use canvas or dom better rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType, // TODO: Remove this once the setting is removed upstream - experimentalCharAtlas: 'dynamic', - experimentalBufferLineImpl: 'TypedArray' + experimentalCharAtlas: 'dynamic' }); if (this._shellLaunchConfig.initialText) { this._xterm.writeln(this._shellLaunchConfig.initialText); } - this._xterm.on('linefeed', () => this._onLineFeed()); - this._xterm.on('key', (key, ev) => this._onKey(key, ev)); + this._xterm.onLineFeed(() => this._onLineFeed()); + this._xterm.onKey(e => this._onKey(e.key, e.domEvent)); if (this._processManager) { this._processManager.onProcessData(data => this._onProcessData(data)); - this._xterm.on('data', data => this._processManager!.write(data)); + this._xterm.onData(data => this._processManager!.write(data)); // TODO: How does the cwd work on detached processes? this.processReady.then(async () => { this._linkHandler.processCwd = await this._processManager!.getInitialCwd(); @@ -439,16 +438,24 @@ export class TerminalInstance implements ITerminalInstance { return; } if (this._processManager.os === platform.OperatingSystem.Windows) { - this._xterm.winptyCompatInit(); + this._xterm.setOption('windowsMode', true); + // Force line data to be sent when the cursor is moved, the main purpose for + // this is because ConPTY will often not do a line feed but instead move the + // cursor, in which case we still want to send the current line's data to tasks. + this._xterm.addCsiHandler('H', () => { + this._onCursorMove(); + return false; + }); } this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, platform.platform, this._processManager); }); + } else if (this.shellLaunchConfig.isRendererOnly) { + this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, undefined, undefined); } - this._xterm.on('focus', () => this._onFocus.fire(this)); // Register listener to trigger the onInput ext API if the terminal is a renderer only if (this._shellLaunchConfig.isRendererOnly) { - this._xterm.on('data', (data) => this._sendRendererInput(data)); + this._xterm.onData(data => this._sendRendererInput(data)); } this._commandTracker = new TerminalCommandTracker(this._xterm); @@ -506,6 +513,7 @@ export class TerminalInstance implements ITerminalInstance { (this._wrapperElement).xterm = this._xterm; this._xterm.open(this._xtermElement); + this._xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this)); this._xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => { // Disable all input if the terminal is exiting if (this._isExiting) { @@ -600,6 +608,9 @@ export class TerminalInstance implements ITerminalInstance { }); } }); + } else if (this._shellLaunchConfig.isRendererOnly) { + this._widgetManager = new TerminalWidgetManager(this._wrapperElement); + this._linkHandler.setWidgetManager(this._widgetManager); } const computedStyle = window.getComputedStyle(this._container); @@ -740,8 +751,8 @@ export class TerminalInstance implements ITerminalInstance { } } if (this._xterm) { - const buffer = (this._xterm._core.buffer); - this._sendLineData(buffer, buffer.ybase + buffer.y); + const buffer = this._xterm.buffer; + this._sendLineData(buffer, buffer.baseY + buffer.cursorY); this._xterm.dispose(); } @@ -854,7 +865,7 @@ export class TerminalInstance implements ITerminalInstance { // necessary if the number of rows in the terminal has decreased while it was in the // background since scrollTop changes take no effect but the terminal's position does // change since the number of visible rows decreases. - this._xterm.emit('scroll', this._xterm._core.buffer.ydisp); + this._xterm._core._onScroll.fire(this._xterm.buffer.viewportY); if (this._container && this._container.parentElement) { // Force a layout when the instance becomes invisible. This is particularly important // for ensuring that terminals that are created in the background by an extension will @@ -965,10 +976,32 @@ export class TerminalInstance implements ITerminalInstance { } this._isExiting = true; - let exitCodeMessage: string; + let exitCodeMessage: string | undefined; + // Create exit code message if (exitCode) { - exitCodeMessage = nls.localize('terminal.integrated.exitedWithCode', 'The terminal process terminated with exit code: {0}', exitCode); + if (exitCode === SHELL_PATH_INVALID_EXIT_CODE) { + exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPath', 'The terminal shell path does not exist: {0}', this._shellLaunchConfig.executable); + } else if (this._processManager && this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) { + let args = ''; + if (typeof this._shellLaunchConfig.args === 'string') { + args = ` ${this._shellLaunchConfig.args}`; + } else if (this._shellLaunchConfig.args && this._shellLaunchConfig.args.length) { + args = ' ' + this._shellLaunchConfig.args.map(a => { + if (typeof a === 'string' && a.indexOf(' ') !== -1) { + return `'${a}'`; + } + return a; + }).join(' '); + } + if (this._shellLaunchConfig.executable) { + exitCodeMessage = nls.localize('terminal.integrated.launchFailed', 'The terminal process command \'{0}{1}\' failed to launch (exit code: {2})', this._shellLaunchConfig.executable, args, exitCode); + } else { + exitCodeMessage = nls.localize('terminal.integrated.launchFailedExtHost', 'The terminal process failed to launch (exit code: {0})', exitCode); + } + } else { + exitCodeMessage = nls.localize('terminal.integrated.exitedWithCode', 'The terminal process terminated with exit code: {0}', exitCode); + } } this._logService.debug(`Terminal process exit (id: ${this.id})${this._processManager ? ' state ' + this._processManager.processState : ''}`); @@ -976,8 +1009,8 @@ export class TerminalInstance implements ITerminalInstance { // Only trigger wait on exit when the exit was *not* triggered by the // user (via the `workbench.action.terminal.kill` command). if (this._shellLaunchConfig.waitOnExit && (!this._processManager || this._processManager.processState !== ProcessState.KILLED_BY_USER)) { - if (exitCode) { - this._xterm.writeln(exitCodeMessage!); + if (exitCodeMessage) { + this._xterm.writeln(exitCodeMessage); } if (typeof this._shellLaunchConfig.waitOnExit === 'string') { let message = this._shellLaunchConfig.waitOnExit; @@ -992,29 +1025,14 @@ export class TerminalInstance implements ITerminalInstance { } } else { this.dispose(); - if (exitCode) { + if (exitCodeMessage) { if (this._processManager && this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) { - let args = ''; - if (typeof this._shellLaunchConfig.args === 'string') { - args = this._shellLaunchConfig.args; - } else if (this._shellLaunchConfig.args && this._shellLaunchConfig.args.length) { - args = ' ' + this._shellLaunchConfig.args.map(a => { - if (typeof a === 'string' && a.indexOf(' ') !== -1) { - return `'${a}'`; - } - return a; - }).join(' '); - } - if (this._shellLaunchConfig.executable) { - this._notificationService.error(nls.localize('terminal.integrated.launchFailed', 'The terminal process command \'{0}{1}\' failed to launch (exit code: {2})', this._shellLaunchConfig.executable, args, exitCode)); - } else { - this._notificationService.error(nls.localize('terminal.integrated.launchFailedExtHost', 'The terminal process failed to launch (exit code: {0})', exitCode)); - } + this._notificationService.error(exitCodeMessage); } else { if (this._configHelper.config.showExitAlert) { - this._notificationService.error(exitCodeMessage!); + this._notificationService.error(exitCodeMessage); } else { - console.warn(exitCodeMessage!); + console.warn(exitCodeMessage); } } } @@ -1096,17 +1114,22 @@ export class TerminalInstance implements ITerminalInstance { } private _onLineFeed(): void { - const buffer = (this._xterm._core.buffer); - const newLine = buffer.lines.get(buffer.ybase + buffer.y); - if (!newLine.isWrapped) { - this._sendLineData(buffer, buffer.ybase + buffer.y - 1); + const buffer = this._xterm.buffer; + const newLine = buffer.getLine(buffer.baseY + buffer.cursorY); + if (newLine && !newLine.isWrapped) { + this._sendLineData(buffer, buffer.baseY + buffer.cursorY - 1); } } - private _sendLineData(buffer: any, lineIndex: number): void { - let lineData = buffer.translateBufferLineToString(lineIndex, true); - while (lineIndex >= 0 && buffer.lines.get(lineIndex--).isWrapped) { - lineData = buffer.translateBufferLineToString(lineIndex, false) + lineData; + private _onCursorMove(): void { + const buffer = this._xterm.buffer; + this._sendLineData(buffer, buffer.baseY + buffer.cursorY); + } + + private _sendLineData(buffer: IBuffer, lineIndex: number): void { + let lineData = buffer.getLine(lineIndex)!.translateToString(true); + while (lineIndex >= 0 && buffer.getLine(lineIndex--)!.isWrapped) { + lineData = buffer.getLine(lineIndex)!.translateToString(false) + lineData; } this._onLineData.fire(lineData); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index c1109f1242d..d182a8d179b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -74,8 +74,8 @@ export class TerminalLinkHandler { constructor( private _xterm: any, - private _platform: platform.Platform, - private readonly _processManager: ITerminalProcessManager, + private _platform: platform.Platform | undefined, + private readonly _processManager: ITerminalProcessManager | undefined, @IOpenerService private readonly _openerService: IOpenerService, @IEditorService private readonly _editorService: IEditorService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -88,6 +88,9 @@ export class TerminalLinkHandler { this._gitDiffPostImagePattern = /^\+\+\+ b\/(\S*)/; this._tooltipCallback = (e: MouseEvent) => { + if (!this._widgetManager) { + return; + } if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') { const target = (e.target as HTMLElement); this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString()); @@ -97,8 +100,10 @@ export class TerminalLinkHandler { }; this.registerWebLinkHandler(); - this.registerLocalLinkHandler(); - this.registerGitDiffLinkHandlers(); + if (this._platform) { + this.registerLocalLinkHandler(); + this.registerGitDiffLinkHandlers(); + } } public setWidgetManager(widgetManager: TerminalWidgetManager): void { @@ -113,7 +118,11 @@ export class TerminalLinkHandler { const options: ILinkMatcherOptions = { matchIndex, tooltipCallback: this._tooltipCallback, - leaveCallback: () => this._widgetManager.closeMessage(), + leaveCallback: () => { + if (this._widgetManager) { + this._widgetManager.closeMessage(); + } + }, willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e), priority: CUSTOM_LINK_PRIORITY }; @@ -186,6 +195,9 @@ export class TerminalLinkHandler { } protected get _localLinkRegex(): RegExp { + if (!this._processManager) { + throw new Error('Process manager is required'); + } const baseLocalLinkClause = this._processManager.os === platform.OperatingSystem.Windows ? winLocalLinkClause : unixLocalLinkClause; // Append line and column number regex return new RegExp(`${baseLocalLinkClause}(${lineAndColumnClause})`); @@ -237,7 +249,11 @@ export class TerminalLinkHandler { private _getLinkHoverString(): string { const editorConf = this._configurationService.getValue<{ multiCursorModifier: 'ctrlCmd' | 'alt' }>('editor'); if (editorConf.multiCursorModifier === 'ctrlCmd') { - return nls.localize('terminalLinkHandler.followLinkAlt', 'Alt + click to follow link'); + if (platform.isMacintosh) { + return nls.localize('terminalLinkHandler.followLinkAlt.mac', 'Option + click to follow link'); + } else { + return nls.localize('terminalLinkHandler.followLinkAlt', 'Alt + click to follow link'); + } } if (platform.isMacintosh) { return nls.localize('terminalLinkHandler.followLinkCmd', 'Cmd + click to follow link'); @@ -246,6 +262,9 @@ export class TerminalLinkHandler { } private get osPath(): IPath { + if (!this._processManager) { + throw new Error('Process manager is required'); + } if (this._processManager.os === platform.OperatingSystem.Windows) { return win32; } @@ -253,6 +272,9 @@ export class TerminalLinkHandler { } protected _preprocessPath(link: string): string | null { + if (!this._processManager) { + throw new Error('Process manager is required'); + } if (link.charAt(0) === '~') { // Resolve ~ -> userHome if (!this._processManager.userHome) { @@ -283,6 +305,10 @@ export class TerminalLinkHandler { } private _resolvePath(link: string): PromiseLike { + if (!this._processManager) { + throw new Error('Process manager is required'); + } + const preprocessedLink = this._preprocessPath(link); if (!preprocessedLink) { return Promise.resolve(null); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index 780147cb952..01d79e0c592 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -7,7 +7,7 @@ import * as dom from 'vs/base/browser/dom'; import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; import { Action, IAction } from 'vs/base/common/actions'; -import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -16,7 +16,7 @@ import { ITerminalService, TERMINAL_PANEL_ID } from 'vs/workbench/contrib/termin import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { TerminalFindWidget } from 'vs/workbench/contrib/terminal/browser/terminalFindWidget'; import { editorHoverBackground, editorHoverBorder, editorForeground } from 'vs/platform/theme/common/colorRegistry'; -import { KillTerminalAction, SwitchTerminalAction, SwitchTerminalActionItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction, CreateNewTerminalAction, SplitTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { KillTerminalAction, SwitchTerminalAction, SwitchTerminalActionViewItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction, CreateNewTerminalAction, SplitTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { Panel } from 'vs/workbench/browser/panel'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { URI } from 'vs/base/common/uri'; @@ -161,12 +161,12 @@ export class TerminalPanel extends Panel { return this._contextMenuActions; } - public getActionItem(action: Action): IActionItem | undefined { + public getActionViewItem(action: Action): IActionViewItem | undefined { if (action.id === SwitchTerminalAction.ID) { - return this._instantiationService.createInstance(SwitchTerminalActionItem, action); + return this._instantiationService.createInstance(SwitchTerminalActionViewItem, action); } - return super.getActionItem(action); + return super.getActionViewItem(action); } public focus(): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index fe1cc18d5f2..33079c36940 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -6,7 +6,7 @@ import * as platform from 'vs/base/common/platform'; import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal'; import { ILogService } from 'vs/platform/log/common/log'; import { Emitter, Event } from 'vs/base/common/event'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -15,8 +15,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { Schemas } from 'vs/base/common/network'; -import { REMOTE_HOST_SCHEME, getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; -import { sanitizeProcessEnvironment } from 'vs/base/common/processes'; +import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IProductService } from 'vs/platform/product/common/product'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; @@ -132,57 +131,14 @@ export class TerminalProcessManager implements ITerminalProcessManager { }); } - const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(hasRemoteAuthority ? REMOTE_HOST_SCHEME : undefined); - this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows); + const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); + this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper); } else { - if (!shellLaunchConfig.executable) { - this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig); - } - - const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file); - const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, this._environmentService.userHome, activeWorkspaceRootUri, this._configHelper.config.cwd); - - // Compel type system as process.env should not have any undefined entries - let env: platform.IProcessEnvironment = {}; - - if (shellLaunchConfig.strictEnv) { - // Only base the terminal process environment on this environment and add the - // various mixins when strictEnv is false - env = { ...shellLaunchConfig.env } as any; - } else { - // Merge process env with the env from config and from shellLaunchConfig - env = { ...process.env } as any; - - // Resolve env vars from config and shell - const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null; - const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); - const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions(); - const envFromConfigValue = this._workspaceConfigurationService.inspect<{ [key: string]: string }>(`terminal.integrated.env.${platformKey}`); - const allowedEnvFromConfig = (isWorkspaceShellAllowed ? envFromConfigValue.value : envFromConfigValue.user); - const envFromConfig = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...allowedEnvFromConfig }, lastActiveWorkspaceRoot); - const envFromShell = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...shellLaunchConfig.env }, lastActiveWorkspaceRoot); - shellLaunchConfig.env = envFromShell; - - terminalEnvironment.mergeEnvironments(env, envFromConfig); - terminalEnvironment.mergeEnvironments(env, shellLaunchConfig.env); - - // Sanitize the environment, removing any undesirable VS Code and Electron environment - // variables - sanitizeProcessEnvironment(env, 'VSCODE_IPC_HOOK_CLI'); - - // Adding other env keys necessary to create the process - terminalEnvironment.addTerminalEnvironmentKeys(env, this._productService.version, platform.locale, this._configHelper.config.setLocaleVariables); - } - - this._logService.debug(`Terminal process launching`, shellLaunchConfig, initialCwd, cols, rows, env); - this._process = this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, this._configHelper.config.windowsEnableConpty); + this._process = this._launchProcess(shellLaunchConfig, cols, rows); } this.processState = ProcessState.LAUNCHING; - // The process is non-null, but TS isn't clever enough to know - const p = this._process!; - - p.onProcessData(data => { + this._process.onProcessData(data => { const beforeProcessDataEvent: IBeforeProcessDataEvent = { data }; this._onBeforeProcessData.fire(beforeProcessDataEvent); if (beforeProcessDataEvent.data && beforeProcessDataEvent.data.length > 0) { @@ -190,19 +146,19 @@ export class TerminalProcessManager implements ITerminalProcessManager { } }); - p.onProcessIdReady(pid => { + this._process.onProcessIdReady(pid => { this.shellProcessId = pid; this._onProcessReady.fire(); // Send any queued data that's waiting - if (this._preLaunchInputQueue.length > 0) { - p.input(this._preLaunchInputQueue.join('')); + if (this._preLaunchInputQueue.length > 0 && this._process) { + this._process.input(this._preLaunchInputQueue.join('')); this._preLaunchInputQueue.length = 0; } }); - p.onProcessTitleChanged(title => this._onProcessTitle.fire(title)); - p.onProcessExit(exitCode => this._onExit(exitCode)); + this._process.onProcessTitleChanged(title => this._onProcessTitle.fire(title)); + this._process.onProcessExit(exitCode => this._onExit(exitCode)); setTimeout(() => { if (this.processState === ProcessState.LAUNCHING) { @@ -211,6 +167,24 @@ export class TerminalProcessManager implements ITerminalProcessManager { }, LAUNCHING_DURATION); } + private _launchProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): ITerminalChildProcess { + if (!shellLaunchConfig.executable) { + this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig, this._terminalInstanceService.getDefaultShell(platform.platform)); + } + + const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file); + const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, this._environmentService.userHome, activeWorkspaceRootUri, this._configHelper.config.cwd); + + const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); + const lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null; + const envFromConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.env.${platformKey}`); + const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions(); + const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.setLocaleVariables); + + const useConpty = this._configHelper.config.windowsEnableConpty; + return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty); + } + public setDimensions(cols: number, rows: number): void { if (!this._process) { return; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index f280a830240..9a8b38a0fb7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -3,21 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; -import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalService as CommonTerminalService } from 'vs/workbench/contrib/terminal/common/terminalService'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; @@ -36,7 +34,6 @@ export abstract class TerminalService extends CommonTerminalService implements I @INotificationService notificationService: INotificationService, @IDialogService dialogService: IDialogService, @IInstantiationService protected readonly _instantiationService: IInstantiationService, - @IWorkbenchEnvironmentService private _environmentService: IWorkbenchEnvironmentService, @IExtensionService extensionService: IExtensionService, @IFileService fileService: IFileService, @IRemoteAgentService remoteAgentService: IRemoteAgentService @@ -44,7 +41,7 @@ export abstract class TerminalService extends CommonTerminalService implements I super(contextKeyService, panelService, lifecycleService, storageService, notificationService, dialogService, extensionService, fileService, remoteAgentService); } - protected abstract _getDefaultShell(p: platform.Platform): string; + public abstract getDefaultShell(p: platform.Platform): string; public createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance { const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, container, shellLaunchConfig); @@ -52,7 +49,7 @@ export abstract class TerminalService extends CommonTerminalService implements I return instance; } - public createTerminal(shell: IShellLaunchConfig = {}, wasNewTerminalAction?: boolean): ITerminalInstance { + public createTerminal(shell: IShellLaunchConfig = {}): ITerminalInstance { const terminalTab = this._instantiationService.createInstance(TerminalTab, this._terminalFocusContextKey, this.configHelper, @@ -68,74 +65,9 @@ export abstract class TerminalService extends CommonTerminalService implements I this.setActiveInstanceByIndex(0); } this._onInstancesChanged.fire(); - this._suggestShellChange(wasNewTerminalAction); return instance; } - private _suggestShellChange(wasNewTerminalAction?: boolean): void { - // Only suggest on Windows since $SHELL works great for macOS/Linux - if (!platform.isWindows) { - return; - } - - if (this._environmentService.configuration.remoteAuthority) { - // Don't suggest if the opened workspace is remote - return; - } - - // Only suggest when the terminal instance is being created by an explicit user action to - // launch a terminal, as opposed to something like tasks, debug, panel restore, etc. - if (!wasNewTerminalAction) { - return; - } - - if (this._environmentService.configuration.remoteAuthority) { - // Don't suggest if the opened workspace is remote - return; - } - - // Don't suggest if the user has explicitly opted out - const neverSuggest = this._storageService.getBoolean(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, StorageScope.GLOBAL, false); - if (neverSuggest) { - return; - } - - // Never suggest if the setting is non-default already (ie. they set the setting manually) - if (this.configHelper.config.shell.windows !== this._getDefaultShell(platform.Platform.Windows)) { - this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL); - return; - } - - this._notificationService.prompt( - Severity.Info, - nls.localize('terminal.integrated.chooseWindowsShellInfo', "You can change the default terminal shell by selecting the customize button."), - [{ - label: nls.localize('customize', "Customize"), - run: () => { - this.selectDefaultWindowsShell().then(shell => { - if (!shell) { - return Promise.resolve(null); - } - // Launch a new instance with the newly selected shell - const instance = this.createTerminal({ - executable: shell, - args: this.configHelper.config.shellArgs.windows - }); - if (instance) { - this.setActiveInstance(instance); - } - return Promise.resolve(null); - }); - } - }, - { - label: nls.localize('never again', "Don't Show Again"), - isSecondary: true, - run: () => this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL) - }] - ); - } - public focusFindWidget(): Promise { return this.showPanel(false).then(() => { const panel = this._panelService.getActivePanel() as TerminalPanel; diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index c1f58a7fc8f..719f1add08b 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -37,7 +37,6 @@ export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED = new RawContextKey export const KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_NOT_FOCUSED: ContextKeyExpr = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED.toNegated(); export const IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY = 'terminal.integrated.isWorkspaceShellAllowed'; -export const NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY = 'terminal.integrated.neverSuggestSelectWindowsShell'; export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverMeasureRenderTime'; // The creation of extension host terminals is delayed by this value (milliseconds). The purpose of @@ -58,14 +57,15 @@ export const TERMINAL_CONFIG_SECTION = 'terminal.integrated'; export const DEFAULT_LETTER_SPACING = 0; export const MINIMUM_LETTER_SPACING = -5; export const DEFAULT_LINE_HEIGHT = 1; +export const SHELL_PATH_INVALID_EXIT_CODE = -1; export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'; export interface ITerminalConfiguration { shell: { - linux: string; - osx: string; - windows: string; + linux: string | null; + osx: string | null; + windows: string | null; }; shellArgs: { linux: string[]; @@ -112,10 +112,10 @@ export interface ITerminalConfigHelper { /** * Merges the default shell path and args into the provided launch configuration */ - mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, platformOverride?: platform.Platform): void; + mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, defaultShell: string, platformOverride?: platform.Platform): void; /** Sets whether a workspace shell configuration is allowed or not */ setWorkspaceShellAllowed(isAllowed: boolean): void; - checkWorkspaceShellPermissions(platformOverride?: platform.Platform): boolean; + checkWorkspaceShellPermissions(osOverride?: platform.OperatingSystem): boolean; } export interface ITerminalFont { @@ -215,10 +215,8 @@ export interface ITerminalService { /** * Creates a terminal. * @param shell The shell launch configuration to use. - * @param wasNewTerminalAction Whether this was triggered by a new terminal action, if so a - * default shell selection dialog may display. */ - createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance; + createTerminal(shell?: IShellLaunchConfig): ITerminalInstance; /** * Creates a terminal renderer. @@ -244,6 +242,12 @@ export interface ITerminalService { setActiveTabToPrevious(): void; setActiveTabByIndex(tabIndex: number): void; + /** + * Fire the onActiveTabChanged event, this will trigger the terminal dropdown to be updated, + * among other things. + */ + refreshActiveTab(): void; + showPanel(focus?: boolean): Promise; hidePanel(): void; focusFindWidget(): Promise; @@ -253,6 +257,7 @@ export interface ITerminalService { findPrevious(): void; setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; + getDefaultShell(p: platform.Platform): string; selectDefaultWindowsShell(): Promise; setWorkspaceShellAllowed(isAllowed: boolean): void; @@ -268,7 +273,7 @@ export interface ITerminalService { preparePathForTerminalAsync(path: string, executable: string | undefined, title: string): Promise; extHostReady(remoteAuthority: string): void; - requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void; + requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void; } export const enum Direction { @@ -688,7 +693,6 @@ export const enum ProcessState { KILLED_BY_PROCESS } - export interface ITerminalProcessExtHostProxy extends IDisposable { readonly terminalId: number; @@ -714,6 +718,7 @@ export interface ITerminalProcessExtHostRequest { activeWorkspaceRootUri: URI; cols: number; rows: number; + isWorkspaceShellAllowed: boolean; } export enum LinuxDistro { diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 5480374bba8..813bd746af3 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -9,12 +9,13 @@ import { URI as Uri } from 'vs/base/common/uri'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { sanitizeProcessEnvironment } from 'vs/base/common/processes'; /** * This module contains utility functions related to the environment, cwd and paths. */ -export function mergeEnvironments(parent: platform.IProcessEnvironment, other?: ITerminalEnvironment): void { +export function mergeEnvironments(parent: platform.IProcessEnvironment, other: ITerminalEnvironment | undefined): void { if (!other) { return; } @@ -49,15 +50,29 @@ function _mergeEnvironmentValue(env: ITerminalEnvironment, key: string, value: s } } -export function addTerminalEnvironmentKeys(env: ITerminalEnvironment, version: string | undefined, locale: string | undefined, setLocaleVariables: boolean): void { +export function addTerminalEnvironmentKeys(env: platform.IProcessEnvironment, version: string | undefined, locale: string | undefined, setLocaleVariables: boolean): void { env['TERM_PROGRAM'] = 'vscode'; - env['TERM_PROGRAM_VERSION'] = version ? version : null; + if (version) { + env['TERM_PROGRAM_VERSION'] = version; + } if (setLocaleVariables) { env['LANG'] = _getLangEnvVariable(locale); } } -export function resolveConfigurationVariables(configurationResolverService: IConfigurationResolverService, env: ITerminalEnvironment, lastActiveWorkspaceRoot: IWorkspaceFolder | null): ITerminalEnvironment { +function mergeNonNullKeys(env: platform.IProcessEnvironment, other: ITerminalEnvironment | NodeJS.ProcessEnv | undefined) { + if (!other) { + return; + } + for (const key of Object.keys(other)) { + const value = other[key]; + if (value) { + env[key] = value; + } + } +} + +function resolveConfigurationVariables(configurationResolverService: IConfigurationResolverService, env: ITerminalEnvironment, lastActiveWorkspaceRoot: IWorkspaceFolder | null): ITerminalEnvironment { Object.keys(env).forEach((key) => { const value = env[key]; if (typeof value === 'string' && lastActiveWorkspaceRoot !== null) { @@ -83,6 +98,7 @@ function _getLangEnvVariable(locale?: string) { es: 'ES', fi: 'FI', fr: 'FR', + hu: 'HU', it: 'IT', ja: 'JP', ko: 'KR', @@ -144,3 +160,79 @@ export function escapeNonWindowsPath(path: string): string { } return newPath; } + +export function mergeDefaultShellPathAndArgs( + shell: IShellLaunchConfig, + fetchSetting: (key: string) => { user: string | string[] | undefined, value: string | string[] | undefined, default: string | string[] | undefined }, + isWorkspaceShellAllowed: boolean, + defaultShell: string, + platformOverride: platform.Platform = platform.platform +): void { + const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux'; + const shellConfigValue = fetchSetting(`terminal.integrated.shell.${platformKey}`); + const shellArgsConfigValue = fetchSetting(`terminal.integrated.shellArgs.${platformKey}`); + + shell.executable = (isWorkspaceShellAllowed ? shellConfigValue.value : shellConfigValue.user) || (shellConfigValue.default || defaultShell); + shell.args = (isWorkspaceShellAllowed ? shellArgsConfigValue.value : shellArgsConfigValue.user) || shellArgsConfigValue.default; + + // Change Sysnative to System32 if the OS is Windows but NOT WoW64. It's + // safe to assume that this was used by accident as Sysnative does not + // exist and will break the terminal in non-WoW64 environments. + if ((platformOverride === platform.Platform.Windows) && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') && process.env.windir) { + const sysnativePath = path.join(process.env.windir, 'Sysnative').toLowerCase(); + if (shell.executable && shell.executable.toLowerCase().indexOf(sysnativePath) === 0) { + shell.executable = path.join(process.env.windir, 'System32', shell.executable.substr(sysnativePath.length)); + } + } + + // Convert / to \ on Windows for convenience + if (shell.executable && platformOverride === platform.Platform.Windows) { + shell.executable = shell.executable.replace(/\//g, '\\'); + } +} + +export function createTerminalEnvironment( + shellLaunchConfig: IShellLaunchConfig, + lastActiveWorkspace: IWorkspaceFolder | null, + envFromConfig: { user: ITerminalEnvironment | undefined, value: ITerminalEnvironment | undefined, default: ITerminalEnvironment | undefined }, + configurationResolverService: IConfigurationResolverService | undefined, + isWorkspaceShellAllowed: boolean, + version: string | undefined, + setLocaleVariables: boolean +): platform.IProcessEnvironment { + // Create a terminal environment based on settings, launch config and permissions + let env: platform.IProcessEnvironment = {}; + if (shellLaunchConfig.strictEnv) { + // strictEnv is true, only use the requested env (ignoring null entries) + mergeNonNullKeys(env, shellLaunchConfig.env); + } else { + // Merge process env with the env from config and from shellLaunchConfig + mergeNonNullKeys(env, process.env); + + // const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); + // const envFromConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.env.${platformKey}`); + const allowedEnvFromConfig = { ...(isWorkspaceShellAllowed ? envFromConfig.value : envFromConfig.user) }; + + // Resolve env vars from config and shell + if (configurationResolverService) { + if (allowedEnvFromConfig) { + resolveConfigurationVariables(configurationResolverService, allowedEnvFromConfig, lastActiveWorkspace); + } + if (shellLaunchConfig.env) { + resolveConfigurationVariables(configurationResolverService, shellLaunchConfig.env, lastActiveWorkspace); + } + } + + // Merge config (settings) and ShellLaunchConfig environments + mergeEnvironments(env, allowedEnvFromConfig); + mergeEnvironments(env, shellLaunchConfig.env); + + // Sanitize the environment, removing any undesirable VS Code and Electron environment + // variables + sanitizeProcessEnvironment(env, 'VSCODE_IPC_HOOK_CLI'); + + // Adding other env keys necessary to create the process + addTerminalEnvironmentKeys(env, version, platform.locale, setLocaleVariables); + } + return env; +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts index b311ce44b90..dc61c51298d 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts @@ -4,9 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import * as nls from 'vs/nls'; + +let hasReceivedResponse: boolean = false; export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerminalProcessExtHostProxy { private _disposables: IDisposable[] = []; @@ -43,10 +47,19 @@ export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerm activeWorkspaceRootUri: URI, cols: number, rows: number, - @ITerminalService private readonly _terminalService: ITerminalService + configHelper: ITerminalConfigHelper, + @ITerminalService private readonly _terminalService: ITerminalService, + @IRemoteAgentService readonly remoteAgentService: IRemoteAgentService ) { - this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows); - setTimeout(() => this._onProcessTitleChanged.fire('Starting...'), 0); + remoteAgentService.getEnvironment().then(env => { + if (!env) { + throw new Error('Could not fetch environment'); + } + this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper.checkWorkspaceShellPermissions(env.os)); + }); + if (!hasReceivedResponse) { + setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0); + } } public dispose(): void { @@ -59,6 +72,7 @@ export class TerminalProcessExtHostProxy implements ITerminalChildProcess, ITerm } public emitTitle(title: string): void { + hasReceivedResponse = true; this._onProcessTitleChanged.fire(title); } diff --git a/src/vs/workbench/contrib/terminal/common/terminalService.ts b/src/vs/workbench/contrib/terminal/common/terminalService.ts index 1b973709755..74d076206c9 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalService.ts @@ -17,7 +17,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, Platform } from 'vs/base/common/platform'; import { basename } from 'vs/base/common/path'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { timeout } from 'vs/base/common/async'; @@ -41,7 +41,7 @@ export abstract class TerminalService implements ITerminalService { public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; } public get terminalTabs(): ITerminalTab[] { return this._terminalTabs; } - private readonly _onActiveTabChanged = new Emitter(); + protected readonly _onActiveTabChanged = new Emitter(); public get onActiveTabChanged(): Event { return this._onActiveTabChanged.event; } protected readonly _onInstanceCreated = new Emitter(); public get onInstanceCreated(): Event { return this._onInstanceCreated.event; } @@ -104,8 +104,10 @@ export abstract class TerminalService implements ITerminalService { protected abstract _getWslPath(path: string): Promise; protected abstract _getWindowsBuildNumber(): number; + public abstract refreshActiveTab(): void; public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance; public abstract createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance; + public abstract getDefaultShell(platform: Platform): string; public abstract selectDefaultWindowsShell(): Promise; public abstract setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; @@ -118,7 +120,7 @@ export abstract class TerminalService implements ITerminalService { return activeInstance ? activeInstance : this.createTerminal(undefined, wasNewTerminalAction); } - public requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void { + public requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void { this._extensionService.whenInstalledExtensionsRegistered().then(async () => { // Wait for the remoteAuthority to be ready (and listening for events) before proceeding const conn = this._remoteAgentService.getConnection(); @@ -127,7 +129,7 @@ export abstract class TerminalService implements ITerminalService { while (!this._extHostsReady[remoteAuthority] && ++retries < 50) { await timeout(100); } - this._onInstanceRequestExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows }); + this._onInstanceRequestExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, isWorkspaceShellAllowed }); }); } @@ -426,6 +428,9 @@ export abstract class TerminalService implements ITerminalService { return Promise.resolve(null); } const current = potentialPaths.shift(); + if (current! === '') { + return this._validateShellPaths(label, potentialPaths); + } return this._fileService.exists(URI.file(current!)).then(exists => { if (!exists) { return this._validateShellPaths(label, potentialPaths); diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts index a55b93caa40..616bf9eba8a 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts @@ -22,19 +22,19 @@ configurationRegistry.registerConfiguration({ type: 'object', properties: { 'terminal.integrated.shell.linux': { - markdownDescription: nls.localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), - type: 'string', - default: getDefaultShell(platform.Platform.Linux) + markdownDescription: nls.localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(platform.Platform.Linux)), + type: ['string', 'null'], + default: null }, 'terminal.integrated.shell.osx': { - markdownDescription: nls.localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), - type: 'string', - default: getDefaultShell(platform.Platform.Mac) + markdownDescription: nls.localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(platform.Platform.Mac)), + type: ['string', 'null'], + default: null }, 'terminal.integrated.shell.windows': { - markdownDescription: nls.localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), - type: 'string', - default: getDefaultShell(platform.Platform.Windows) + markdownDescription: nls.localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows (default: {0}). [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration).", getDefaultShell(platform.Platform.Windows)), + type: ['string', 'null'], + default: null } } }); diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts index 9fedff9cb21..53445235342 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts @@ -10,9 +10,10 @@ import { ITerminalInstance, IWindowsShellHelper, ITerminalConfigHelper, ITermina import { WindowsShellHelper } from 'vs/workbench/contrib/terminal/node/windowsShellHelper'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; -import { IProcessEnvironment } from 'vs/base/common/platform'; +import { IProcessEnvironment, Platform } from 'vs/base/common/platform'; import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess'; import * as typeAheadAddon from 'vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon'; +import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal'; let Terminal: typeof XTermTerminal; @@ -35,7 +36,6 @@ export class TerminalInstanceService implements ITerminalInstanceService { // Enable xterm.js addons Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/search/search')); Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/webLinks/webLinks')); - Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/winptyCompat/winptyCompat')); Terminal.applyAddon(typeAheadAddon); // Localize strings Terminal.strings.blankLine = nls.localize('terminal.integrated.a11yBlankLine', 'Blank line'); @@ -54,6 +54,10 @@ export class TerminalInstanceService implements ITerminalInstanceService { } public createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess { - return new TerminalProcess(shellLaunchConfig, cwd, cols, rows, env, windowsEnableConpty); + return this._instantiationService.createInstance(TerminalProcess, shellLaunchConfig, cwd, cols, rows, env, windowsEnableConpty); + } + + public getDefaultShell(p: Platform): string { + return getDefaultShell(p); } } \ No newline at end of file diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalService.ts index d7e3026986e..929f9626d11 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalService.ts @@ -20,7 +20,6 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ipcRenderer as ipc } from 'electron'; import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; import { coalesce } from 'vs/base/common/arrays'; @@ -45,11 +44,10 @@ export class TerminalService extends BrowserTerminalService implements ITerminal @INotificationService notificationService: INotificationService, @IDialogService dialogService: IDialogService, @IExtensionService extensionService: IExtensionService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IFileService fileService: IFileService, @IRemoteAgentService remoteAgentService: IRemoteAgentService ) { - super(contextKeyService, panelService, layoutService, lifecycleService, storageService, notificationService, dialogService, instantiationService, environmentService, extensionService, fileService, remoteAgentService); + super(contextKeyService, panelService, layoutService, lifecycleService, storageService, notificationService, dialogService, instantiationService, extensionService, fileService, remoteAgentService); this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, linuxDistro); ipc.on('vscode:openFiles', (_event: any, request: IOpenFileRequest) => { @@ -98,10 +96,15 @@ export class TerminalService extends BrowserTerminalService implements ITerminal }); } - protected _getDefaultShell(p: platform.Platform): string { + public getDefaultShell(p: platform.Platform): string { return getDefaultShell(p); } + public refreshActiveTab(): void { + // Fire active instances changed + this._onActiveTabChanged.fire(); + } + public selectDefaultWindowsShell(): Promise { return this._detectWindowsShells().then(shells => { const options: IPickOptions = { @@ -117,7 +120,28 @@ export class TerminalService extends BrowserTerminalService implements ITerminal }); } - private _detectWindowsShells(): Promise { + /** + * Get the executable file path of shell from registry. + * @param shellName The shell name to get the executable file path + * @returns `[]` or `[ 'path' ]` + */ + private async _getShellPathFromRegistry(shellName: string): Promise { + const Registry = await import('vscode-windows-registry'); + + try { + const shellPath = Registry.GetStringRegKey('HKEY_LOCAL_MACHINE', `SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${shellName}.exe`, ''); + + if (shellPath === undefined) { + return []; + } + + return [shellPath]; + } catch (error) { + return []; + } + } + + private async _detectWindowsShells(): Promise { // Determine the correct System32 path. We want to point to Sysnative // when the 32-bit version of VS Code is running on a 64-bit machine. // The reason for this is because PowerShell's important PSReadline @@ -134,6 +158,7 @@ export class TerminalService extends BrowserTerminalService implements ITerminal const expectedLocations = { 'Command Prompt': [`${system32Path}\\cmd.exe`], PowerShell: [`${system32Path}\\WindowsPowerShell\\v1.0\\powershell.exe`], + 'PowerShell Core': await this._getShellPathFromRegistry('pwsh'), 'WSL Bash': [`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`], 'Git Bash': [ `${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`, diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts index 16488ef5287..128576e59fe 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts @@ -13,11 +13,12 @@ import { getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/termin import { IDisposable } from 'vs/base/common/lifecycle'; import { IShellLaunchConfig, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal'; import { exec } from 'child_process'; +import { ILogService } from 'vs/platform/log/common/log'; export class TerminalProcess implements ITerminalChildProcess, IDisposable { private _exitCode: number; private _closeTimeout: any; - private _ptyProcess: pty.IPty; + private _ptyProcess: pty.IPty | undefined; private _currentTitle: string = ''; private _processStartupComplete: Promise; private _isDisposed: boolean = false; @@ -39,7 +40,8 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { cols: number, rows: number, env: platform.IProcessEnvironment, - windowsEnableConpty: boolean + windowsEnableConpty: boolean, + @ILogService private readonly _logService: ILogService ) { let shellName: string; if (os.platform() === 'win32') { @@ -51,7 +53,15 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { } this._initialCwd = cwd; - const useConpty = windowsEnableConpty && process.platform === 'win32' && getWindowsBuildNumber() >= 18309; + + // Only use ConPTY when the client is non WoW64 (see #72190) and the Windows build number is at least 18309 (for + // stability/performance reasons) + const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); + const useConpty = windowsEnableConpty && + process.platform === 'win32' && + !is32ProcessOn64Windows && + getWindowsBuildNumber() >= 18309; + const options: pty.IPtyForkOptions = { name: shellName, cwd, @@ -61,37 +71,42 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { experimentalUseConpty: useConpty }; - try { - this._ptyProcess = pty.spawn(shellLaunchConfig.executable!, shellLaunchConfig.args || [], options); - this._processStartupComplete = new Promise(c => { - this.onProcessIdReady((pid) => { - c(); - }); - }); - } catch (error) { - // The only time this is expected to happen is when the file specified to launch with does not exist. - this._exitCode = 2; - this._queueProcessExit(); - this._processStartupComplete = Promise.resolve(undefined); - return; - } - this._ptyProcess.on('data', (data) => { + // TODO: Need to verify whether executable is on $PATH, otherwise things like cmd.exe will break + // fs.stat(shellLaunchConfig.executable!, (err) => { + // if (err && err.code === 'ENOENT') { + // this._exitCode = SHELL_PATH_INVALID_EXIT_CODE; + // this._queueProcessExit(); + // this._processStartupComplete = Promise.resolve(undefined); + // return; + // } + this.setupPtyProcess(shellLaunchConfig, options); + // }); + } + + private setupPtyProcess(shellLaunchConfig: IShellLaunchConfig, options: pty.IPtyForkOptions): void { + const args = shellLaunchConfig.args || []; + this._logService.trace('IPty#spawn', shellLaunchConfig.executable, args, options); + const ptyProcess = pty.spawn(shellLaunchConfig.executable!, args, options); + this._ptyProcess = ptyProcess; + this._processStartupComplete = new Promise(c => { + this.onProcessIdReady(() => c()); + }); + ptyProcess.on('data', (data) => { this._onProcessData.fire(data); if (this._closeTimeout) { clearTimeout(this._closeTimeout); this._queueProcessExit(); } }); - this._ptyProcess.on('exit', (code) => { + ptyProcess.on('exit', (code) => { this._exitCode = code; this._queueProcessExit(); }); - + this._setupTitlePolling(ptyProcess); // TODO: We should no longer need to delay this since pty.spawn is sync setTimeout(() => { - this._sendProcessId(); + this._sendProcessId(ptyProcess); }, 500); - this._setupTitlePolling(); } public dispose(): void { @@ -106,15 +121,15 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { this._onProcessTitleChanged.dispose(); } - private _setupTitlePolling() { + private _setupTitlePolling(ptyProcess: pty.IPty) { // Send initial timeout async to give event listeners a chance to init setTimeout(() => { - this._sendProcessTitle(); + this._sendProcessTitle(ptyProcess); }, 0); // Setup polling this._titleInterval = setInterval(() => { - if (this._currentTitle !== this._ptyProcess.process) { - this._sendProcessTitle(); + if (this._currentTitle !== ptyProcess.process) { + this._sendProcessTitle(ptyProcess); } }, 200); } @@ -138,7 +153,10 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { // Attempt to kill the pty, it may have already been killed at this // point but we want to make sure try { - this._ptyProcess.kill(); + if (this._ptyProcess) { + this._logService.trace('IPty#kill'); + this._ptyProcess.kill(); + } } catch (ex) { // Swallow, the pty has already been killed } @@ -147,15 +165,15 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { }); } - private _sendProcessId() { - this._onProcessIdReady.fire(this._ptyProcess.pid); + private _sendProcessId(ptyProcess: pty.IPty) { + this._onProcessIdReady.fire(ptyProcess.pid); } - private _sendProcessTitle(): void { + private _sendProcessTitle(ptyProcess: pty.IPty): void { if (this._isDisposed) { return; } - this._currentTitle = this._ptyProcess.process; + this._currentTitle = ptyProcess.process; this._onProcessTitleChanged.fire(this._currentTitle); } @@ -168,9 +186,10 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { } public input(data: string): void { - if (this._isDisposed) { + if (this._isDisposed || !this._ptyProcess) { return; } + this._logService.trace('IPty#write', data); this._ptyProcess.write(data); } @@ -180,7 +199,12 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { } // Ensure that cols and rows are always >= 1, this prevents a native // exception in winpty. - this._ptyProcess.resize(Math.max(cols, 1), Math.max(rows, 1)); + if (this._ptyProcess) { + cols = Math.max(cols, 1); + rows = Math.max(rows, 1); + this._logService.trace('IPty#resize', cols, rows); + this._ptyProcess.resize(cols, rows); + } } public getInitialCwd(): Promise { @@ -190,6 +214,11 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { public getCwd(): Promise { if (platform.isMacintosh) { return new Promise(resolve => { + if (!this._ptyProcess) { + resolve(this._initialCwd); + return; + } + this._logService.trace('IPty#pid'); exec('lsof -p ' + this._ptyProcess.pid + ' | grep cwd', (error, stdout, stderr) => { if (stdout !== '') { resolve(stdout.substring(stdout.indexOf('/'), stdout.length - 1)); @@ -200,6 +229,11 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { if (platform.isLinux) { return new Promise(resolve => { + if (!this._ptyProcess) { + resolve(this._initialCwd); + return; + } + this._logService.trace('IPty#pid'); fs.readlink('/proc/' + this._ptyProcess.pid + '/cwd', (err, linkedstr) => { if (err) { resolve(this._initialCwd); diff --git a/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts b/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts index d841d023695..1bc875456dd 100644 --- a/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts +++ b/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts @@ -61,15 +61,15 @@ export class WindowsShellHelper implements IWindowsShellHelper { // If this is done on every linefeed, parsing ends up taking // significantly longer due to resetting timers. Note that this is // private API. - this._xterm.on('linefeed', () => this._newLineFeed = true); - this._xterm.on('cursormove', () => { + this._xterm.onLineFeed(() => this._newLineFeed = true); + this._xterm.onCursorMove(() => { if (this._newLineFeed) { this._onCheckShell.fire(undefined); } }); // Fire a new check for the shell when any key is pressed. - this._xterm.on('keypress', () => this._onCheckShell.fire(undefined)); + this._xterm.onKey(() => this._onCheckShell.fire(undefined)); }); } diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts index ec94ff8a518..a2d20eb2c2e 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts @@ -46,13 +46,13 @@ suite('Workbench - TerminalCommandTracker', () => { test('should track commands when the prompt is of sufficient size', () => { assert.equal(xterm.markers.length, 0); syncWrite(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm.emit('key', '\x0d'); + xterm._core._onKey.fire({ key: '\x0d' }); assert.equal(xterm.markers.length, 1); }); test('should not track commands when the prompt is too small', () => { assert.equal(xterm.markers.length, 0); syncWrite(xterm, '\x1b[2G'); // Move cursor to column 2 - xterm.emit('key', '\x0d'); + xterm._core._onKey.fire({ key: '\x0d' }); assert.equal(xterm.markers.length, 0); }); }); @@ -60,30 +60,30 @@ suite('Workbench - TerminalCommandTracker', () => { suite('Commands', () => { test('should scroll to the next and previous commands', () => { syncWrite(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm.emit('key', '\x0d'); // Mark line #10 + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line #10 assert.equal(xterm.markers[0].line, 9); for (let i = 0; i < 20; i++) { syncWrite(xterm, `\r\n`); } - assert.equal(xterm._core.buffer.ybase, 20); - assert.equal(xterm._core.buffer.ydisp, 20); + assert.equal(xterm.buffer.baseY, 20); + assert.equal(xterm.buffer.viewportY, 20); // Scroll to marker commandTracker.scrollToPreviousCommand(); - assert.equal(xterm._core.buffer.ydisp, 9); + assert.equal(xterm.buffer.viewportY, 9); // Scroll to top boundary commandTracker.scrollToPreviousCommand(); - assert.equal(xterm._core.buffer.ydisp, 0); + assert.equal(xterm.buffer.viewportY, 0); // Scroll to marker commandTracker.scrollToNextCommand(); - assert.equal(xterm._core.buffer.ydisp, 9); + assert.equal(xterm.buffer.viewportY, 9); // Scroll to bottom boundary commandTracker.scrollToNextCommand(); - assert.equal(xterm._core.buffer.ydisp, 20); + assert.equal(xterm.buffer.viewportY, 20); }); test('should select to the next and previous commands', () => { (window).matchMedia = () => { @@ -94,16 +94,16 @@ suite('Workbench - TerminalCommandTracker', () => { syncWrite(xterm, '\r0'); syncWrite(xterm, '\n\r1'); syncWrite(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm.emit('key', '\x0d'); // Mark line + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line assert.equal(xterm.markers[0].line, 10); syncWrite(xterm, '\n\r2'); syncWrite(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm.emit('key', '\x0d'); // Mark line + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line assert.equal(xterm.markers[1].line, 11); syncWrite(xterm, '\n\r3'); - assert.equal(xterm._core.buffer.ybase, 3); - assert.equal(xterm._core.buffer.ydisp, 3); + assert.equal(xterm.buffer.baseY, 3); + assert.equal(xterm.buffer.viewportY, 3); assert.equal(xterm.getSelection(), ''); commandTracker.selectToPreviousCommand(); @@ -124,16 +124,16 @@ suite('Workbench - TerminalCommandTracker', () => { syncWrite(xterm, '\r0'); syncWrite(xterm, '\n\r1'); syncWrite(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm.emit('key', '\x0d'); // Mark line + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line assert.equal(xterm.markers[0].line, 10); syncWrite(xterm, '\n\r2'); syncWrite(xterm, '\x1b[3G'); // Move cursor to column 3 - xterm.emit('key', '\x0d'); // Mark line + xterm._core._onKey.fire({ key: '\x0d' }); // Mark line assert.equal(xterm.markers[1].line, 11); syncWrite(xterm, '\n\r3'); - assert.equal(xterm._core.buffer.ybase, 3); - assert.equal(xterm._core.buffer.ydisp, 3); + assert.equal(xterm.buffer.baseY, 3); + assert.equal(xterm.buffer.viewportY, 3); assert.equal(xterm.getSelection(), ''); commandTracker.selectToPreviousLine(); diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 756bfc7733a..7c65d7b8956 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -236,7 +236,7 @@ class GenerateColorThemeAction extends Action { }, null, '\t'); contents = contents.replace(/\"__/g, '//"'); - return this.editorService.openEditor({ contents, language: 'jsonc' }); + return this.editorService.openEditor({ contents, mode: 'jsonc' }); } } diff --git a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts index 1142d57524b..fdabd1a9971 100644 --- a/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts @@ -234,8 +234,8 @@ CommandsRegistry.registerCommand('_workbench.captureSyntaxTokens', function (acc let fileName = basename(resource); let snapper = accessor.get(IInstantiationService).createInstance(Snapper); - return fileService.resolveContent(resource).then(content => { - return snapper.captureSyntaxTokens(fileName, content.value); + return fileService.readFile(resource).then(content => { + return snapper.captureSyntaxTokens(fileName, content.value.toString()); }); }; diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 133b3d28edd..2afb9ea3125 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -153,7 +153,7 @@ module.exports = function createWebviewManager(host) { scrollTarget.scrollIntoView(); } } else { - host.postMessage('did-click-link', node.href); + host.postMessage('did-click-link', node.href.baseVal || node.href); } event.preventDefault(); break; diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 0df01ea4d6f..85975f66fb2 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -170,7 +170,7 @@ export class WebviewEditorInput extends EditorInput { this._html = value; if (this._webview) { - this._webview.contents = value; + this._webview.html = value; this._currentWebviewHtml = value; } } diff --git a/src/vs/workbench/contrib/webview/common/webview.ts b/src/vs/workbench/contrib/webview/common/webview.ts index 3358c27dc92..6fefc9914e7 100644 --- a/src/vs/workbench/contrib/webview/common/webview.ts +++ b/src/vs/workbench/contrib/webview/common/webview.ts @@ -47,7 +47,7 @@ export interface WebviewContentOptions { export interface Webview { - contents: string; + html: string; options: WebviewContentOptions; initialScrollProgress: number; state: string | undefined; diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index e8ac299bd3a..b86cdfc4bac 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -16,7 +16,7 @@ import * as modes from 'vs/editor/common/modes'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IFileService } from 'vs/platform/files/common/files'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; @@ -118,7 +118,7 @@ class WebviewProtocolProvider extends Disposable { private readonly _extensionLocation: URI | undefined, private readonly _getLocalResourceRoots: () => ReadonlyArray, private readonly _environmentService: IEnvironmentService, - private readonly _fileService: IFileService, + private readonly _textFileService: ITextFileService, ) { super(); @@ -137,11 +137,11 @@ class WebviewProtocolProvider extends Disposable { const appRootUri = URI.file(this._environmentService.appRoot); - registerFileProtocol(contents, WebviewProtocol.CoreResource, this._fileService, undefined, () => [ + registerFileProtocol(contents, WebviewProtocol.CoreResource, this._textFileService, undefined, () => [ appRootUri ]); - registerFileProtocol(contents, WebviewProtocol.VsCodeResource, this._fileService, this._extensionLocation, () => + registerFileProtocol(contents, WebviewProtocol.VsCodeResource, this._textFileService, this._extensionLocation, () => this._getLocalResourceRoots() ); } @@ -354,6 +354,11 @@ class WebviewKeyboardHandler extends Disposable { } } +interface WebviewContent { + readonly html: string; + readonly options: WebviewContentOptions; + readonly state: string | undefined; +} export class WebviewElement extends Disposable implements Webview { private _webview: Electron.WebviewTag; @@ -361,8 +366,8 @@ export class WebviewElement extends Disposable implements Webview { private _webviewFindWidget: WebviewFindWidget; private _findStarted: boolean = false; - private _contents: string = ''; - private _state: string | undefined = undefined; + private content: WebviewContent; + private _focused = false; private readonly _onDidFocus = this._register(new Emitter()); @@ -370,19 +375,24 @@ export class WebviewElement extends Disposable implements Webview { constructor( private readonly _options: WebviewOptions, - private _contentOptions: WebviewContentOptions, + contentOptions: WebviewContentOptions, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @IEnvironmentService environmentService: IEnvironmentService, - @IFileService fileService: IFileService, + @ITextFileService textFileService: ITextFileService, @ITunnelService tunnelService: ITunnelService, @ITelemetryService telemetryService: ITelemetryService, @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); + this.content = { + html: '', + options: contentOptions, + state: undefined + }; + this._webview = document.createElement('webview'); this._webview.setAttribute('partition', `webview${Date.now()}`); - this._webview.setAttribute('webpreferences', 'contextIsolation=yes'); this._webview.style.flex = '0 1'; @@ -410,21 +420,21 @@ export class WebviewElement extends Disposable implements Webview { this._register(new WebviewProtocolProvider( this._webview, this._options.extension ? this._options.extension.location : undefined, - () => (this._contentOptions.localResourceRoots || []), + () => (this.content.options.localResourceRoots || []), environmentService, - fileService)); + textFileService)); this._register(new WebviewPortMappingProvider( session, _options.extension ? _options.extension.location : undefined, - () => (this._contentOptions.portMappings || []), + () => (this.content.options.portMappings || []), tunnelService, _options.extension ? _options.extension.id : undefined, telemetryService )); if (!this._options.allowSvgs) { - const svgBlocker = this._register(new SvgBlocker(session, this._contentOptions)); + const svgBlocker = this._register(new SvgBlocker(session, this.content.options)); svgBlocker.onDidBlockSvg(() => this.onDidBlockSvg()); } @@ -476,8 +486,9 @@ export class WebviewElement extends Disposable implements Webview { return; case 'do-update-state': - this._state = event.args[0]; - this._onDidUpdateState.fire(this._state); + const state = event.args[0]; + this.state = state; + this._onDidUpdateState.fire(state); return; case 'did-focus': @@ -542,42 +553,53 @@ export class WebviewElement extends Disposable implements Webview { this._send('initial-scroll-position', value); } - public set state(value: string | undefined) { - this._state = value; + public set state(state: string | undefined) { + this.content = { + html: this.content.html, + options: this.content.options, + state, + }; } - public set options(value: WebviewContentOptions) { - if (this._contentOptions && areWebviewInputOptionsEqual(value, this._contentOptions)) { + public set options(options: WebviewContentOptions) { + if (areWebviewInputOptionsEqual(options, this.content.options)) { return; } - this._contentOptions = value; - this._send('content', { - contents: this._contents, - options: this._contentOptions, - state: this._state - }); + this.content = { + html: this.content.html, + options: options, + state: this.content.state, + }; + this.doUpdateContent(); } - public set contents(value: string) { - this._contents = value; - this._send('content', { - contents: value, - options: this._contentOptions, - state: this._state - }); + public set html(value: string) { + this.content = { + html: value, + options: this.content.options, + state: this.content.state, + }; + this.doUpdateContent(); } - public update(value: string, options: WebviewContentOptions, retainContextWhenHidden: boolean) { - if (retainContextWhenHidden && value === this._contents && this._contentOptions && areWebviewInputOptionsEqual(options, this._contentOptions)) { + public update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean) { + if (retainContextWhenHidden && html === this.content.html && areWebviewInputOptionsEqual(options, this.content.options)) { return; } - this._contents = value; - this._contentOptions = options; + this.content = { + html: html, + options: options, + state: this.content.state, + }; + this.doUpdateContent(); + } + + private doUpdateContent() { this._send('content', { - contents: this._contents, - options: this._contentOptions, - state: this._state + contents: this.content.html, + options: this.content.options, + state: this.content.state }); } @@ -620,7 +642,6 @@ export class WebviewElement extends Disposable implements Webview { return colors; }, {} as { [key: string]: string }); - const styles = { 'vscode-font-family': '-apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif', 'vscode-font-weight': 'normal', @@ -717,7 +738,7 @@ export class WebviewElement extends Disposable implements Webview { } public reload() { - this.contents = this._contents; + this.doUpdateContent(); } public selectAll() { diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts index 8681fa6ce23..37ea9bbf6d0 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts @@ -6,16 +6,16 @@ import { getMediaMime, MIME_UNKNOWN } from 'vs/base/common/mime'; import { extname, sep } from 'vs/base/common/path'; import { startsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; -import { IFileService } from 'vs/platform/files/common/files'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; export const enum WebviewProtocol { CoreResource = 'vscode-core-resource', VsCodeResource = 'vscode-resource', } -function resolveContent(fileService: IFileService, resource: URI, mime: string, callback: any): void { - fileService.resolveContent(resource, { encoding: 'binary' }).then(contents => { +function resolveContent(textFileService: ITextFileService, resource: URI, mime: string, callback: any): void { + textFileService.read(resource, { encoding: 'binary' }).then(contents => { callback({ data: Buffer.from(contents.value, contents.encoding), mimeType: mime @@ -29,30 +29,32 @@ function resolveContent(fileService: IFileService, resource: URI, mime: string, export function registerFileProtocol( contents: Electron.WebContents, protocol: WebviewProtocol, - fileService: IFileService, + textFileService: ITextFileService, extensionLocation: URI | undefined, getRoots: () => ReadonlyArray ) { contents.session.protocol.registerBufferProtocol(protocol, (request, callback: any) => { - if (extensionLocation && extensionLocation.scheme === REMOTE_HOST_SCHEME) { - const requestUri = URI.parse(request.url); - const redirectedUri = URI.from({ - scheme: REMOTE_HOST_SCHEME, - authority: extensionLocation.authority, - path: '/vscode-resource', - query: JSON.stringify({ - requestResourcePath: requestUri.path - }) - }); - resolveContent(fileService, redirectedUri, getMimeType(requestUri), callback); - return; - } - const requestPath = URI.parse(request.url).path; const normalizedPath = URI.file(requestPath); for (const root of getRoots()) { - if (startsWith(normalizedPath.fsPath, root.fsPath + sep)) { - resolveContent(fileService, normalizedPath, getMimeType(normalizedPath), callback); + if (!startsWith(normalizedPath.fsPath, root.fsPath + sep)) { + continue; + } + + if (extensionLocation && extensionLocation.scheme === REMOTE_HOST_SCHEME) { + const requestUri = URI.parse(request.url); + const redirectedUri = URI.from({ + scheme: REMOTE_HOST_SCHEME, + authority: extensionLocation.authority, + path: '/vscode-resource', + query: JSON.stringify({ + requestResourcePath: requestUri.path + }) + }); + resolveContent(textFileService, redirectedUri, getMimeType(requestUri), callback); + return; + } else { + resolveContent(textFileService, normalizedPath, getMimeType(normalizedPath), callback); return; } } diff --git a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts index 1b74d9c8f35..f76817286f6 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts @@ -35,7 +35,7 @@ export class WalkThroughContentProvider implements ITextModelContentProvider, IW reject(err); } }); - }) : this.textFileService.resolve(URI.file(resource.fsPath)).then(content => content.value)); + }) : this.textFileService.readStream(URI.file(resource.fsPath)).then(content => content.value)); return content.then(content => { let codeEditorModel = this.modelService.getModel(resource); if (!codeEditorModel) { @@ -61,7 +61,7 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi } public provideTextContent(resource: URI): Promise { - return this.textFileService.resolve(URI.file(resource.fsPath)).then(content => { + return this.textFileService.readStream(URI.file(resource.fsPath)).then(content => { let codeEditorModel = this.modelService.getModel(resource); if (!codeEditorModel) { const j = parseInt(resource.fragment); diff --git a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts index 338d372c657..42feb610289 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts @@ -80,7 +80,7 @@ export class WalkThroughInput extends EditorInput { return this.options.telemetryFrom; } - getTelemetryDescriptor(): object { + getTelemetryDescriptor(): { [key: string]: unknown } { const descriptor = super.getTelemetryDescriptor(); descriptor['target'] = this.getTelemetryFrom(); /* __GDPR__FRAGMENT__ diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index f7ccc18d389..71f07611ed0 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -21,7 +21,7 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ADD_ROOT_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; -import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext, WorkbenchStateContext, WorkspaceFolderCountContext, RemoteFileDialogContext } from 'vs/workbench/common/contextkeys'; +import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext, WorkbenchStateContext, WorkspaceFolderCountContext, RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import { LogStorageAction } from 'vs/platform/storage/node/storageService'; @@ -36,12 +36,12 @@ import { LogStorageAction } from 'vs/platform/storage/node/storageService'; if (isMacintosh) { registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLocalFileFolderAction, OpenLocalFileFolderAction.ID, OpenLocalFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, RemoteFileDialogContext), 'File: Open Local...', fileCategory, RemoteFileDialogContext); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLocalFileFolderAction, OpenLocalFileFolderAction.ID, OpenLocalFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, RemoteFileDialogContext), 'File: Open Local...', fileCategory); } else { registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLocalFileAction, OpenLocalFileAction.ID, OpenLocalFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, RemoteFileDialogContext), 'File: Open Local File...', fileCategory, RemoteFileDialogContext); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLocalFolderAction, OpenLocalFolderAction.ID, OpenLocalFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }, RemoteFileDialogContext), 'File: Open Local Folder...', fileCategory, RemoteFileDialogContext); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLocalFileAction, OpenLocalFileAction.ID, OpenLocalFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }, RemoteFileDialogContext), 'File: Open Local File...', fileCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLocalFolderAction, OpenLocalFolderAction.ID, OpenLocalFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }, RemoteFileDialogContext), 'File: Open Local Folder...', fileCategory); } registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); @@ -636,6 +636,7 @@ import { LogStorageAction } from 'vs/platform/storage/node/storageService'; 'type': 'boolean', 'default': true, 'description': nls.localize('autoDetectHighContrast', "If enabled, will automatically change to high contrast theme if Windows is using a high contrast theme, and to dark theme when switching away from a Windows high contrast theme."), + 'scope': ConfigurationScope.APPLICATION, 'included': isWindows }, 'window.doubleClickIconToClose': { @@ -662,6 +663,7 @@ import { LogStorageAction } from 'vs/platform/storage/node/storageService'; 'type': 'boolean', 'default': true, 'description': nls.localize('window.nativeFullScreen', "Controls if native full-screen should be used on macOS. Disable this option to prevent macOS from creating a new space when going full-screen."), + 'scope': ConfigurationScope.APPLICATION, 'included': isMacintosh }, 'window.clickThroughInactive': { diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index b76d8960d08..1d13aba375f 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -41,9 +41,9 @@ import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-brow import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { FileService } from 'vs/workbench/services/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; -import { DiskFileSystemProvider } from 'vs/workbench/services/files2/electron-browser/diskFileSystemProvider'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files/electron-browser/diskFileSystemProvider'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteExtensionsFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; import { DefaultConfigurationExportHelper } from 'vs/workbench/services/configuration/node/configurationExportHelper'; @@ -91,7 +91,7 @@ class CodeRendererMain extends Disposable { const filesToWait = this.configuration.filesToWait; const filesToWaitPaths = filesToWait && filesToWait.paths; - [filesToWaitPaths, this.configuration.filesToOpen, this.configuration.filesToCreate, this.configuration.filesToDiff].forEach(paths => { + [filesToWaitPaths, this.configuration.filesToOpenOrCreate, this.configuration.filesToDiff].forEach(paths => { if (Array.isArray(paths)) { paths.forEach(path => { if (path.fileUri) { @@ -190,7 +190,7 @@ class CodeRendererMain extends Disposable { serviceCollection.set(IRemoteAgentService, remoteAgentService); // Files - const fileService = this._register(new FileService2(logService)); + const fileService = this._register(new FileService(logService)); serviceCollection.set(IFileService, fileService); const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService)); @@ -295,9 +295,9 @@ class CodeRendererMain extends Disposable { }, error => onUnexpectedError(error)); } - private createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: FileService2, remoteAgentService: IRemoteAgentService, logService: ILogService): Promise { + private createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: FileService, remoteAgentService: IRemoteAgentService, logService: ILogService): Promise { const configurationFileService = new ConfigurationFileService(); - fileService.whenReady.then(() => configurationFileService.fileService = fileService); + configurationFileService.fileService = fileService; const workspaceService = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), remoteAuthority: this.configuration.remoteAuthority, configurationCache: new ConfigurationCache(environmentService) }, configurationFileService, remoteAgentService); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 755efd95864..c3d153bcb44 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -11,10 +11,10 @@ import * as DOM from 'vs/base/browser/dom'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, Action } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; -import { toResource, IUntitledResourceInput, SideBySideEditor } from 'vs/workbench/common/editor'; +import { toResource, IUntitledResourceInput, SideBySideEditor, pathsToEditors } from 'vs/workbench/common/editor'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWindowsService, IWindowService, IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IPathData, IRunKeybindingInWindowRequest } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IWindowService, IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IRunKeybindingInWindowRequest } from 'vs/platform/windows/common/windows'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IWorkbenchThemeService, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; @@ -26,7 +26,7 @@ import { ipcRenderer as ipc, webFrame, crashReporter, Event } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { fillInActionBarActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { fillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -43,6 +43,8 @@ import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessi import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { coalesce } from 'vs/base/common/arrays'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { isEqual } from 'vs/base/common/resources'; const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), undefined, true, () => Promise.resolve(document.execCommand('undo'))), @@ -88,7 +90,8 @@ export class ElectronWindow extends Disposable { @IIntegrityService private readonly integrityService: IIntegrityService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @ITextFileService private readonly textFileService: ITextFileService ) { super(); @@ -228,11 +231,10 @@ export class ElectronWindow extends Disposable { // Listen to editor closing (if we run with --wait) const filesToWait = this.environmentService.configuration.filesToWait; if (filesToWait) { - const resourcesToWaitFor = coalesce(filesToWait.paths.map(p => p.fileUri)); const waitMarkerFile = filesToWait.waitMarkerFileUri; - const listenerDispose = this.editorService.onDidCloseEditor(() => this.onEditorClosed(listenerDispose, resourcesToWaitFor, waitMarkerFile)); + const resourcesToWaitFor = coalesce(filesToWait.paths.map(p => p.fileUri)); - this._register(listenerDispose); + this._register(this.trackClosedWaitFiles(waitMarkerFile, resourcesToWaitFor)); } } @@ -257,17 +259,6 @@ export class ElectronWindow extends Disposable { } } - private onEditorClosed(listenerDispose: IDisposable, resourcesToWaitFor: URI[], waitMarkerFile: URI): void { - - // In wait mode, listen to changes to the editors and wait until the files - // are closed that the user wants to wait for. When this happens we delete - // the wait marker file to signal to the outside that editing is done. - if (resourcesToWaitFor.every(resource => !this.editorService.isOpen({ resource }))) { - listenerDispose.dispose(); - this.fileService.del(waitMarkerFile); - } - } - private onContextMenu(e: MouseEvent): void { if (e.target instanceof HTMLElement) { const target = e.target; @@ -468,20 +459,16 @@ export class ElectronWindow extends Disposable { this.workspaceEditingService.addFolders(foldersToAdd); } - private onOpenFiles(request: IOpenFileRequest): void { + private async onOpenFiles(request: IOpenFileRequest): Promise { const inputs: IResourceEditor[] = []; const diffMode = !!(request.filesToDiff && (request.filesToDiff.length === 2)); - if (!diffMode && request.filesToOpen) { - inputs.push(...this.toInputs(request.filesToOpen, false)); - } - - if (!diffMode && request.filesToCreate) { - inputs.push(...this.toInputs(request.filesToCreate, true)); + if (!diffMode && request.filesToOpenOrCreate) { + inputs.push(...(await pathsToEditors(request.filesToOpenOrCreate, this.fileService))); } if (diffMode && request.filesToDiff) { - inputs.push(...this.toInputs(request.filesToDiff, false)); + inputs.push(...(await pathsToEditors(request.filesToDiff, this.fileService))); } if (inputs.length) { @@ -492,15 +479,50 @@ export class ElectronWindow extends Disposable { // In wait mode, listen to changes to the editors and wait until the files // are closed that the user wants to wait for. When this happens we delete // the wait marker file to signal to the outside that editing is done. - const resourcesToWaitFor = request.filesToWait.paths.map(p => URI.revive(p.fileUri)); const waitMarkerFile = URI.revive(request.filesToWait.waitMarkerFileUri); - const unbind = this.editorService.onDidCloseEditor(() => { - if (resourcesToWaitFor.every(resource => !this.editorService.isOpen({ resource }))) { - unbind.dispose(); - this.fileService.del(waitMarkerFile); + const resourcesToWaitFor = coalesce(request.filesToWait.paths.map(p => URI.revive(p.fileUri))); + this.trackClosedWaitFiles(waitMarkerFile, resourcesToWaitFor); + } + } + + private trackClosedWaitFiles(waitMarkerFile: URI, resourcesToWaitFor: URI[]): IDisposable { + const listener = this.editorService.onDidCloseEditor(async () => { + // In wait mode, listen to changes to the editors and wait until the files + // are closed that the user wants to wait for. When this happens we delete + // the wait marker file to signal to the outside that editing is done. + if (resourcesToWaitFor.every(resource => !this.editorService.isOpen({ resource }))) { + // If auto save is configured with the default delay (1s) it is possible + // to close the editor while the save still continues in the background. As such + // we have to also check if the files to wait for are dirty and if so wait + // for them to get saved before deleting the wait marker file. + const dirtyFilesToWait = this.textFileService.getDirty(resourcesToWaitFor); + if (dirtyFilesToWait.length > 0) { + await Promise.all(dirtyFilesToWait.map(async dirtyFileToWait => await this.joinResourceSaved(dirtyFileToWait))); + } + + listener.dispose(); + await this.fileService.del(waitMarkerFile); + } + }); + + return listener; + } + + private joinResourceSaved(resource: URI): Promise { + return new Promise(resolve => { + if (!this.textFileService.isDirty(resource)) { + return resolve(); // return early if resource is not dirty + } + + // Otherwise resolve promise when resource is saved + const listener = this.textFileService.models.onModelSaved(e => { + if (isEqual(resource, e.resource)) { + listener.dispose(); + + resolve(); } }); - } + }); } private openResources(resources: Array, diffMode: boolean): void { @@ -521,27 +543,6 @@ export class ElectronWindow extends Disposable { }); } - private toInputs(paths: IPathData[], isNew: boolean): IResourceEditor[] { - return paths.map(p => { - const resource = URI.revive(p.fileUri); - let input: IResourceInput | IUntitledResourceInput; - if (isNew) { - input = { filePath: resource!.fsPath, options: { pinned: true } }; - } else { - input = { resource, options: { pinned: true } }; - } - - if (!isNew && typeof p.lineNumber === 'number' && typeof p.columnNumber === 'number') { - input.options!.selection = { - startLineNumber: p.lineNumber, - startColumn: p.columnNumber - }; - } - - return input; - }); - } - dispose(): void { this.touchBarDisposables = dispose(this.touchBarDisposables); diff --git a/src/vs/workbench/services/backup/common/backup.ts b/src/vs/workbench/services/backup/common/backup.ts index 4ee81357b36..9d1b0208434 100644 --- a/src/vs/workbench/services/backup/common/backup.ts +++ b/src/vs/workbench/services/backup/common/backup.ts @@ -5,12 +5,14 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IResolveContentOptions, ITextSnapshot } from 'vs/platform/files/common/files'; -import { ITextBufferFactory } from 'vs/editor/common/model'; +import { ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; export const IBackupFileService = createDecorator('backupFileService'); -export const BACKUP_FILE_RESOLVE_OPTIONS: IResolveContentOptions = { acceptTextOnly: true, encoding: 'utf8' }; +export interface IResolvedBackup { + value: ITextBufferFactory; + meta?: T; +} /** * A service that handles any I/O and state associated with the backup system. @@ -45,8 +47,10 @@ export interface IBackupFileService { * @param resource The resource to back up. * @param content The content of the resource as snapshot. * @param versionId The version id of the resource to backup. + * @param meta The (optional) meta data of the resource to backup. This information + * can be restored later when loading the backup again. */ - backupResource(resource: URI, content: ITextSnapshot, versionId?: number): Promise; + backupResource(resource: URI, content: ITextSnapshot, versionId?: number, meta?: T): Promise; /** * Gets a list of file backups for the current workspace. @@ -58,10 +62,10 @@ export interface IBackupFileService { /** * Resolves the backup for the given resource. * - * @param value The contents from a backup resource as stream. - * @return The backup file's backed up content as text buffer factory. + * @param resource The resource to get the backup for. + * @return The backup file's backed up content and metadata if available. */ - resolveBackupContent(backup: URI): Promise; + resolveBackupContent(resource: URI): Promise>; /** * Discards the backup associated with a resource if it exists.. diff --git a/src/vs/workbench/services/backup/node/backupFileService.ts b/src/vs/workbench/services/backup/node/backupFileService.ts index 4990c43a7a7..850fe4b103a 100644 --- a/src/vs/workbench/services/backup/node/backupFileService.ts +++ b/src/vs/workbench/services/backup/node/backupFileService.ts @@ -3,92 +3,112 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'vs/base/common/path'; -import * as crypto from 'crypto'; -import * as pfs from 'vs/base/node/pfs'; -import { URI as Uri } from 'vs/base/common/uri'; +import { join } from 'vs/base/common/path'; +import { joinPath } from 'vs/base/common/resources'; +import { createHash } from 'crypto'; +import { URI } from 'vs/base/common/uri'; +import { coalesce } from 'vs/base/common/arrays'; +import { equals, deepClone } from 'vs/base/common/objects'; import { ResourceQueue } from 'vs/base/common/async'; -import { IBackupFileService, BACKUP_FILE_RESOLVE_OPTIONS } from 'vs/workbench/services/backup/common/backup'; -import { IFileService, ITextSnapshot, TextSnapshotReadable } from 'vs/platform/files/common/files'; +import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; +import { IFileService } from 'vs/platform/files/common/files'; import { readToMatchingString } from 'vs/base/node/stream'; -import { ITextBufferFactory } from 'vs/editor/common/model'; +import { ITextSnapshot } from 'vs/editor/common/model'; import { createTextBufferFactoryFromStream, createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { keys } from 'vs/base/common/map'; +import { keys, ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { TextSnapshotReadable } from 'vs/workbench/services/textfile/common/textfiles'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export interface IBackupFilesModel { - resolve(backupRoot: string): Promise; + resolve(backupRoot: URI): Promise; - add(resource: Uri, versionId?: number): void; - has(resource: Uri, versionId?: number): boolean; - get(): Uri[]; - remove(resource: Uri): void; + add(resource: URI, versionId?: number, meta?: object): void; + has(resource: URI, versionId?: number, meta?: object): boolean; + get(): URI[]; + remove(resource: URI): void; count(): number; clear(): void; } +interface IBackupCacheEntry { + versionId?: number; + meta?: object; +} + export class BackupFilesModel implements IBackupFilesModel { - private cache: { [resource: string]: number /* version ID */ } = Object.create(null); + private cache: ResourceMap = new ResourceMap(); - resolve(backupRoot: string): Promise { - return pfs.readDirsInDir(backupRoot).then(backupSchemas => { + constructor(private fileService: IFileService) { } - // For all supported schemas - return Promise.all(backupSchemas.map(backupSchema => { + async resolve(backupRoot: URI): Promise { + try { + const backupRootStat = await this.fileService.resolve(backupRoot); + if (backupRootStat.children) { + await Promise.all(backupRootStat.children + .filter(child => child.isDirectory) + .map(async backupSchema => { - // Read backup directory for backups - const backupSchemaPath = path.join(backupRoot, backupSchema); - return pfs.readdir(backupSchemaPath).then(backupHashes => { + // Read backup directory for backups + const backupSchemaStat = await this.fileService.resolve(backupSchema.resource); - // Remember known backups in our caches - backupHashes.forEach(backupHash => { - const backupResource = Uri.file(path.join(backupSchemaPath, backupHash)); - this.add(backupResource); - }); - }); - })); - }).then(() => this, error => this); + // Remember known backups in our caches + if (backupSchemaStat.children) { + backupSchemaStat.children.forEach(backupHash => this.add(backupHash.resource)); + } + })); + } + } catch (error) { + // ignore any errors + } + + return this; } - add(resource: Uri, versionId = 0): void { - this.cache[resource.toString()] = versionId; + add(resource: URI, versionId = 0, meta?: object): void { + this.cache.set(resource, { versionId, meta: deepClone(meta) }); // make sure to not store original meta in our cache... } count(): number { - return Object.keys(this.cache).length; + return this.cache.size; } - has(resource: Uri, versionId?: number): boolean { - const cachedVersionId = this.cache[resource.toString()]; - if (typeof cachedVersionId !== 'number') { + has(resource: URI, versionId?: number, meta?: object): boolean { + const entry = this.cache.get(resource); + if (!entry) { return false; // unknown resource } - if (typeof versionId === 'number') { - return versionId === cachedVersionId; // if we are asked with a specific version ID, make sure to test for it + if (typeof versionId === 'number' && versionId !== entry.versionId) { + return false; // different versionId + } + + if (meta && !equals(meta, entry.meta)) { + return false; // different metadata } return true; } - get(): Uri[] { - return Object.keys(this.cache).map(k => Uri.parse(k)); + get(): URI[] { + return this.cache.keys(); } - remove(resource: Uri): void { - delete this.cache[resource.toString()]; + remove(resource: URI): void { + this.cache.delete(resource); } clear(): void { - this.cache = Object.create(null); + this.cache.clear(); } } export class BackupFileService implements IBackupFileService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private impl: IBackupFileService; @@ -114,15 +134,15 @@ export class BackupFileService implements IBackupFileService { return this.impl.hasBackups(); } - loadBackupResource(resource: Uri): Promise { + loadBackupResource(resource: URI): Promise { return this.impl.loadBackupResource(resource); } - backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): Promise { - return this.impl.backupResource(resource, content, versionId); + backupResource(resource: URI, content: ITextSnapshot, versionId?: number, meta?: T): Promise { + return this.impl.backupResource(resource, content, versionId, meta); } - discardResourceBackup(resource: Uri): Promise { + discardResourceBackup(resource: URI): Promise { return this.impl.discardResourceBackup(resource); } @@ -130,26 +150,28 @@ export class BackupFileService implements IBackupFileService { return this.impl.discardAllWorkspaceBackups(); } - getWorkspaceFileBackups(): Promise { + getWorkspaceFileBackups(): Promise { return this.impl.getWorkspaceFileBackups(); } - resolveBackupContent(backup: Uri): Promise { + resolveBackupContent(backup: URI): Promise> { return this.impl.resolveBackupContent(backup); } - toBackupResource(resource: Uri): Uri { + toBackupResource(resource: URI): URI { return this.impl.toBackupResource(resource); } } class BackupFileServiceImpl implements IBackupFileService { - private static readonly META_MARKER = '\n'; + private static readonly PREAMBLE_END_MARKER = '\n'; + private static readonly PREAMBLE_META_SEPARATOR = ' '; // using a character that is know to be escaped in a URI as separator + private static readonly PREAMBLE_MAX_LENGTH = 10000; _serviceBrand: any; - private backupWorkspacePath: string; + private backupWorkspacePath: URI; private isShuttingDown: boolean; private ready: Promise; @@ -166,113 +188,165 @@ class BackupFileServiceImpl implements IBackupFileService { } initialize(backupWorkspacePath: string): void { - this.backupWorkspacePath = backupWorkspacePath; + this.backupWorkspacePath = URI.file(backupWorkspacePath); this.ready = this.init(); } private init(): Promise { - const model = new BackupFilesModel(); + const model = new BackupFilesModel(this.fileService); return model.resolve(this.backupWorkspacePath); } - hasBackups(): Promise { - return this.ready.then(model => { - return model.count() > 0; - }); + async hasBackups(): Promise { + const model = await this.ready; + + return model.count() > 0; } - loadBackupResource(resource: Uri): Promise { - return this.ready.then(model => { + async loadBackupResource(resource: URI): Promise { + const model = await this.ready; - // Return directly if we have a known backup with that resource - const backupResource = this.toBackupResource(resource); - if (model.has(backupResource)) { - return backupResource; - } - - return undefined; - }); - } - - backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): Promise { - if (this.isShuttingDown) { - return Promise.resolve(); + // Return directly if we have a known backup with that resource + const backupResource = this.toBackupResource(resource); + if (model.has(backupResource)) { + return backupResource; } - return this.ready.then(model => { - const backupResource = this.toBackupResource(resource); - if (model.has(backupResource, versionId)) { - return undefined; // return early if backup version id matches requested one + return undefined; + } + + async backupResource(resource: URI, content: ITextSnapshot, versionId?: number, meta?: T): Promise { + if (this.isShuttingDown) { + return; + } + + const model = await this.ready; + + const backupResource = this.toBackupResource(resource); + if (model.has(backupResource, versionId, meta)) { + return; // return early if backup version id matches requested one + } + + return this.ioOperationQueues.queueFor(backupResource).queue(async () => { + let preamble: string | undefined = undefined; + + // With Metadata: URI + META-START + Meta + END + if (meta) { + const preambleWithMeta = `${resource.toString()}${BackupFileServiceImpl.PREAMBLE_META_SEPARATOR}${JSON.stringify(meta)}${BackupFileServiceImpl.PREAMBLE_END_MARKER}`; + if (preambleWithMeta.length < BackupFileServiceImpl.PREAMBLE_MAX_LENGTH) { + preamble = preambleWithMeta; + } } - return this.ioOperationQueues.queueFor(backupResource).queue(() => { - const preamble = `${resource.toString()}${BackupFileServiceImpl.META_MARKER}`; + // Without Metadata: URI + END + if (!preamble) { + preamble = `${resource.toString()}${BackupFileServiceImpl.PREAMBLE_END_MARKER}`; + } - // Update content with value - return this.fileService.writeFile(backupResource, new TextSnapshotReadable(content, preamble)).then(() => model.add(backupResource, versionId)); - }); + // Update content with value + await this.fileService.writeFile(backupResource, new TextSnapshotReadable(content, preamble)); + + // Update model + model.add(backupResource, versionId, meta); }); } - discardResourceBackup(resource: Uri): Promise { - return this.ready.then(model => { - const backupResource = this.toBackupResource(resource); + async discardResourceBackup(resource: URI): Promise { + const model = await this.ready; + const backupResource = this.toBackupResource(resource); - return this.ioOperationQueues.queueFor(backupResource).queue(() => { - return pfs.rimraf(backupResource.fsPath, pfs.RimRafMode.MOVE).then(() => model.remove(backupResource)); - }); + return this.ioOperationQueues.queueFor(backupResource).queue(async () => { + await this.fileService.del(backupResource, { recursive: true }); + + model.remove(backupResource); }); } - discardAllWorkspaceBackups(): Promise { + async discardAllWorkspaceBackups(): Promise { this.isShuttingDown = true; - return this.ready.then(model => { - return pfs.rimraf(this.backupWorkspacePath, pfs.RimRafMode.MOVE).then(() => model.clear()); - }); + const model = await this.ready; + + await this.fileService.del(this.backupWorkspacePath, { recursive: true }); + + model.clear(); } - getWorkspaceFileBackups(): Promise { - return this.ready.then(model => { - const readPromises: Promise[] = []; + async getWorkspaceFileBackups(): Promise { + const model = await this.ready; - model.get().forEach(fileBackup => { - readPromises.push( - readToMatchingString(fileBackup.fsPath, BackupFileServiceImpl.META_MARKER, 2000, 10000).then(Uri.parse) - ); - }); + const backups = await Promise.all(model.get().map(async fileBackup => { + const backupPreamble = await readToMatchingString(fileBackup.fsPath, BackupFileServiceImpl.PREAMBLE_END_MARKER, BackupFileServiceImpl.PREAMBLE_MAX_LENGTH / 5, BackupFileServiceImpl.PREAMBLE_MAX_LENGTH); + if (!backupPreamble) { + return undefined; + } - return Promise.all(readPromises); - }); + // Preamble with metadata: URI + META-START + Meta + END + const metaStartIndex = backupPreamble.indexOf(BackupFileServiceImpl.PREAMBLE_META_SEPARATOR); + if (metaStartIndex > 0) { + return URI.parse(backupPreamble.substring(0, metaStartIndex)); + } + + // Preamble without metadata: URI + END + else { + return URI.parse(backupPreamble); + } + })); + + return coalesce(backups); } - resolveBackupContent(backup: Uri): Promise { - return this.fileService.resolveStreamContent(backup, BACKUP_FILE_RESOLVE_OPTIONS).then(content => { + async resolveBackupContent(backup: URI): Promise> { - // Add a filter method to filter out everything until the meta marker - let metaFound = false; - const metaPreambleFilter = (chunk: string) => { - if (!metaFound && chunk) { - const metaIndex = chunk.indexOf(BackupFileServiceImpl.META_MARKER); - if (metaIndex === -1) { - return ''; // meta not yet found, return empty string - } + // Metadata extraction + let metaRaw = ''; + let metaEndFound = false; - metaFound = true; - return chunk.substr(metaIndex + 1); // meta found, return everything after + // Add a filter method to filter out everything until the meta end marker + const metaPreambleFilter = (chunk: VSBuffer) => { + const chunkString = chunk.toString(); + + if (!metaEndFound) { + const metaEndIndex = chunkString.indexOf(BackupFileServiceImpl.PREAMBLE_END_MARKER); + if (metaEndIndex === -1) { + metaRaw += chunkString; + + return VSBuffer.fromString(''); // meta not yet found, return empty string } - return chunk; - }; + metaEndFound = true; + metaRaw += chunkString.substring(0, metaEndIndex); // ensure to get last chunk from metadata - return createTextBufferFactoryFromStream(content.value, metaPreambleFilter); - }); + return VSBuffer.fromString(chunkString.substr(metaEndIndex + 1)); // meta found, return everything after + } + + return chunk; + }; + + // Read backup into factory + const content = await this.fileService.readFileStream(backup); + const factory = await createTextBufferFactoryFromStream(content.value, metaPreambleFilter); + + // Trigger read for meta data extraction from the filter above + factory.getFirstLineText(1); + + let meta: T | undefined; + const metaStartIndex = metaRaw.indexOf(BackupFileServiceImpl.PREAMBLE_META_SEPARATOR); + if (metaStartIndex !== -1) { + try { + meta = JSON.parse(metaRaw.substr(metaStartIndex + 1)); + } catch (error) { + // ignore JSON parse errors + } + } + + return { value: factory, meta }; } - toBackupResource(resource: Uri): Uri { - return Uri.file(path.join(this.backupWorkspacePath, resource.scheme, hashPath(resource))); + toBackupResource(resource: URI): URI { + return joinPath(this.backupWorkspacePath, resource.scheme, hashPath(resource)); } } @@ -286,7 +360,7 @@ export class InMemoryBackupFileService implements IBackupFileService { return Promise.resolve(this.backups.size > 0); } - loadBackupResource(resource: Uri): Promise { + loadBackupResource(resource: URI): Promise { const backupResource = this.toBackupResource(resource); if (this.backups.has(backupResource.toString())) { return Promise.resolve(backupResource); @@ -295,27 +369,27 @@ export class InMemoryBackupFileService implements IBackupFileService { return Promise.resolve(undefined); } - backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): Promise { + backupResource(resource: URI, content: ITextSnapshot, versionId?: number, meta?: T): Promise { const backupResource = this.toBackupResource(resource); this.backups.set(backupResource.toString(), content); return Promise.resolve(); } - resolveBackupContent(backupResource: Uri): Promise { + resolveBackupContent(backupResource: URI): Promise> { const snapshot = this.backups.get(backupResource.toString()); if (snapshot) { - return Promise.resolve(createTextBufferFactoryFromSnapshot(snapshot)); + return Promise.resolve({ value: createTextBufferFactoryFromSnapshot(snapshot) }); } - return Promise.resolve(undefined); + return Promise.reject('Unexpected backup resource to resolve'); } - getWorkspaceFileBackups(): Promise { - return Promise.resolve(keys(this.backups).map(key => Uri.parse(key))); + getWorkspaceFileBackups(): Promise { + return Promise.resolve(keys(this.backups).map(key => URI.parse(key))); } - discardResourceBackup(resource: Uri): Promise { + discardResourceBackup(resource: URI): Promise { this.backups.delete(this.toBackupResource(resource).toString()); return Promise.resolve(); @@ -327,17 +401,17 @@ export class InMemoryBackupFileService implements IBackupFileService { return Promise.resolve(); } - toBackupResource(resource: Uri): Uri { - return Uri.file(path.join(resource.scheme, hashPath(resource))); + toBackupResource(resource: URI): URI { + return URI.file(join(resource.scheme, hashPath(resource))); } } /* * Exported only for testing */ -export function hashPath(resource: Uri): string { +export function hashPath(resource: URI): string { const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString(); - return crypto.createHash('md5').update(str).digest('hex'); + return createHash('md5').update(str).digest('hex'); } registerSingleton(IBackupFileService, BackupFileService); \ No newline at end of file diff --git a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts deleted file mode 100644 index 8076b3c25bf..00000000000 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ /dev/null @@ -1,411 +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 * as platform from 'vs/base/common/platform'; -import * as crypto from 'crypto'; -import * as os from 'os'; -import * as fs from 'fs'; -import * as path from 'vs/base/common/path'; -import * as pfs from 'vs/base/node/pfs'; -import { URI as Uri } from 'vs/base/common/uri'; -import { BackupFileService, BackupFilesModel, hashPath } from 'vs/workbench/services/backup/node/backupFileService'; -import { LegacyFileService } from 'vs/workbench/services/files/node/fileService'; -import { TextModel, createTextBufferFactory } from 'vs/editor/common/model/textModel'; -import { TestContextService, TestTextResourceConfigurationService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; -import { getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; -import { DefaultEndOfLine } from 'vs/editor/common/model'; -import { snapshotToString } from 'vs/platform/files/common/files'; -import { Schemas } from 'vs/base/common/network'; -import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; -import { NullLogService } from 'vs/platform/log/common/log'; -import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; -import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; -import { parseArgs } from 'vs/platform/environment/node/argv'; - -const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice'); -const backupHome = path.join(parentDir, 'Backups'); -const workspacesJsonPath = path.join(backupHome, 'workspaces.json'); - -const workspaceResource = Uri.file(platform.isWindows ? 'c:\\workspace' : '/workspace'); -const workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource)); -const fooFile = Uri.file(platform.isWindows ? 'c:\\Foo' : '/Foo'); -const barFile = Uri.file(platform.isWindows ? 'c:\\Bar' : '/Bar'); -const untitledFile = Uri.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); -const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile)); -const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile)); -const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile)); - -class TestBackupEnvironmentService extends WorkbenchEnvironmentService { - - private config: IWindowConfiguration; - - constructor(workspaceBackupPath: string) { - super(parseArgs(process.argv) as IWindowConfiguration, process.execPath); - - this.config = Object.create(null); - this.config.backupPath = workspaceBackupPath; - } - - get configuration(): IWindowConfiguration { - return this.config; - } -} - -class TestBackupFileService extends BackupFileService { - constructor(workspace: Uri, backupHome: string, workspacesJsonPath: string) { - const fileService = new FileService2(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.setLegacyService(new LegacyFileService( - fileService, - new TestContextService(new Workspace(workspace.fsPath, toWorkspaceFolders([{ path: workspace.fsPath }]))), - TestEnvironmentService, - new TestTextResourceConfigurationService(), - )); - const environmentService = new TestBackupEnvironmentService(workspaceBackupPath); - - super(environmentService, fileService); - } - - public toBackupResource(resource: Uri): Uri { - return super.toBackupResource(resource); - } -} - -suite('BackupFileService', () => { - let service: TestBackupFileService; - - setup(() => { - service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath); - - // Delete any existing backups completely and then re-create it. - return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE).then(() => { - return pfs.mkdirp(backupHome).then(() => { - return pfs.writeFile(workspacesJsonPath, ''); - }); - }); - }); - - teardown(() => { - return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); - }); - - suite('hashPath', () => { - test('should correctly hash the path for untitled scheme URIs', () => { - const uri = Uri.from({ - scheme: 'untitled', - path: 'Untitled-1' - }); - const actual = hashPath(uri); - // If these hashes change people will lose their backed up files! - assert.equal(actual, '13264068d108c6901b3592ea654fcd57'); - assert.equal(actual, crypto.createHash('md5').update(uri.fsPath).digest('hex')); - }); - - test('should correctly hash the path for file scheme URIs', () => { - const uri = Uri.file('/foo'); - const actual = hashPath(uri); - // If these hashes change people will lose their backed up files! - if (platform.isWindows) { - assert.equal(actual, 'dec1a583f52468a020bd120c3f01d812'); - } else { - assert.equal(actual, '1effb2475fcfba4f9e8b8a1dbc8f3caf'); - } - assert.equal(actual, crypto.createHash('md5').update(uri.fsPath).digest('hex')); - }); - }); - - suite('getBackupResource', () => { - test('should get the correct backup path for text files', () => { - // Format should be: /// - const backupResource = fooFile; - const workspaceHash = hashPath(workspaceResource); - const filePathHash = hashPath(backupResource); - const expectedPath = Uri.file(path.join(backupHome, workspaceHash, 'file', filePathHash)).fsPath; - assert.equal(service.toBackupResource(backupResource).fsPath, expectedPath); - }); - - test('should get the correct backup path for untitled files', () => { - // Format should be: /// - const backupResource = Uri.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); - const workspaceHash = hashPath(workspaceResource); - const filePathHash = hashPath(backupResource); - const expectedPath = Uri.file(path.join(backupHome, workspaceHash, 'untitled', filePathHash)).fsPath; - assert.equal(service.toBackupResource(backupResource).fsPath, expectedPath); - }); - }); - - suite('loadBackupResource', () => { - test('should return whether a backup resource exists', () => { - return pfs.mkdirp(path.dirname(fooBackupPath)).then(() => { - fs.writeFileSync(fooBackupPath, 'foo'); - service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath); - return service.loadBackupResource(fooFile).then(resource => { - assert.ok(resource); - assert.equal(path.basename(resource!.fsPath), path.basename(fooBackupPath)); - return service.hasBackups().then(hasBackups => { - assert.ok(hasBackups); - }); - }); - }); - }); - }); - - suite('backupResource', () => { - test('text file', function () { - return service.backupResource(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); - assert.equal(fs.existsSync(fooBackupPath), true); - assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`); - }); - }); - - test('untitled file', function () { - return service.backupResource(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); - assert.equal(fs.existsSync(untitledBackupPath), true); - assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`); - }); - }); - - test('text file (ITextSnapshot)', function () { - const model = TextModel.createFromString('test'); - - return service.backupResource(fooFile, model.createSnapshot()).then(() => { - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); - assert.equal(fs.existsSync(fooBackupPath), true); - assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`); - model.dispose(); - }); - }); - - test('untitled file (ITextSnapshot)', function () { - const model = TextModel.createFromString('test'); - - return service.backupResource(untitledFile, model.createSnapshot()).then(() => { - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); - assert.equal(fs.existsSync(untitledBackupPath), true); - assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`); - model.dispose(); - }); - }); - - test('text file (large file, ITextSnapshot)', function () { - const largeString = (new Array(10 * 1024)).join('Large String\n'); - const model = TextModel.createFromString(largeString); - - return service.backupResource(fooFile, model.createSnapshot()).then(() => { - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); - assert.equal(fs.existsSync(fooBackupPath), true); - assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\n${largeString}`); - model.dispose(); - }); - }); - - test('untitled file (large file, ITextSnapshot)', function () { - const largeString = (new Array(10 * 1024)).join('Large String\n'); - const model = TextModel.createFromString(largeString); - - return service.backupResource(untitledFile, model.createSnapshot()).then(() => { - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); - assert.equal(fs.existsSync(untitledBackupPath), true); - assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\n${largeString}`); - model.dispose(); - }); - }); - }); - - suite('discardResourceBackup', () => { - test('text file', function () { - return service.backupResource(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); - return service.discardResourceBackup(fooFile).then(() => { - assert.equal(fs.existsSync(fooBackupPath), false); - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 0); - }); - }); - }); - - test('untitled file', function () { - return service.backupResource(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); - return service.discardResourceBackup(untitledFile).then(() => { - assert.equal(fs.existsSync(untitledBackupPath), false); - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 0); - }); - }); - }); - }); - - suite('discardAllWorkspaceBackups', () => { - test('text file', function () { - return service.backupResource(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); - return service.backupResource(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 2); - return service.discardAllWorkspaceBackups().then(() => { - assert.equal(fs.existsSync(fooBackupPath), false); - assert.equal(fs.existsSync(barBackupPath), false); - assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'file')), false); - }); - }); - }); - }); - - test('untitled file', function () { - return service.backupResource(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); - return service.discardAllWorkspaceBackups().then(() => { - assert.equal(fs.existsSync(untitledBackupPath), false); - assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'untitled')), false); - }); - }); - }); - - test('should disable further backups', function () { - return service.discardAllWorkspaceBackups().then(() => { - return service.backupResource(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - assert.equal(fs.existsSync(workspaceBackupPath), false); - }); - }); - }); - }); - - suite('getWorkspaceFileBackups', () => { - test('("file") - text file', () => { - return service.backupResource(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - return service.getWorkspaceFileBackups().then(textFiles => { - assert.deepEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath]); - return service.backupResource(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - return service.getWorkspaceFileBackups().then(textFiles => { - assert.deepEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath, barFile.fsPath]); - }); - }); - }); - }); - }); - - test('("file") - untitled file', () => { - return service.backupResource(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - return service.getWorkspaceFileBackups().then(textFiles => { - assert.deepEqual(textFiles.map(f => f.fsPath), [untitledFile.fsPath]); - }); - }); - }); - - test('("untitled") - untitled file', () => { - return service.backupResource(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - return service.getWorkspaceFileBackups().then(textFiles => { - assert.deepEqual(textFiles.map(f => f.fsPath), ['Untitled-1']); - }); - }); - }); - }); - - test('resolveBackupContent', () => { - test('should restore the original contents (untitled file)', () => { - const contents = 'test\nand more stuff'; - service.backupResource(untitledFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - service.resolveBackupContent(service.toBackupResource(untitledFile)).then(factory => { - assert.equal(contents, snapshotToString(factory!.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true))); - }); - }); - }); - - test('should restore the original contents (text file)', () => { - const contents = [ - 'Lorem ipsum ', - 'dolor öäü sit amet ', - 'consectetur ', - 'adipiscing ßß elit', - ].join(''); - - service.backupResource(fooFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { - service.resolveBackupContent(service.toBackupResource(untitledFile)).then(factory => { - assert.equal(contents, snapshotToString(factory!.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true))); - }); - }); - }); - }); -}); - -suite('BackupFilesModel', () => { - test('simple', () => { - const model = new BackupFilesModel(); - - const resource1 = Uri.file('test.html'); - - assert.equal(model.has(resource1), false); - - model.add(resource1); - - assert.equal(model.has(resource1), true); - assert.equal(model.has(resource1, 0), true); - assert.equal(model.has(resource1, 1), false); - - model.remove(resource1); - - assert.equal(model.has(resource1), false); - - model.add(resource1); - - assert.equal(model.has(resource1), true); - assert.equal(model.has(resource1, 0), true); - assert.equal(model.has(resource1, 1), false); - - model.clear(); - - assert.equal(model.has(resource1), false); - - model.add(resource1, 1); - - assert.equal(model.has(resource1), true); - assert.equal(model.has(resource1, 0), false); - assert.equal(model.has(resource1, 1), true); - - const resource2 = Uri.file('test1.html'); - const resource3 = Uri.file('test2.html'); - const resource4 = Uri.file('test3.html'); - - model.add(resource2); - model.add(resource3); - model.add(resource4); - - assert.equal(model.has(resource1), true); - assert.equal(model.has(resource2), true); - assert.equal(model.has(resource3), true); - assert.equal(model.has(resource4), true); - }); - - test('resolve', () => { - return pfs.mkdirp(path.dirname(fooBackupPath)).then(() => { - fs.writeFileSync(fooBackupPath, 'foo'); - - const model = new BackupFilesModel(); - - return model.resolve(workspaceBackupPath).then(model => { - assert.equal(model.has(Uri.file(fooBackupPath)), true); - }); - }); - }); - - test('get', () => { - const model = new BackupFilesModel(); - - assert.deepEqual(model.get(), []); - - const file1 = Uri.file('/root/file/foo.html'); - const file2 = Uri.file('/root/file/bar.html'); - const untitled = Uri.file('/root/untitled/bar.html'); - - model.add(file1); - model.add(file2); - model.add(untitled); - - assert.deepEqual(model.get().map(f => f.fsPath), [file1.fsPath, file2.fsPath, untitled.fsPath]); - }); -}); diff --git a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts new file mode 100644 index 00000000000..52a5bc95d1c --- /dev/null +++ b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts @@ -0,0 +1,579 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as platform from 'vs/base/common/platform'; +import * as crypto from 'crypto'; +import * as os from 'os'; +import * as fs from 'fs'; +import * as path from 'vs/base/common/path'; +import * as pfs from 'vs/base/node/pfs'; +import { URI } from 'vs/base/common/uri'; +import { BackupFileService, BackupFilesModel, hashPath } from 'vs/workbench/services/backup/node/backupFileService'; +import { TextModel, createTextBufferFactory } from 'vs/editor/common/model/textModel'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { DefaultEndOfLine } from 'vs/editor/common/model'; +import { Schemas } from 'vs/base/common/network'; +import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { FileService } from 'vs/workbench/services/files/common/fileService'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; +import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; +import { parseArgs } from 'vs/platform/environment/node/argv'; +import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { IFileService } from 'vs/platform/files/common/files'; + +const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice'); +const backupHome = path.join(parentDir, 'Backups'); +const workspacesJsonPath = path.join(backupHome, 'workspaces.json'); + +const workspaceResource = URI.file(platform.isWindows ? 'c:\\workspace' : '/workspace'); +const workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource)); +const fooFile = URI.file(platform.isWindows ? 'c:\\Foo' : '/Foo'); +const customFile = URI.parse('customScheme://some/path'); +const customFileWithFragment = URI.parse('customScheme2://some/path#fragment'); +const barFile = URI.file(platform.isWindows ? 'c:\\Bar' : '/Bar'); +const fooBarFile = URI.file(platform.isWindows ? 'c:\\Foo Bar' : '/Foo Bar'); +const untitledFile = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); +const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile)); +const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile)); +const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile)); + +class TestBackupEnvironmentService extends WorkbenchEnvironmentService { + + private config: IWindowConfiguration; + + constructor(workspaceBackupPath: string) { + super(parseArgs(process.argv) as IWindowConfiguration, process.execPath); + + this.config = Object.create(null); + this.config.backupPath = workspaceBackupPath; + } + + get configuration(): IWindowConfiguration { + return this.config; + } +} + +class TestBackupFileService extends BackupFileService { + + readonly fileService: IFileService; + + constructor(workspace: URI, backupHome: string, workspacesJsonPath: string) { + const fileService = new FileService(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); + const environmentService = new TestBackupEnvironmentService(workspaceBackupPath); + + super(environmentService, fileService); + + this.fileService = fileService; + } + + toBackupResource(resource: URI): URI { + return super.toBackupResource(resource); + } +} + +suite('BackupFileService', () => { + let service: TestBackupFileService; + + setup(async () => { + service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath); + + // Delete any existing backups completely and then re-create it. + await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + await pfs.mkdirp(backupHome); + + return pfs.writeFile(workspacesJsonPath, ''); + }); + + teardown(() => { + return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + }); + + suite('hashPath', () => { + test('should correctly hash the path for untitled scheme URIs', () => { + const uri = URI.from({ + scheme: 'untitled', + path: 'Untitled-1' + }); + const actual = hashPath(uri); + // If these hashes change people will lose their backed up files! + assert.equal(actual, '13264068d108c6901b3592ea654fcd57'); + assert.equal(actual, crypto.createHash('md5').update(uri.fsPath).digest('hex')); + }); + + test('should correctly hash the path for file scheme URIs', () => { + const uri = URI.file('/foo'); + const actual = hashPath(uri); + // If these hashes change people will lose their backed up files! + if (platform.isWindows) { + assert.equal(actual, 'dec1a583f52468a020bd120c3f01d812'); + } else { + assert.equal(actual, '1effb2475fcfba4f9e8b8a1dbc8f3caf'); + } + assert.equal(actual, crypto.createHash('md5').update(uri.fsPath).digest('hex')); + }); + }); + + suite('getBackupResource', () => { + test('should get the correct backup path for text files', () => { + // Format should be: /// + const backupResource = fooFile; + const workspaceHash = hashPath(workspaceResource); + const filePathHash = hashPath(backupResource); + const expectedPath = URI.file(path.join(backupHome, workspaceHash, 'file', filePathHash)).fsPath; + assert.equal(service.toBackupResource(backupResource).fsPath, expectedPath); + }); + + test('should get the correct backup path for untitled files', () => { + // Format should be: /// + const backupResource = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); + const workspaceHash = hashPath(workspaceResource); + const filePathHash = hashPath(backupResource); + const expectedPath = URI.file(path.join(backupHome, workspaceHash, 'untitled', filePathHash)).fsPath; + assert.equal(service.toBackupResource(backupResource).fsPath, expectedPath); + }); + }); + + suite('loadBackupResource', () => { + test('should return whether a backup resource exists', async () => { + await pfs.mkdirp(path.dirname(fooBackupPath)); + fs.writeFileSync(fooBackupPath, 'foo'); + service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath); + const resource = await service.loadBackupResource(fooFile); + assert.ok(resource); + assert.equal(path.basename(resource!.fsPath), path.basename(fooBackupPath)); + const hasBackups = await service.hasBackups(); + assert.ok(hasBackups); + }); + }); + + suite('backupResource', () => { + test('text file', async () => { + await service.backupResource(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); + assert.equal(fs.existsSync(fooBackupPath), true); + assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`); + }); + + test('text file (with meta)', async () => { + await service.backupResource(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false), undefined, { etag: '678', orphaned: true }); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); + assert.equal(fs.existsSync(fooBackupPath), true); + assert.equal(fs.readFileSync(fooBackupPath).toString(), `${fooFile.toString()} {"etag":"678","orphaned":true}\ntest`); + }); + + test('untitled file', async () => { + await service.backupResource(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); + assert.equal(fs.existsSync(untitledBackupPath), true); + assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`); + }); + + test('text file (ITextSnapshot)', async () => { + const model = TextModel.createFromString('test'); + + await service.backupResource(fooFile, model.createSnapshot()); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); + assert.equal(fs.existsSync(fooBackupPath), true); + assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\ntest`); + model.dispose(); + }); + + test('untitled file (ITextSnapshot)', async () => { + const model = TextModel.createFromString('test'); + + await service.backupResource(untitledFile, model.createSnapshot()); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); + assert.equal(fs.existsSync(untitledBackupPath), true); + assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\ntest`); + model.dispose(); + }); + + test('text file (large file, ITextSnapshot)', async () => { + const largeString = (new Array(10 * 1024)).join('Large String\n'); + const model = TextModel.createFromString(largeString); + + await service.backupResource(fooFile, model.createSnapshot()); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); + assert.equal(fs.existsSync(fooBackupPath), true); + assert.equal(fs.readFileSync(fooBackupPath), `${fooFile.toString()}\n${largeString}`); + model.dispose(); + }); + + test('untitled file (large file, ITextSnapshot)', async () => { + const largeString = (new Array(10 * 1024)).join('Large String\n'); + const model = TextModel.createFromString(largeString); + + await service.backupResource(untitledFile, model.createSnapshot()); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); + assert.equal(fs.existsSync(untitledBackupPath), true); + assert.equal(fs.readFileSync(untitledBackupPath), `${untitledFile.toString()}\n${largeString}`); + model.dispose(); + }); + }); + + suite('discardResourceBackup', () => { + test('text file', async () => { + await service.backupResource(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); + await service.discardResourceBackup(fooFile); + assert.equal(fs.existsSync(fooBackupPath), false); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 0); + }); + + test('untitled file', async () => { + await service.backupResource(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); + await service.discardResourceBackup(untitledFile); + assert.equal(fs.existsSync(untitledBackupPath), false); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 0); + }); + }); + + suite('discardAllWorkspaceBackups', () => { + test('text file', async () => { + await service.backupResource(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); + await service.backupResource(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 2); + await service.discardAllWorkspaceBackups(); + assert.equal(fs.existsSync(fooBackupPath), false); + assert.equal(fs.existsSync(barBackupPath), false); + assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'file')), false); + }); + + test('untitled file', async () => { + await service.backupResource(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); + await service.discardAllWorkspaceBackups(); + assert.equal(fs.existsSync(untitledBackupPath), false); + assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'untitled')), false); + }); + + test('should disable further backups', async () => { + await service.discardAllWorkspaceBackups(); + await service.backupResource(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + assert.equal(fs.existsSync(workspaceBackupPath), false); + }); + }); + + suite('getWorkspaceFileBackups', () => { + test('("file") - text file', async () => { + await service.backupResource(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + const textFiles = await service.getWorkspaceFileBackups(); + assert.deepEqual(textFiles.map(f => f.fsPath), [fooFile.fsPath]); + await service.backupResource(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + const textFiles_1 = await service.getWorkspaceFileBackups(); + assert.deepEqual(textFiles_1.map(f => f.fsPath), [fooFile.fsPath, barFile.fsPath]); + }); + + test('("file") - untitled file', async () => { + await service.backupResource(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + const textFiles = await service.getWorkspaceFileBackups(); + assert.deepEqual(textFiles.map(f => f.fsPath), [untitledFile.fsPath]); + }); + + test('("untitled") - untitled file', async () => { + await service.backupResource(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); + const textFiles = await service.getWorkspaceFileBackups(); + assert.deepEqual(textFiles.map(f => f.fsPath), ['Untitled-1']); + }); + }); + + suite('resolveBackupContent', () => { + + interface IBackupTestMetaData { + mtime?: number; + size?: number; + etag?: string; + orphaned?: boolean; + } + + test('should restore the original contents (untitled file)', async () => { + const contents = 'test\nand more stuff'; + + await testResolveBackup(untitledFile, contents); + }); + + test('should restore the original contents (untitled file with metadata)', async () => { + const contents = 'test\nand more stuff'; + + const meta = { + etag: 'the Etag', + size: 666, + mtime: Date.now(), + orphaned: true + }; + + await testResolveBackup(untitledFile, contents, meta); + }); + + test('should restore the original contents (text file)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'consectetur ', + 'adipiscing ßß elit' + ].join(''); + + await testResolveBackup(fooFile, contents); + }); + + test('should restore the original contents (text file - custom scheme)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'consectetur ', + 'adipiscing ßß elit' + ].join(''); + + await testResolveBackup(customFile, contents); + }); + + test('should restore the original contents (text file with metadata)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'adipiscing ßß elit', + 'consectetur ' + ].join(''); + + const meta = { + etag: 'theEtag', + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await testResolveBackup(fooFile, contents, meta); + }); + + test('should restore the original contents (text file with metadata changed once)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'adipiscing ßß elit', + 'consectetur ' + ].join(''); + + const meta = { + etag: 'theEtag', + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await testResolveBackup(fooFile, contents, meta); + + // Change meta and test again + meta.size = 999; + await testResolveBackup(fooFile, contents, meta); + }); + + test('should restore the original contents (text file with broken metadata)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'adipiscing ßß elit', + 'consectetur ' + ].join(''); + + const meta = { + etag: 'theEtag', + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await service.backupResource(fooFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1, meta); + + assert.ok(await service.loadBackupResource(fooFile)); + + const fileContents = fs.readFileSync(fooBackupPath).toString(); + assert.equal(fileContents.indexOf(fooFile.toString()), 0); + + const metaIndex = fileContents.indexOf('{'); + const newFileContents = fileContents.substring(0, metaIndex) + '{{' + fileContents.substr(metaIndex); + fs.writeFileSync(fooBackupPath, newFileContents); + + const backup = await service.resolveBackupContent(service.toBackupResource(fooFile)); + assert.equal(contents, snapshotToString(backup.value.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true))); + assert.ok(!backup.meta); + }); + + test('should restore the original contents (text file with metadata and fragment URI)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'adipiscing ßß elit', + 'consectetur ' + ].join(''); + + const meta = { + etag: 'theEtag', + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await testResolveBackup(customFileWithFragment, contents, meta); + }); + + test('should restore the original contents (text file with space in name with metadata)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'adipiscing ßß elit', + 'consectetur ' + ].join(''); + + const meta = { + etag: 'theEtag', + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await testResolveBackup(fooBarFile, contents, meta); + }); + + test('should restore the original contents (text file with too large metadata to persist)', async () => { + const contents = [ + 'Lorem ipsum ', + 'dolor öäü sit amet ', + 'adipiscing ßß elit', + 'consectetur ' + ].join(''); + + const meta = { + etag: (new Array(100 * 1024)).join('Large String'), + size: 888, + mtime: Date.now(), + orphaned: false + }; + + await testResolveBackup(fooBarFile, contents, meta, null); + }); + + async function testResolveBackup(resource: URI, contents: string, meta?: IBackupTestMetaData, expectedMeta?: IBackupTestMetaData | null) { + if (typeof expectedMeta === 'undefined') { + expectedMeta = meta; + } + + await service.backupResource(resource, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false), 1, meta); + + assert.ok(await service.loadBackupResource(resource)); + + const backup = await service.resolveBackupContent(service.toBackupResource(resource)); + assert.equal(contents, snapshotToString(backup.value.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true))); + + if (expectedMeta) { + assert.equal(backup.meta!.etag, expectedMeta.etag); + assert.equal(backup.meta!.size, expectedMeta.size); + assert.equal(backup.meta!.mtime, expectedMeta.mtime); + assert.equal(backup.meta!.orphaned, expectedMeta.orphaned); + } else { + assert.ok(!backup.meta); + } + } + }); +}); + +suite('BackupFilesModel', () => { + + let service: TestBackupFileService; + + setup(async () => { + service = new TestBackupFileService(workspaceResource, backupHome, workspacesJsonPath); + + // Delete any existing backups completely and then re-create it. + await pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + await pfs.mkdirp(backupHome); + + return pfs.writeFile(workspacesJsonPath, ''); + }); + + teardown(() => { + return pfs.rimraf(backupHome, pfs.RimRafMode.MOVE); + }); + + test('simple', () => { + const model = new BackupFilesModel(service.fileService); + + const resource1 = URI.file('test.html'); + + assert.equal(model.has(resource1), false); + + model.add(resource1); + + assert.equal(model.has(resource1), true); + assert.equal(model.has(resource1, 0), true); + assert.equal(model.has(resource1, 1), false); + assert.equal(model.has(resource1, 1, { foo: 'bar' }), false); + + model.remove(resource1); + + assert.equal(model.has(resource1), false); + + model.add(resource1); + + assert.equal(model.has(resource1), true); + assert.equal(model.has(resource1, 0), true); + assert.equal(model.has(resource1, 1), false); + + model.clear(); + + assert.equal(model.has(resource1), false); + + model.add(resource1, 1); + + assert.equal(model.has(resource1), true); + assert.equal(model.has(resource1, 0), false); + assert.equal(model.has(resource1, 1), true); + + const resource2 = URI.file('test1.html'); + const resource3 = URI.file('test2.html'); + const resource4 = URI.file('test3.html'); + + model.add(resource2); + model.add(resource3); + model.add(resource4, undefined, { foo: 'bar' }); + + assert.equal(model.has(resource1), true); + assert.equal(model.has(resource2), true); + assert.equal(model.has(resource3), true); + + assert.equal(model.has(resource4), true); + assert.equal(model.has(resource4, undefined, { foo: 'bar' }), true); + assert.equal(model.has(resource4, undefined, { bar: 'foo' }), false); + }); + + test('resolve', async () => { + await pfs.mkdirp(path.dirname(fooBackupPath)); + fs.writeFileSync(fooBackupPath, 'foo'); + const model = new BackupFilesModel(service.fileService); + + const resolvedModel = await model.resolve(URI.file(workspaceBackupPath)); + assert.equal(resolvedModel.has(URI.file(fooBackupPath)), true); + }); + + test('get', () => { + const model = new BackupFilesModel(service.fileService); + + assert.deepEqual(model.get(), []); + + const file1 = URI.file('/root/file/foo.html'); + const file2 = URI.file('/root/file/bar.html'); + const untitled = URI.file('/root/untitled/bar.html'); + + model.add(file1); + model.add(file2); + model.add(untitled); + + assert.deepEqual(model.get().map(f => f.fsPath), [file1.fsPath, file2.fsPath, untitled.fsPath]); + }); +}); diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 9ce17f99298..b091f4451ff 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -133,7 +133,7 @@ export class UserConfiguration extends Disposable { async reload(): Promise { try { - const content = await this.configurationFileService.resolveContent(this.configurationResource); + const content = await this.configurationFileService.readFile(this.configurationResource); this.parser.parseContent(content); return this.parser.configurationModel; } catch (e) { @@ -263,13 +263,13 @@ export class WorkspaceConfiguration extends Disposable { this._workspaceIdentifier = workspaceIdentifier; if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) { if (this._workspaceIdentifier.configPath.scheme === Schemas.file) { - this.switch(); + this.switch(new FileServiceBasedWorkspaceConfiguration(this._configurationFileService)); } else { this.waitAndSwitch(this._workspaceIdentifier); } } - await this._workspaceConfiguration.load(this._workspaceIdentifier); this._loaded = this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration; + await this._workspaceConfiguration.load(this._workspaceIdentifier); } reload(): Promise { @@ -300,21 +300,25 @@ export class WorkspaceConfiguration extends Disposable { private async waitAndSwitch(workspaceIdentifier: IWorkspaceIdentifier): Promise { await this._configurationFileService.whenProviderRegistered(workspaceIdentifier.configPath.scheme); if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) { - this.switch(); + const fileServiceBasedWorkspaceConfiguration = this._register(new FileServiceBasedWorkspaceConfiguration(this._configurationFileService)); + await fileServiceBasedWorkspaceConfiguration.load(workspaceIdentifier); + this.switch(fileServiceBasedWorkspaceConfiguration); this._loaded = true; - this.onDidWorkspaceConfigurationChange(); + this.onDidWorkspaceConfigurationChange(false); } } - private switch(): void { + private switch(fileServiceBasedWorkspaceConfiguration: FileServiceBasedWorkspaceConfiguration): void { this._workspaceConfiguration.dispose(); this._workspaceConfigurationChangeDisposable.dispose(); - this._workspaceConfiguration = this._register(new FileServiceBasedWorkspaceConfiguration(this._configurationFileService)); - this._workspaceConfigurationChangeDisposable = this._register(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange())); + this._workspaceConfiguration = this._register(fileServiceBasedWorkspaceConfiguration); + this._workspaceConfigurationChangeDisposable = this._register(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange(true))); } - private async onDidWorkspaceConfigurationChange(): Promise { - await this.reload(); + private async onDidWorkspaceConfigurationChange(reload: boolean): Promise { + if (reload) { + await this.reload(); + } this.updateCache(); this._onDidUpdateConfiguration.fire(); } @@ -379,7 +383,7 @@ class FileServiceBasedWorkspaceConfiguration extends Disposable implements IWork } let contents = ''; try { - contents = await this.configurationFileService.resolveContent(this._workspaceIdentifier.configPath); + contents = await this.configurationFileService.readFile(this._workspaceIdentifier.configPath); } catch (error) { const exists = await this.configurationFileService.exists(this._workspaceIdentifier.configPath); if (exists) { @@ -547,7 +551,7 @@ class FileServiceBasedFolderConfiguration extends Disposable implements IFolderC async loadConfiguration(): Promise { const configurationContents = await Promise.all(this.configurationResources.map(async resource => { try { - return await this.configurationFileService.resolveContent(resource); + return await this.configurationFileService.readFile(resource); } catch (error) { const exists = await this.configurationFileService.exists(resource); if (exists) { @@ -662,7 +666,7 @@ class CachedFolderConfiguration extends Disposable implements IFolderConfigurati private readonly configurationCache: IConfigurationCache ) { super(); - this.key = createSHA1(join(folder.path, configFolderRelativePath)).then(key => ({ type: 'folder', key })); + this.key = createSHA1(join(folder.path, configFolderRelativePath)).then(key => ({ type: 'folder', key })); this.configurationModel = new ConfigurationModel(); } diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 78957e2c301..a8b346d524d 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -4,14 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import * as assert from 'vs/base/common/assert'; import { Event, Emitter } from 'vs/base/common/event'; import { ResourceMap } from 'vs/base/common/map'; import { equals, deepClone } from 'vs/base/common/objects'; import { Disposable } from 'vs/base/common/lifecycle'; import { Queue, Barrier } from 'vs/base/common/async'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; -import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent, WorkspaceFolder, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { isLinux } from 'vs/base/common/platform'; import { ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -61,6 +60,9 @@ export class WorkspaceService extends Disposable implements IConfigurationServic private configurationEditingService: ConfigurationEditingService; private jsonEditingService: JSONEditingService; + private cyclicDependencyReady: Function; + private cyclicDependency = new Promise(resolve => this.cyclicDependencyReady = resolve); + constructor( { userSettingsResource, remoteAuthority, configurationCache }: { userSettingsResource?: URI, remoteAuthority?: string, configurationCache: IConfigurationCache }, private readonly configurationFileService: IConfigurationFileService, @@ -131,8 +133,9 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } public updateFolders(foldersToAdd: IWorkspaceFolderCreationData[], foldersToRemove: URI[], index?: number): Promise { - assert.ok(this.jsonEditingService, 'Workbench is not initialized yet'); - return Promise.resolve(this.workspaceEditingQueue.queue(() => this.doUpdateFolders(foldersToAdd, foldersToRemove, index))); + return this.cyclicDependency.then(() => { + return this.workspaceEditingQueue.queue(() => this.doUpdateFolders(foldersToAdd, foldersToRemove, index)); + }); } public isInsideWorkspace(resource: URI): boolean { @@ -178,8 +181,9 @@ export class WorkspaceService extends Disposable implements IConfigurationServic if (foldersToAdd.length) { // Recompute current workspace folders if we have folders to add - const workspaceConfigFolder = dirname(this.getWorkspace().configuration!); - currentWorkspaceFolders = toWorkspaceFolders(newStoredFolders, workspaceConfigFolder); + const workspaceConfigPath = this.getWorkspace().configuration!; + const workspaceConfigFolder = dirname(workspaceConfigPath); + currentWorkspaceFolders = toWorkspaceFolders(newStoredFolders, workspaceConfigPath); const currentWorkspaceFolderUris = currentWorkspaceFolders.map(folder => folder.uri); const storedFoldersToAdd: IStoredWorkspaceFolder[] = []; @@ -214,8 +218,10 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } private setFolders(folders: IStoredWorkspaceFolder[]): Promise { - return this.workspaceConfiguration.setFolders(folders, this.jsonEditingService) - .then(() => this.onWorkspaceConfigurationChanged()); + return this.cyclicDependency.then(() => { + return this.workspaceConfiguration.setFolders(folders, this.jsonEditingService) + .then(() => this.onWorkspaceConfigurationChanged()); + }); } private contains(resources: URI[], toCheck: URI): boolean { @@ -250,11 +256,12 @@ export class WorkspaceService extends Disposable implements IConfigurationServic updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): Promise; updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget, donotNotifyError: boolean): Promise; updateValue(key: string, value: any, arg3?: any, arg4?: any, donotNotifyError?: any): Promise { - assert.ok(this.configurationEditingService, 'Workbench is not initialized yet'); - const overrides = isConfigurationOverrides(arg3) ? arg3 : undefined; - const target = this.deriveConfigurationTarget(key, value, overrides, overrides ? arg4 : arg3); - return target ? this.writeConfigurationValue(key, value, target, overrides, donotNotifyError) - : Promise.resolve(); + return this.cyclicDependency.then(() => { + const overrides = isConfigurationOverrides(arg3) ? arg3 : undefined; + const target = this.deriveConfigurationTarget(key, value, overrides, overrides ? arg4 : arg3); + return target ? this.writeConfigurationValue(key, value, target, overrides, donotNotifyError) + : Promise.resolve(); + }); } reloadConfiguration(folder?: IWorkspaceFolder, key?: string): Promise { @@ -299,6 +306,12 @@ export class WorkspaceService extends Disposable implements IConfigurationServic acquireInstantiationService(instantiationService: IInstantiationService): void { this.configurationEditingService = instantiationService.createInstance(ConfigurationEditingService); this.jsonEditingService = instantiationService.createInstance(JSONEditingService); + + if (this.cyclicDependencyReady) { + this.cyclicDependencyReady(); + } else { + this.cyclicDependency = Promise.resolve(undefined); + } } private createWorkspace(arg: IWorkspaceInitializationPayload): Promise { @@ -317,7 +330,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic return this.workspaceConfiguration.load({ id: workspaceIdentifier.id, configPath: workspaceIdentifier.configPath }) .then(() => { const workspaceConfigPath = workspaceIdentifier.configPath; - const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), dirname(workspaceConfigPath)); + const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), workspaceConfigPath); const workspaceId = workspaceIdentifier.id; const workspace = new Workspace(workspaceId, workspaceFolders, workspaceConfigPath); if (this.workspaceConfiguration.loaded) { @@ -328,16 +341,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } private createSingleFolderWorkspace(singleFolder: ISingleFolderWorkspaceInitializationPayload): Promise { - const folder = singleFolder.folder; - - let configuredFolders: IStoredWorkspaceFolder[]; - if (folder.scheme === 'file') { - configuredFolders = [{ path: folder.fsPath }]; - } else { - configuredFolders = [{ uri: folder.toString() }]; - } - - const workspace = new Workspace(singleFolder.id, toWorkspaceFolders(configuredFolders)); + const workspace = new Workspace(singleFolder.id, [toWorkspaceFolder(singleFolder.folder)]); this.releaseWorkspaceBarrier(); // Release barrier as workspace is complete because it is single folder. return Promise.resolve(workspace); } @@ -392,7 +396,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic } private compareFolders(currentFolders: IWorkspaceFolder[], newFolders: IWorkspaceFolder[]): IWorkspaceFoldersChangeEvent { - const result = { added: [], removed: [], changed: [] } as IWorkspaceFoldersChangeEvent; + const result: IWorkspaceFoldersChangeEvent = { added: [], removed: [], changed: [] }; result.added = newFolders.filter(newFolder => !currentFolders.some(currentFolder => newFolder.uri.toString() === currentFolder.uri.toString())); for (let currentIndex = 0; currentIndex < currentFolders.length; currentIndex++) { let currentFolder = currentFolders[currentIndex]; @@ -546,7 +550,7 @@ export class WorkspaceService extends Disposable implements IConfigurationServic private onWorkspaceConfigurationChanged(): Promise { if (this.workspace && this.workspace.configuration && this._configuration) { const workspaceConfigurationChangeEvent = this._configuration.compareAndUpdateWorkspaceConfiguration(this.workspaceConfiguration.getConfiguration()); - let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), dirname(this.workspace.configuration)); + let configuredFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), this.workspace.configuration); const changes = this.compareFolders(this.workspace.folders, configuredFolders); if (changes.added.length || changes.removed.length || changes.changed.length) { this.workspace.folders = configuredFolders; diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index c8ec23ec441..02192c4c871 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -49,7 +49,7 @@ export interface IConfigurationFileService { whenProviderRegistered(scheme: string): Promise; watch(resource: URI): IDisposable; exists(resource: URI): Promise; - resolveContent(resource: URI): Promise; + readFile(resource: URI): Promise; } export class ConfigurationFileService implements IConfigurationFileService { @@ -82,8 +82,8 @@ export class ConfigurationFileService implements IConfigurationFileService { return this.fileService.exists(resource); } - resolveContent(resource: URI): Promise { - return this.fileService.resolveContent(resource, { encoding: 'utf8' }).then(content => content.value); + readFile(resource: URI): Promise { + return this.fileService.readFile(resource).then(content => content.value.toString()); } } diff --git a/src/vs/workbench/services/configuration/node/configurationFileService.ts b/src/vs/workbench/services/configuration/node/configurationFileService.ts index 77bcc9cdbbe..18690c44040 100644 --- a/src/vs/workbench/services/configuration/node/configurationFileService.ts +++ b/src/vs/workbench/services/configuration/node/configurationFileService.ts @@ -51,9 +51,9 @@ export class ConfigurationFileService extends Disposable implements IConfigurati return this._fileServiceBasedConfigurationFileService ? this._fileServiceBasedConfigurationFileService.exists(resource) : pfs.exists(resource.fsPath); } - async resolveContent(resource: URI): Promise { + async readFile(resource: URI): Promise { if (this._fileServiceBasedConfigurationFileService) { - return this._fileServiceBasedConfigurationFileService.resolveContent(resource); + return this._fileServiceBasedConfigurationFileService.readFile(resource); } else { const contents = await pfs.readFile(resource.fsPath); return contents.toString(); diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index d5bd26501f7..b3c90ce98d9 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -14,11 +14,10 @@ import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/ import { parseArgs } from 'vs/platform/environment/node/argv'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { TestTextFileService, TestTextResourceConfigurationService, workbenchInstantiationService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; +import { TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; -import { LegacyFileService } from 'vs/workbench/services/files/node/fileService'; import { ConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService'; import { WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -34,10 +33,10 @@ import { URI } from 'vs/base/common/uri'; import { createHash } from 'crypto'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { FileService } from 'vs/workbench/services/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; -import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; import { IFileService } from 'vs/platform/files/common/files'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService'; @@ -110,14 +109,8 @@ suite('ConfigurationEditingService', () => { instantiationService.stub(IWorkspaceContextService, workspaceService); return workspaceService.initialize(noWorkspace ? { id: '' } : { folder: URI.file(workspaceDir), id: createHash('md5').update(URI.file(workspaceDir).toString()).digest('hex') }).then(() => { instantiationService.stub(IConfigurationService, workspaceService); - const fileService = new FileService2(new NullLogService()); + const fileService = new FileService(new NullLogService()); fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.setLegacyService(new LegacyFileService( - fileService, - workspaceService, - TestEnvironmentService, - new TestTextResourceConfigurationService(), - )); instantiationService.stub(IFileService, fileService); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 0c3d0a6910d..e35f5b0de41 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -22,8 +22,7 @@ import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configurati import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; import { ConfigurationTarget, IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { workbenchInstantiationService, TestTextResourceConfigurationService, TestTextFileService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; -import { LegacyFileService } from 'vs/workbench/services/files/node/fileService'; +import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -38,9 +37,9 @@ import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-browser/remoteAuthorityResolverService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { FileService } from 'vs/workbench/services/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; -import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; import { ConfigurationCache } from 'vs/workbench/services/configuration/node/configurationCache'; import { ConfigurationFileService } from 'vs/workbench/services/configuration/node/configurationFileService'; @@ -219,9 +218,10 @@ suite('WorkspaceContextService - Workspace Editing', () => { const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, path.join(parentDir, 'settings.json')); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); - const fileService = new FileService2(new NullLogService()); + const fileService = new FileService(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); const configurationFileService = new ConfigurationFileService(); - fileService.whenReady.then(() => configurationFileService.fileService = fileService); + configurationFileService.fileService = fileService; const workspaceService = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), configurationCache: new ConfigurationCache(environmentService) }, configurationFileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -229,13 +229,6 @@ suite('WorkspaceContextService - Workspace Editing', () => { instantiationService.stub(IEnvironmentService, environmentService); return workspaceService.initialize(getWorkspaceIdentifier(configPath)).then(() => { - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.setLegacyService(new LegacyFileService( - fileService, - workspaceService, - TestEnvironmentService, - new TestTextResourceConfigurationService() - )); instantiationService.stub(IFileService, fileService); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); @@ -486,22 +479,16 @@ suite('WorkspaceService - Initialization', () => { const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); - const fileService = new FileService2(new NullLogService()); + const fileService = new FileService(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); const configurationFileService = new ConfigurationFileService(); - fileService.whenReady.then(() => configurationFileService.fileService = fileService); + configurationFileService.fileService = fileService; const workspaceService = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), configurationCache: new ConfigurationCache(environmentService) }, configurationFileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); instantiationService.stub(IEnvironmentService, environmentService); return workspaceService.initialize({ id: '' }).then(() => { - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.setLegacyService(new LegacyFileService( - fileService, - workspaceService, - TestEnvironmentService, - new TestTextResourceConfigurationService() - )); instantiationService.stub(IFileService, fileService); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); @@ -756,22 +743,16 @@ suite('WorkspaceConfigurationService - Folder', () => { const environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, globalSettingsFile); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); - const fileService = new FileService2(new NullLogService()); + const fileService = new FileService(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); const configurationFileService = new ConfigurationFileService(); - fileService.whenReady.then(() => configurationFileService.fileService = fileService); + configurationFileService.fileService = fileService; const workspaceService = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), configurationCache: new ConfigurationCache(environmentService) }, configurationFileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); instantiationService.stub(IConfigurationService, workspaceService); instantiationService.stub(IEnvironmentService, environmentService); return workspaceService.initialize(convertToWorkspacePayload(URI.file(folderDir))).then(() => { - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.setLegacyService(new LegacyFileService( - fileService, - workspaceService, - TestEnvironmentService, - new TestTextResourceConfigurationService() - )); instantiationService.stub(IFileService, fileService); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); @@ -1090,9 +1071,10 @@ suite('WorkspaceConfigurationService-Multiroot', () => { environmentService = new SettingsTestEnvironmentService(parseArgs(process.argv), process.execPath, path.join(parentDir, 'settings.json')); const remoteAgentService = instantiationService.createInstance(RemoteAgentService, {}); instantiationService.stub(IRemoteAgentService, remoteAgentService); - const fileService = new FileService2(new NullLogService()); + const fileService = new FileService(new NullLogService()); + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); const configurationFileService = new ConfigurationFileService(); - fileService.whenReady.then(() => configurationFileService.fileService = fileService); + configurationFileService.fileService = fileService; const workspaceService = new WorkspaceService({ userSettingsResource: URI.file(environmentService.appSettingsPath), configurationCache: new ConfigurationCache(environmentService) }, configurationFileService, remoteAgentService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -1100,13 +1082,6 @@ suite('WorkspaceConfigurationService-Multiroot', () => { instantiationService.stub(IEnvironmentService, environmentService); return workspaceService.initialize(getWorkspaceIdentifier(configPath)).then(() => { - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.setLegacyService(new LegacyFileService( - fileService, - workspaceService, - TestEnvironmentService, - new TestTextResourceConfigurationService() - )); instantiationService.stub(IFileService, fileService); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); diff --git a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts index f4dae2a33c1..ec0319b4d8a 100644 --- a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts @@ -97,7 +97,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService x = elementPosition.left; y = elementPosition.top + elementPosition.height; } else { - const pos = <{ x: number; y: number; }>anchor; + const pos: { x: number; y: number; } = anchor; x = pos.x + 1; /* prevent first item from being selected automatically under mouse */ y = pos.y; } diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index 4d50a0b8ff0..9ff5db4854a 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -85,8 +85,8 @@ export class FileDialogService implements IFileDialogService { } private shouldUseSimplified(schema: string): boolean { - const setting = this.configurationService.getValue('workbench.dialogs.useSimplified'); - return (schema !== Schemas.file) || ((setting === 'true') || (setting === true)); + const setting = this.configurationService.getValue('files.simpleDialog.enable'); + return (schema !== Schemas.file) || (setting === true); } private ensureFileSchema(schema: string): string[] { diff --git a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts index 8b838112e23..1aa23393c30 100644 --- a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts @@ -9,7 +9,7 @@ import * as objects from 'vs/base/common/objects'; import { IFileService, IFileStat, FileKind } from 'vs/platform/files/common/files'; import { IQuickInputService, IQuickPickItem, IQuickPick } from 'vs/platform/quickinput/common/quickInput'; import { URI } from 'vs/base/common/uri'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, OperatingSystem } from 'vs/base/common/platform'; import { ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -22,10 +22,12 @@ import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { RemoteFileDialogContext } from 'vs/workbench/common/contextkeys'; -import { equalsIgnoreCase, format } from 'vs/base/common/strings'; +import { equalsIgnoreCase, format, startsWithIgnoreCase } from 'vs/base/common/strings'; import { OpenLocalFileAction, OpenLocalFileFolderAction, OpenLocalFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { isValidBasename } from 'vs/base/common/extpath'; +import { RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; interface FileQuickPickItem extends IQuickPickItem { uri: URI; @@ -34,14 +36,11 @@ interface FileQuickPickItem extends IQuickPickItem { enum UpdateResult { Updated, + Updating, NotUpdated, InvalidPath } -// Reference: https://en.wikipedia.org/wiki/Filename -const INVALID_FILE_CHARS = isWindows ? /[\\/:\*\?"<>\|]/g : /[\\/]/g; -const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])$/i; - export class RemoteFileDialog { private options: IOpenDialogOptions; private currentFolder: URI; @@ -51,14 +50,16 @@ export class RemoteFileDialog { private allowFolderSelection: boolean; private remoteAuthority: string | undefined; private requiresTrailing: boolean; + private trailing: string | undefined; private scheme: string = REMOTE_HOST_SCHEME; - private shouldOverwriteFile: boolean = false; private contextKey: IContextKey; private userEnteredPathSegment: string; private autoCompletePathSegment: string; private activeItem: FileQuickPickItem; private userHome: URI; private badPath: string | undefined; + private remoteAgentEnvironment: IRemoteAgentEnvironment | null; + private separator: string; constructor( @IFileService private readonly fileService: IFileService, @@ -135,9 +136,16 @@ export class RemoteFileDialog { return defaultUri ? defaultUri.scheme : (available ? available[0] : Schemas.file); } + private async getRemoteAgentEnvironment(): Promise { + if (this.remoteAgentEnvironment === undefined) { + this.remoteAgentEnvironment = await this.remoteAgentService.getEnvironment(); + } + return this.remoteAgentEnvironment; + } + private async getUserHome(): Promise { if (this.scheme !== Schemas.file) { - const env = await this.remoteAgentService.getEnvironment(); + const env = await this.getRemoteAgentEnvironment(); if (env) { return env.userHome; } @@ -148,9 +156,9 @@ export class RemoteFileDialog { private async pickResource(isSave: boolean = false): Promise { this.allowFolderSelection = !!this.options.canSelectFolders; this.allowFileSelection = !!this.options.canSelectFiles; + this.separator = this.labelService.getSeparator(this.scheme, this.remoteAuthority); this.hidden = false; let homedir: URI = this.options.defaultUri ? this.options.defaultUri : this.workspaceContextService.getWorkspace().folders[0].uri; - let trailing: string | undefined; let stat: IFileStat | undefined; let ext: string = resources.extname(homedir); if (this.options.defaultUri) { @@ -161,14 +169,14 @@ export class RemoteFileDialog { } if (!stat || !stat.isDirectory) { homedir = resources.dirname(this.options.defaultUri); - trailing = resources.basename(this.options.defaultUri); + this.trailing = resources.basename(this.options.defaultUri); } // append extension if (isSave && !ext && this.options.filters) { for (let i = 0; i < this.options.filters.length; i++) { if (this.options.filters[i].extensions[0] !== '*') { ext = '.' + this.options.filters[i].extensions[0]; - trailing = trailing ? trailing + ext : ext; + this.trailing = this.trailing ? this.trailing + ext : ext; break; } } @@ -177,8 +185,10 @@ export class RemoteFileDialog { return new Promise(async (resolve) => { this.filePickBox = this.quickInputService.createQuickPick(); + this.filePickBox.busy = true; this.filePickBox.matchOnLabel = false; this.filePickBox.autoFocusOnList = false; + this.filePickBox.ignoreFocusOut = true; this.filePickBox.ok = true; if (this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) { this.filePickBox.customButton = true; @@ -221,6 +231,7 @@ export class RemoteFileDialog { this.options.availableFileSystems.shift(); } this.options.defaultUri = undefined; + this.filePickBox.hide(); if (this.requiresTrailing) { return this.fileDialogService.showSaveDialog(this.options).then(result => { doResolve(this, result); @@ -255,28 +266,32 @@ export class RemoteFileDialog { isAcceptHandled = false; // update input box to match the first selected item if ((i.length === 1) && this.isChangeFromUser()) { + this.filePickBox.validationMessage = undefined; this.setAutoComplete(this.constructFullUserPath(), this.userEnteredPathSegment, i[0], true); } }); this.filePickBox.onDidChangeValue(async value => { - // onDidChangeValue can also be triggered by the auto complete, so if it looks like the auto complete, don't do anything - if (this.isChangeFromUser()) { - // If the user has just entered more bad path, don't change anything - if (value !== this.constructFullUserPath() && !this.isBadSubpath(value)) { - this.filePickBox.validationMessage = undefined; - this.shouldOverwriteFile = false; - const valueUri = this.remoteUriFrom(this.trimTrailingSlash(this.filePickBox.value)); - let updated: UpdateResult = UpdateResult.NotUpdated; - if (!resources.isEqual(this.remoteUriFrom(this.trimTrailingSlash(this.pathFromUri(this.currentFolder))), valueUri, true)) { - updated = await this.tryUpdateItems(value, this.remoteUriFrom(this.filePickBox.value)); + try { + // onDidChangeValue can also be triggered by the auto complete, so if it looks like the auto complete, don't do anything + if (this.isChangeFromUser()) { + // If the user has just entered more bad path, don't change anything + if (!equalsIgnoreCase(value, this.constructFullUserPath()) && !this.isBadSubpath(value)) { + this.filePickBox.validationMessage = undefined; + const filePickBoxUri = this.filePickBoxValue(); + let updated: UpdateResult = UpdateResult.NotUpdated; + if (!resources.isEqual(this.currentFolder, filePickBoxUri, true)) { + updated = await this.tryUpdateItems(value, filePickBoxUri); + } + if (updated === UpdateResult.NotUpdated) { + this.setActiveItems(value); + } + } else { + this.filePickBox.activeItems = []; } - if (updated === UpdateResult.NotUpdated) { - this.setActiveItems(value); - } - } else { - this.filePickBox.activeItems = []; } + } catch { + // Since any text can be entered in the input box, there is potential for error causing input. If this happens, do nothing. } }); this.filePickBox.onDidHide(() => { @@ -288,12 +303,13 @@ export class RemoteFileDialog { this.filePickBox.show(); this.contextKey.set(true); - await this.updateItems(homedir, trailing); - if (trailing) { - this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - ext.length]; + await this.updateItems(homedir, true, this.trailing); + if (this.trailing) { + this.filePickBox.valueSelection = [this.filePickBox.value.length - this.trailing.length, this.filePickBox.value.length - ext.length]; } else { this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length]; } + this.filePickBox.busy = false; }); } @@ -302,7 +318,7 @@ export class RemoteFileDialog { } private isChangeFromUser(): boolean { - if ((this.filePickBox.value === this.pathAppend(this.currentFolder, this.userEnteredPathSegment + this.autoCompletePathSegment)) + if (equalsIgnoreCase(this.filePickBox.value, this.pathAppend(this.currentFolder, this.userEnteredPathSegment + this.autoCompletePathSegment)) && (this.activeItem === (this.filePickBox.activeItems ? this.filePickBox.activeItems[0] : undefined))) { return false; } @@ -313,61 +329,81 @@ export class RemoteFileDialog { return this.pathAppend(this.currentFolder, this.userEnteredPathSegment); } + private filePickBoxValue(): URI { + // The file pick box can't render everything, so we use the current folder to create the uri so that it is an existing path. + const directUri = this.remoteUriFrom(this.filePickBox.value); + const currentPath = this.pathFromUri(this.currentFolder); + if (equalsIgnoreCase(this.filePickBox.value, currentPath)) { + return this.currentFolder; + } + const currentDisplayUri = this.remoteUriFrom(currentPath); + const relativePath = resources.relativePath(currentDisplayUri, directUri); + const isSameRoot = (this.filePickBox.value.length > 1 && currentPath.length > 1) ? equalsIgnoreCase(this.filePickBox.value.substr(0, 2), currentPath.substr(0, 2)) : false; + if (relativePath && isSameRoot) { + return resources.joinPath(this.currentFolder, relativePath); + } else { + return directUri; + } + } + private async onDidAccept(): Promise { - let resolveValue: URI | undefined; - let navigateValue: URI | undefined; - const trimmedPickBoxValue = ((this.filePickBox.value.length > 1) && this.endsWithSlash(this.filePickBox.value)) ? this.filePickBox.value.substr(0, this.filePickBox.value.length - 1) : this.filePickBox.value; - const inputUri = this.remoteUriFrom(trimmedPickBoxValue); - const inputUriDirname = resources.dirname(inputUri); - let stat: IFileStat | undefined; - let statDirname: IFileStat | undefined; - try { - statDirname = await this.fileService.resolve(inputUriDirname); - stat = await this.fileService.resolve(inputUri); - } catch (e) { - // do nothing + this.filePickBox.busy = true; + if (this.filePickBox.activeItems.length === 1) { + const item = this.filePickBox.selectedItems[0]; + if (item.isFolder) { + if (this.trailing) { + await this.updateItems(item.uri, true, this.trailing); + } else { + // When possible, cause the update to happen by modifying the input box. + // This allows all input box updates to happen first, and uses the same code path as the user typing. + const newPath = this.pathFromUri(item.uri); + if (startsWithIgnoreCase(newPath, this.filePickBox.value)) { + const insertValue = newPath.substring(this.filePickBox.value.length, newPath.length); + this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length]; + this.insertText(newPath, insertValue); + } else if ((item.label === '..') && startsWithIgnoreCase(this.filePickBox.value, newPath)) { + this.filePickBox.valueSelection = [newPath.length, this.filePickBox.value.length]; + this.insertText(newPath, ''); + } else { + await this.updateItems(item.uri, true); + } + } + return; + } + } else { + // If the items have updated, don't try to resolve + if ((await this.tryUpdateItems(this.filePickBox.value, this.filePickBoxValue())) !== UpdateResult.NotUpdated) { + return; + } } + let resolveValue: URI | undefined; // Find resolve value if (this.filePickBox.activeItems.length === 0) { - if (!this.requiresTrailing && resources.isEqual(this.currentFolder, inputUri, true)) { - resolveValue = inputUri; - } else if (statDirname && statDirname.isDirectory) { - resolveValue = inputUri; - } else if (stat && stat.isDirectory) { - navigateValue = inputUri; - } + resolveValue = this.filePickBoxValue(); } else if (this.filePickBox.activeItems.length === 1) { - const item = this.filePickBox.selectedItems[0]; - if (item) { - if (!item.isFolder) { - resolveValue = item.uri; - } else { - navigateValue = item.uri; - } - } + resolveValue = this.filePickBox.selectedItems[0].uri; } - if (resolveValue) { resolveValue = this.addPostfix(resolveValue); - if (await this.validate(resolveValue)) { - return Promise.resolve(resolveValue); - } - } else if (navigateValue) { - // Try to navigate into the folder - await this.updateItems(navigateValue); - } else { - // validation error. Path does not exist. } - return Promise.resolve(undefined); + if (await this.validate(resolveValue)) { + this.filePickBox.busy = false; + return resolveValue; + } + this.filePickBox.busy = false; + return undefined; } private async tryUpdateItems(value: string, valueUri: URI): Promise { - if (value[value.length - 1] === '~') { - await this.updateItems(this.userHome); - this.badPath = undefined; + if ((value.length > 0) && ((value[value.length - 1] === '~') || (value[0] === '~'))) { + let newDir = this.userHome; + if ((value[0] === '~') && (value.length > 1)) { + newDir = resources.joinPath(newDir, value.substring(1)); + } + await this.updateItems(newDir, true); return UpdateResult.Updated; - } else if (this.endsWithSlash(value) || (!resources.isEqual(this.currentFolder, resources.dirname(valueUri), true) && resources.isEqualOrParent(this.currentFolder, resources.dirname(valueUri), true))) { + } else if (!resources.isEqual(this.currentFolder, valueUri, true) && (this.endsWithSlash(value) || (!resources.isEqual(this.currentFolder, resources.dirname(valueUri), true) && resources.isEqualOrParent(this.currentFolder, resources.dirname(valueUri), true)))) { let stat: IFileStat | undefined; try { stat = await this.fileService.resolve(valueUri); @@ -386,7 +422,7 @@ export class RemoteFileDialog { return UpdateResult.InvalidPath; } else { const inputUriDirname = resources.dirname(valueUri); - if (!resources.isEqual(this.remoteUriFrom(this.trimTrailingSlash(this.pathFromUri(this.currentFolder))), inputUriDirname, true)) { + if (!resources.isEqual(resources.removeTrailingPathSeparator(this.currentFolder), inputUriDirname, true)) { let statWithoutTrailing: IFileStat | undefined; try { statWithoutTrailing = await this.fileService.resolve(inputUriDirname); @@ -394,7 +430,7 @@ export class RemoteFileDialog { // do nothing } if (statWithoutTrailing && statWithoutTrailing.isDirectory && (resources.basename(valueUri) !== '.')) { - await this.updateItems(inputUriDirname, resources.basename(valueUri)); + await this.updateItems(inputUriDirname, false, resources.basename(valueUri)); this.badPath = undefined; return UpdateResult.Updated; } @@ -409,7 +445,7 @@ export class RemoteFileDialog { const inputBasename = resources.basename(this.remoteUriFrom(value)); // Make sure that the folder whose children we are currently viewing matches the path in the input const userPath = this.constructFullUserPath(); - if (userPath === value.substring(0, userPath.length)) { + if (equalsIgnoreCase(userPath, value.substring(0, userPath.length))) { let hasMatch = false; for (let i = 0; i < this.filePickBox.items.length; i++) { const item = this.filePickBox.items[i]; @@ -424,7 +460,7 @@ export class RemoteFileDialog { this.filePickBox.activeItems = []; } } else { - if (inputBasename !== resources.basename(this.currentFolder)) { + if (!equalsIgnoreCase(inputBasename, resources.basename(this.currentFolder))) { this.userEnteredPathSegment = inputBasename; } else { this.userEnteredPathSegment = ''; @@ -440,26 +476,36 @@ export class RemoteFileDialog { this.autoCompletePathSegment = ''; return false; } - const itemBasename = quickPickItem.label; + const itemBasename = this.trimTrailingSlash(quickPickItem.label); // Either force the autocomplete, or the old value should be one smaller than the new value and match the new value. - if (!force && (itemBasename.length >= startingBasename.length) && equalsIgnoreCase(itemBasename.substr(0, startingBasename.length), startingBasename)) { + if (itemBasename === '..') { + // Don't match on the up directory item ever. + this.userEnteredPathSegment = startingValue; + this.autoCompletePathSegment = ''; + this.activeItem = quickPickItem; + if (force) { + // clear any selected text + this.insertText(this.userEnteredPathSegment, ''); + } + return false; + } else if (!force && (itemBasename.length >= startingBasename.length) && equalsIgnoreCase(itemBasename.substr(0, startingBasename.length), startingBasename)) { this.userEnteredPathSegment = startingBasename; this.activeItem = quickPickItem; // Changing the active items will trigger the onDidActiveItemsChanged. Clear the autocomplete first, then set it after. this.autoCompletePathSegment = ''; this.filePickBox.activeItems = [quickPickItem]; - this.autoCompletePathSegment = itemBasename.substr(startingBasename.length); + this.autoCompletePathSegment = this.trimTrailingSlash(itemBasename.substr(startingBasename.length)); this.insertText(startingValue + this.autoCompletePathSegment, this.autoCompletePathSegment); this.filePickBox.valueSelection = [startingValue.length, this.filePickBox.value.length]; return true; - } else if (force && (quickPickItem.label !== (this.userEnteredPathSegment + this.autoCompletePathSegment))) { + } else if (force && (!equalsIgnoreCase(quickPickItem.label, (this.userEnteredPathSegment + this.autoCompletePathSegment)))) { this.userEnteredPathSegment = ''; - this.autoCompletePathSegment = itemBasename; + this.autoCompletePathSegment = this.trimTrailingSlash(itemBasename); this.activeItem = quickPickItem; this.filePickBox.valueSelection = [this.pathFromUri(this.currentFolder, true).length, this.filePickBox.value.length]; // use insert text to preserve undo buffer - this.insertText(this.pathAppend(this.currentFolder, itemBasename), itemBasename); - this.filePickBox.valueSelection = [this.filePickBox.value.length - itemBasename.length, this.filePickBox.value.length]; + this.insertText(this.pathAppend(this.currentFolder, this.autoCompletePathSegment), this.autoCompletePathSegment); + this.filePickBox.valueSelection = [this.filePickBox.value.length - this.autoCompletePathSegment.length, this.filePickBox.value.length]; return true; } else { this.userEnteredPathSegment = startingBasename; @@ -482,18 +528,16 @@ export class RemoteFileDialog { // Make sure that the suffix is added. If the user deleted it, we automatically add it here let hasExt: boolean = false; const currentExt = resources.extname(uri).substr(1); - if (currentExt !== '') { - for (let i = 0; i < this.options.filters.length; i++) { - for (let j = 0; j < this.options.filters[i].extensions.length; j++) { - if ((this.options.filters[i].extensions[j] === '*') || (this.options.filters[i].extensions[j] === currentExt)) { - hasExt = true; - break; - } - } - if (hasExt) { + for (let i = 0; i < this.options.filters.length; i++) { + for (let j = 0; j < this.options.filters[i].extensions.length; j++) { + if ((this.options.filters[i].extensions[j] === '*') || (this.options.filters[i].extensions[j] === currentExt)) { + hasExt = true; break; } } + if (hasExt) { + break; + } } if (!hasExt) { result = resources.joinPath(resources.dirname(uri), resources.basename(uri) + '.' + this.options.filters[0].extensions[0]); @@ -506,21 +550,24 @@ export class RemoteFileDialog { return ((path.length > 1) && this.endsWithSlash(path)) ? path.substr(0, path.length - 1) : path; } - private yesNoPrompt(message: string): Promise { + private yesNoPrompt(uri: URI, message: string): Promise { interface YesNoItem extends IQuickPickItem { value: boolean; } const prompt = this.quickInputService.createQuickPick(); - const no = nls.localize('remoteFileDialog.no', 'No'); - prompt.items = [{ label: no, value: false }, { label: nls.localize('remoteFileDialog.yes', 'Yes'), value: true }]; prompt.title = message; - prompt.placeholder = no; + prompt.ignoreFocusOut = true; + prompt.ok = true; + prompt.customButton = true; + prompt.customLabel = nls.localize('remoteFileDialog.cancel', 'Cancel'); + prompt.value = this.pathFromUri(uri); + let isResolving = false; return new Promise(resolve => { prompt.onDidAccept(() => { isResolving = true; prompt.hide(); - resolve(prompt.selectedItems ? prompt.selectedItems[0].value : false); + resolve(true); }); prompt.onDidHide(() => { if (!isResolving) { @@ -531,11 +578,22 @@ export class RemoteFileDialog { this.filePickBox.items = this.filePickBox.items; prompt.dispose(); }); + prompt.onDidChangeValue(() => { + prompt.hide(); + }); + prompt.onDidCustom(() => { + prompt.hide(); + }); prompt.show(); }); } - private async validate(uri: URI): Promise { + private async validate(uri: URI | undefined): Promise { + if (uri === undefined) { + this.filePickBox.validationMessage = nls.localize('remoteFileDialog.invalidPath', 'Please enter a valid path.'); + return Promise.resolve(false); + } + let stat: IFileStat | undefined; let statDirname: IFileStat | undefined; try { @@ -550,13 +608,12 @@ export class RemoteFileDialog { // Can't do this this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateFolder', 'The folder already exists. Please use a new file name.'); return Promise.resolve(false); - } else if (stat && !this.shouldOverwriteFile) { + } else if (stat) { // Replacing a file. - this.shouldOverwriteFile = true; // Show a yes/no prompt const message = nls.localize('remoteFileDialog.validateExisting', '{0} already exists. Are you sure you want to overwrite it?', resources.basename(uri)); - return this.yesNoPrompt(message); - } else if (!this.isValidBaseName(resources.basename(uri))) { + return this.yesNoPrompt(uri, message); + } else if (!(isValidBasename(resources.basename(uri), await this.isWindowsOS()))) { // Filename not allowed this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateBadFilename', 'Please enter a valid file name.'); return Promise.resolve(false); @@ -583,36 +640,42 @@ export class RemoteFileDialog { return Promise.resolve(true); } - private async updateItems(newFolder: URI, trailing?: string) { + private async updateItems(newFolder: URI, force: boolean = false, trailing?: string) { this.filePickBox.busy = true; this.userEnteredPathSegment = trailing ? trailing : ''; this.autoCompletePathSegment = ''; const newValue = trailing ? this.pathFromUri(resources.joinPath(newFolder, trailing)) : this.pathFromUri(newFolder, true); - this.currentFolder = this.remoteUriFrom(this.pathFromUri(newFolder, true)); + this.currentFolder = resources.addTrailingPathSeparator(newFolder, this.separator); return this.createItems(this.currentFolder).then(items => { this.filePickBox.items = items; if (this.allowFolderSelection) { this.filePickBox.activeItems = []; } - if (!equalsIgnoreCase(this.filePickBox.value, newValue)) { + // the user might have continued typing while we were updating. Only update the input box if it doesn't match the directory. + if (!equalsIgnoreCase(this.filePickBox.value, newValue) && force) { this.filePickBox.valueSelection = [0, this.filePickBox.value.length]; this.insertText(newValue, newValue); } - this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length]; + if (force && trailing) { + // Keep the cursor position in front of the save as name. + this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - trailing.length]; + } else if (!trailing) { + // If there is trailing, we don't move the cursor. If there is no trailing, cursor goes at the end. + this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length]; + } this.filePickBox.busy = false; }); } private pathFromUri(uri: URI, endWithSeparator: boolean = false): string { - const sep = this.labelService.getSeparator(uri.scheme, uri.authority); - let result: string; - if (sep === '/') { - result = uri.fsPath.replace(/\\/g, sep); + let result: string = uri.fsPath.replace(/\n/g, ''); + if (this.separator === '/') { + result = result.replace(/\\/g, this.separator); } else { - result = uri.fsPath.replace(/\//g, sep); + result = result.replace(/\//g, this.separator); } if (endWithSeparator && !this.endsWithSlash(result)) { - result = result + sep; + result = result + this.separator; } return result; } @@ -620,39 +683,19 @@ export class RemoteFileDialog { private pathAppend(uri: URI, additional: string): string { if ((additional === '..') || (additional === '.')) { const basePath = this.pathFromUri(uri); - return basePath + (this.endsWithSlash(basePath) ? '' : this.labelService.getSeparator(uri.scheme, uri.authority)) + additional; + return basePath + (this.endsWithSlash(basePath) ? '' : this.separator) + additional; } else { return this.pathFromUri(resources.joinPath(uri, additional)); } } - private isValidBaseName(name: string): boolean { - if (!name || name.length === 0 || /^\s+$/.test(name)) { - return false; // require a name that is not just whitespace + private async isWindowsOS(): Promise { + let isWindowsOS = isWindows; + const env = await this.getRemoteAgentEnvironment(); + if (env) { + isWindowsOS = env.os === OperatingSystem.Windows; } - - INVALID_FILE_CHARS.lastIndex = 0; // the holy grail of software development - if (INVALID_FILE_CHARS.test(name)) { - return false; // check for certain invalid file characters - } - - if (isWindows && WINDOWS_FORBIDDEN_NAMES.test(name)) { - return false; // check for certain invalid file names - } - - if (name === '.' || name === '..') { - return false; // check for reserved values - } - - if (isWindows && name[name.length - 1] === '.') { - return false; // Windows: file cannot end with a "." - } - - if (isWindows && name.length !== name.trim().length) { - return false; // Windows: file cannot end with a whitespace - } - - return true; + return isWindowsOS; } private endsWithSlash(s: string) { @@ -668,7 +711,7 @@ export class RemoteFileDialog { private createBackItem(currFolder: URI): FileQuickPickItem | null { const parentFolder = resources.dirname(currFolder)!; if (!resources.isEqual(currFolder, parentFolder, true)) { - return { label: '..', uri: resources.dirname(currFolder), isFolder: true }; + return { label: '..', uri: resources.addTrailingPathSeparator(parentFolder, this.separator), isFolder: true }; } return null; } @@ -726,6 +769,7 @@ export class RemoteFileDialog { const stat = await this.fileService.resolve(fullPath); if (stat.isDirectory) { filename = this.basenameWithTrailingSlash(fullPath); + fullPath = resources.addTrailingPathSeparator(fullPath, this.separator); return { label: filename, uri: fullPath, isFolder: true, iconClasses: getIconClasses(this.modelService, this.modeService, fullPath || undefined, FileKind.FOLDER) }; } else if (!stat.isDirectory && this.allowFileSelection && this.filterFile(fullPath)) { return { label: filename, uri: fullPath, isFolder: false, iconClasses: getIconClasses(this.modelService, this.modeService, fullPath || undefined) }; diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index f68a22dc4e3..9b93b9b3975 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -527,13 +527,8 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Untitled file support const untitledInput = input; - if (!untitledInput.resource || typeof untitledInput.filePath === 'string' || (untitledInput.resource instanceof URI && untitledInput.resource.scheme === Schemas.untitled)) { - return this.untitledEditorService.createOrGet( - untitledInput.filePath ? URI.file(untitledInput.filePath) : untitledInput.resource, - untitledInput.language, - untitledInput.contents, - untitledInput.encoding - ); + if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) { + return this.untitledEditorService.createOrGet(untitledInput.resource, untitledInput.mode, untitledInput.contents, untitledInput.encoding); } // Resource Editor Support @@ -544,13 +539,13 @@ export class EditorService extends Disposable implements EditorServiceImpl { label = basename(resourceInput.resource); // derive the label from the path (but not for data URIs) } - return this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.forceFile) as EditorInput; + return this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.mode, resourceInput.forceFile) as EditorInput; } throw new Error('Unknown input type'); } - private createOrGet(resource: URI, instantiationService: IInstantiationService, label: string | undefined, description: string | undefined, encoding: string | undefined, forceFile: boolean | undefined): ICachedEditorInput { + private createOrGet(resource: URI, instantiationService: IInstantiationService, label: string | undefined, description: string | undefined, encoding: string | undefined, mode: string | undefined, forceFile: boolean | undefined): ICachedEditorInput { if (EditorService.CACHE.has(resource)) { const input = EditorService.CACHE.get(resource)!; if (input instanceof ResourceEditorInput) { @@ -561,10 +556,18 @@ export class EditorService extends Disposable implements EditorServiceImpl { if (description) { input.setDescription(description); } + + if (mode) { + input.setPreferredMode(mode); + } } else if (!(input instanceof DataUriEditorInput)) { if (encoding) { input.setPreferredEncoding(encoding); } + + if (mode) { + input.setPreferredMode(mode); + } } return input; @@ -574,7 +577,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // File if (forceFile /* fix for https://github.com/Microsoft/vscode/issues/48275 */ || this.fileService.canHandleResource(resource)) { - input = this.fileInputFactory.createFileInput(resource, encoding, instantiationService); + input = this.fileInputFactory.createFileInput(resource, encoding, mode, instantiationService); } // Data URI @@ -584,13 +587,12 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Resource else { - input = instantiationService.createInstance(ResourceEditorInput, label, description, resource); + input = instantiationService.createInstance(ResourceEditorInput, label, description, resource, mode); } + // Add to cache and remove when input gets disposed EditorService.CACHE.set(resource, input); - Event.once(input.onDispose)(() => { - EditorService.CACHE.delete(resource); - }); + Event.once(input.onDispose)(() => EditorService.CACHE.delete(resource)); return input; } diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index 0fe83803a0c..28720257840 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -24,10 +24,10 @@ export class TestEditorControl extends BaseEditor { constructor(@ITelemetryService telemetryService: ITelemetryService) { super('MyFileEditorForEditorGroupService', NullTelemetryService, new TestThemeService(), new TestStorageService()); } - setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Promise { + async setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Promise { super.setInput(input, options, token); - return input.resolve().then(() => undefined); + await input.resolve(); } getId(): string { return 'MyFileEditorForEditorGroupService'; } @@ -45,11 +45,13 @@ export class TestEditorInput extends EditorInput implements IFileEditorInput { setEncoding(encoding: string) { } getEncoding(): string { return null!; } setPreferredEncoding(encoding: string) { } + setMode(mode: string) { } + setPreferredMode(mode: string) { } getResource(): URI { return this.resource; } setForceOpenAsBinary(): void { } } -suite('Editor groups service', () => { +suite('EditorGroupsService', () => { function registerTestEditorInput(): void { @@ -291,7 +293,7 @@ suite('Editor groups service', () => { part.dispose(); }); - test('copy/merge groups', function () { + test('copy/merge groups', async () => { const part = createPart(); let groupAddedCounter = 0; @@ -312,40 +314,32 @@ suite('Editor groups service', () => { const input = new TestEditorInput(URI.file('foo/bar')); - return rootGroup.openEditor(input, EditorOptions.create({ pinned: true })).then(() => { - const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT, { activate: true }); - const downGroup = part.copyGroup(rootGroup, rightGroup, GroupDirection.DOWN); - - assert.equal(groupAddedCounter, 2); - assert.equal(downGroup.count, 1); - assert.ok(downGroup.activeEditor instanceof TestEditorInput); - - part.mergeGroup(rootGroup, rightGroup, { mode: MergeGroupMode.COPY_EDITORS }); - assert.equal(rightGroup.count, 1); - assert.ok(rightGroup.activeEditor instanceof TestEditorInput); - - part.mergeGroup(rootGroup, rightGroup, { mode: MergeGroupMode.MOVE_EDITORS }); - assert.equal(rootGroup.count, 0); - - part.mergeGroup(rootGroup, downGroup); - assert.equal(groupRemovedCounter, 1); - assert.equal(rootGroupDisposed, true); - - groupAddedListener.dispose(); - groupRemovedListener.dispose(); - disposeListener.dispose(); - - part.dispose(); - }); + await rootGroup.openEditor(input, EditorOptions.create({ pinned: true })); + const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT, { activate: true }); + const downGroup = part.copyGroup(rootGroup, rightGroup, GroupDirection.DOWN); + assert.equal(groupAddedCounter, 2); + assert.equal(downGroup.count, 1); + assert.ok(downGroup.activeEditor instanceof TestEditorInput); + part.mergeGroup(rootGroup, rightGroup, { mode: MergeGroupMode.COPY_EDITORS }); + assert.equal(rightGroup.count, 1); + assert.ok(rightGroup.activeEditor instanceof TestEditorInput); + part.mergeGroup(rootGroup, rightGroup, { mode: MergeGroupMode.MOVE_EDITORS }); + assert.equal(rootGroup.count, 0); + part.mergeGroup(rootGroup, downGroup); + assert.equal(groupRemovedCounter, 1); + assert.equal(rootGroupDisposed, true); + groupAddedListener.dispose(); + groupRemovedListener.dispose(); + disposeListener.dispose(); + part.dispose(); }); - test('whenRestored', () => { + test('whenRestored', async () => { const part = createPart(); - return part.whenRestored.then(() => { - assert.ok(true); - part.dispose(); - }); + await part.whenRestored; + assert.ok(true); + part.dispose(); }); test('options', () => { @@ -467,7 +461,7 @@ suite('Editor groups service', () => { part.dispose(); }); - test('openEditors / closeEditors', function () { + test('openEditors / closeEditors', async () => { const part = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty(), true); @@ -475,20 +469,17 @@ suite('Editor groups service', () => { const input = new TestEditorInput(URI.file('foo/bar')); const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); - return group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]).then(() => { - assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); + assert.equal(group.count, 2); + assert.equal(group.getEditor(0), input); + assert.equal(group.getEditor(1), inputInactive); - return group.closeEditors([input, inputInactive]).then(() => { - assert.equal(group.isEmpty(), true); - - part.dispose(); - }); - }); + await group.closeEditors([input, inputInactive]); + assert.equal(group.isEmpty(), true); + part.dispose(); }); - test('closeEditors (except one)', function () { + test('closeEditors (except one)', async () => { const part = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty(), true); @@ -497,22 +488,19 @@ suite('Editor groups service', () => { const input2 = new TestEditorInput(URI.file('foo/bar2')); const input3 = new TestEditorInput(URI.file('foo/bar3')); - return group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]).then(() => { - assert.equal(group.count, 3); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); - assert.equal(group.getEditor(2), input3); + await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); + assert.equal(group.count, 3); + assert.equal(group.getEditor(0), input1); + assert.equal(group.getEditor(1), input2); + assert.equal(group.getEditor(2), input3); - return group.closeEditors({ except: input2 }).then(() => { - assert.equal(group.count, 1); - assert.equal(group.getEditor(0), input2); - - part.dispose(); - }); - }); + await group.closeEditors({ except: input2 }); + assert.equal(group.count, 1); + assert.equal(group.getEditor(0), input2); + part.dispose(); }); - test('closeEditors (saved only)', function () { + test('closeEditors (saved only)', async () => { const part = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty(), true); @@ -521,21 +509,18 @@ suite('Editor groups service', () => { const input2 = new TestEditorInput(URI.file('foo/bar2')); const input3 = new TestEditorInput(URI.file('foo/bar3')); - return group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]).then(() => { - assert.equal(group.count, 3); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); - assert.equal(group.getEditor(2), input3); + await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); + assert.equal(group.count, 3); + assert.equal(group.getEditor(0), input1); + assert.equal(group.getEditor(1), input2); + assert.equal(group.getEditor(2), input3); - return group.closeEditors({ savedOnly: true }).then(() => { - assert.equal(group.count, 0); - - part.dispose(); - }); - }); + await group.closeEditors({ savedOnly: true }); + assert.equal(group.count, 0); + part.dispose(); }); - test('closeEditors (direction: right)', function () { + test('closeEditors (direction: right)', async () => { const part = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty(), true); @@ -544,23 +529,20 @@ suite('Editor groups service', () => { const input2 = new TestEditorInput(URI.file('foo/bar2')); const input3 = new TestEditorInput(URI.file('foo/bar3')); - return group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]).then(() => { - assert.equal(group.count, 3); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); - assert.equal(group.getEditor(2), input3); + await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); + assert.equal(group.count, 3); + assert.equal(group.getEditor(0), input1); + assert.equal(group.getEditor(1), input2); + assert.equal(group.getEditor(2), input3); - return group.closeEditors({ direction: CloseDirection.RIGHT, except: input2 }).then(() => { - assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); - - part.dispose(); - }); - }); + await group.closeEditors({ direction: CloseDirection.RIGHT, except: input2 }); + assert.equal(group.count, 2); + assert.equal(group.getEditor(0), input1); + assert.equal(group.getEditor(1), input2); + part.dispose(); }); - test('closeEditors (direction: left)', function () { + test('closeEditors (direction: left)', async () => { const part = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty(), true); @@ -569,23 +551,20 @@ suite('Editor groups service', () => { const input2 = new TestEditorInput(URI.file('foo/bar2')); const input3 = new TestEditorInput(URI.file('foo/bar3')); - return group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]).then(() => { - assert.equal(group.count, 3); - assert.equal(group.getEditor(0), input1); - assert.equal(group.getEditor(1), input2); - assert.equal(group.getEditor(2), input3); + await group.openEditors([{ editor: input1, options: { pinned: true } }, { editor: input2, options: { pinned: true } }, { editor: input3 }]); + assert.equal(group.count, 3); + assert.equal(group.getEditor(0), input1); + assert.equal(group.getEditor(1), input2); + assert.equal(group.getEditor(2), input3); - return group.closeEditors({ direction: CloseDirection.LEFT, except: input2 }).then(() => { - assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input2); - assert.equal(group.getEditor(1), input3); - - part.dispose(); - }); - }); + await group.closeEditors({ direction: CloseDirection.LEFT, except: input2 }); + assert.equal(group.count, 2); + assert.equal(group.getEditor(0), input2); + assert.equal(group.getEditor(1), input3); + part.dispose(); }); - test('closeAllEditors', () => { + test('closeAllEditors', async () => { const part = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty(), true); @@ -593,20 +572,17 @@ suite('Editor groups service', () => { const input = new TestEditorInput(URI.file('foo/bar')); const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); - return group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]).then(() => { - assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); + await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); + assert.equal(group.count, 2); + assert.equal(group.getEditor(0), input); + assert.equal(group.getEditor(1), inputInactive); - return group.closeAllEditors().then(() => { - assert.equal(group.isEmpty(), true); - - part.dispose(); - }); - }); + await group.closeAllEditors(); + assert.equal(group.isEmpty(), true); + part.dispose(); }); - test('moveEditor (same group)', function () { + test('moveEditor (same group)', async () => { const part = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty(), true); @@ -622,22 +598,19 @@ suite('Editor groups service', () => { } }); - return group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]).then(() => { - assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); - - group.moveEditor(inputInactive, group, { index: 0 }); - assert.equal(editorMoveCounter, 1); - assert.equal(group.getEditor(0), inputInactive); - assert.equal(group.getEditor(1), input); - - editorGroupChangeListener.dispose(); - part.dispose(); - }); + await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); + assert.equal(group.count, 2); + assert.equal(group.getEditor(0), input); + assert.equal(group.getEditor(1), inputInactive); + group.moveEditor(inputInactive, group, { index: 0 }); + assert.equal(editorMoveCounter, 1); + assert.equal(group.getEditor(0), inputInactive); + assert.equal(group.getEditor(1), input); + editorGroupChangeListener.dispose(); + part.dispose(); }); - test('moveEditor (across groups)', function () { + test('moveEditor (across groups)', async () => { const part = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty(), true); @@ -647,23 +620,19 @@ suite('Editor groups service', () => { const input = new TestEditorInput(URI.file('foo/bar')); const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); - return group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]).then(() => { - assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); - - group.moveEditor(inputInactive, rightGroup, { index: 0 }); - assert.equal(group.count, 1); - assert.equal(group.getEditor(0), input); - - assert.equal(rightGroup.count, 1); - assert.equal(rightGroup.getEditor(0), inputInactive); - - part.dispose(); - }); + await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); + assert.equal(group.count, 2); + assert.equal(group.getEditor(0), input); + assert.equal(group.getEditor(1), inputInactive); + group.moveEditor(inputInactive, rightGroup, { index: 0 }); + assert.equal(group.count, 1); + assert.equal(group.getEditor(0), input); + assert.equal(rightGroup.count, 1); + assert.equal(rightGroup.getEditor(0), inputInactive); + part.dispose(); }); - test('copyEditor (across groups)', function () { + test('copyEditor (across groups)', async () => { const part = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty(), true); @@ -673,24 +642,20 @@ suite('Editor groups service', () => { const input = new TestEditorInput(URI.file('foo/bar')); const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); - return group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]).then(() => { - assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); - - group.copyEditor(inputInactive, rightGroup, { index: 0 }); - assert.equal(group.count, 2); - assert.equal(group.getEditor(0), input); - assert.equal(group.getEditor(1), inputInactive); - - assert.equal(rightGroup.count, 1); - assert.equal(rightGroup.getEditor(0), inputInactive); - - part.dispose(); - }); + await group.openEditors([{ editor: input, options: { pinned: true } }, { editor: inputInactive }]); + assert.equal(group.count, 2); + assert.equal(group.getEditor(0), input); + assert.equal(group.getEditor(1), inputInactive); + group.copyEditor(inputInactive, rightGroup, { index: 0 }); + assert.equal(group.count, 2); + assert.equal(group.getEditor(0), input); + assert.equal(group.getEditor(1), inputInactive); + assert.equal(rightGroup.count, 1); + assert.equal(rightGroup.getEditor(0), inputInactive); + part.dispose(); }); - test('replaceEditors', () => { + test('replaceEditors', async () => { const part = createPart(); const group = part.activeGroup; assert.equal(group.isEmpty(), true); @@ -698,17 +663,14 @@ suite('Editor groups service', () => { const input = new TestEditorInput(URI.file('foo/bar')); const inputInactive = new TestEditorInput(URI.file('foo/bar/inactive')); - return group.openEditor(input).then(() => { - assert.equal(group.count, 1); - assert.equal(group.getEditor(0), input); + await group.openEditor(input); + assert.equal(group.count, 1); + assert.equal(group.getEditor(0), input); - return group.replaceEditors([{ editor: input, replacement: inputInactive }]).then(() => { - assert.equal(group.count, 1); - assert.equal(group.getEditor(0), inputInactive); - - part.dispose(); - }); - }); + await group.replaceEditors([{ editor: input, replacement: inputInactive }]); + assert.equal(group.count, 1); + assert.equal(group.getEditor(0), inputInactive); + part.dispose(); }); test('find neighbour group (left/right)', function () { diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index c67da39c4b4..658a4a84e77 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -8,7 +8,7 @@ import { IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorInput, EditorOptions, IFileEditorInput, IEditorInput } from 'vs/workbench/common/editor'; -import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestStorageService, NullFileSystemProvider } from 'vs/workbench/test/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { EditorService, DelegatingEditorService } from 'vs/workbench/services/editor/browser/editorService'; @@ -27,15 +27,19 @@ import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; import { timeout } from 'vs/base/common/async'; import { toResource } from 'vs/base/test/common/utils'; +import { IFileService } from 'vs/platform/files/common/files'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; +import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; export class TestEditorControl extends BaseEditor { constructor(@ITelemetryService telemetryService: ITelemetryService) { super('MyTestEditorForEditorService', NullTelemetryService, new TestThemeService(), new TestStorageService()); } - setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Promise { + async setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Promise { super.setInput(input, options, token); - return input.resolve().then(() => undefined); + await input.resolve(); } getId(): string { return 'MyTestEditorForEditorService'; } @@ -54,6 +58,8 @@ export class TestEditorInput extends EditorInput implements IFileEditorInput { setEncoding(encoding: string) { } getEncoding(): string { return null!; } setPreferredEncoding(encoding: string) { } + setMode(mode: string) { } + setPreferredMode(mode: string) { } getResource(): URI { return this.resource; } setForceOpenAsBinary(): void { } setFailToOpen(): void { @@ -65,7 +71,15 @@ export class TestEditorInput extends EditorInput implements IFileEditorInput { } } -suite('Editor service', () => { +class FileServiceProvider extends Disposable { + constructor(scheme: string, @IFileService fileService: IFileService) { + super(); + + this._register(fileService.registerProvider(scheme, new NullFileSystemProvider())); + } +} + +suite('EditorService', () => { function registerTestEditorInput(): void { Registry.as(Extensions.Editors).registerEditor(new EditorDescriptor(TestEditorControl, 'MyTestEditorForEditorService', 'My Test Editor For Next Editor Service'), new SyncDescriptor(TestEditorInput)); @@ -73,7 +87,7 @@ suite('Editor service', () => { registerTestEditorInput(); - test('basics', function () { + test('basics', async () => { const partInstantiator = workbenchInstantiationService(); const part = partInstantiator.createInstance(EditorPart); @@ -102,51 +116,49 @@ suite('Editor service', () => { didCloseEditorListenerCounter++; }); - return part.whenRestored.then(() => { + await part.whenRestored; - // Open input - return service.openEditor(input, { pinned: true }).then(editor => { - assert.ok(editor instanceof TestEditorControl); - assert.equal(editor, service.activeControl); - assert.equal(input, service.activeEditor); - assert.equal(service.visibleControls.length, 1); - assert.equal(service.visibleControls[0], editor); - assert.ok(!service.activeTextEditorWidget); - assert.equal(service.visibleTextEditorWidgets.length, 0); - assert.equal(service.isOpen(input), true); - assert.equal(service.getOpened({ resource: input.getResource() }), input); - assert.equal(service.isOpen(input, part.activeGroup), true); - assert.equal(activeEditorChangeEventCounter, 1); - assert.equal(visibleEditorChangeEventCounter, 1); + // Open input + let editor = await service.openEditor(input, { pinned: true }); - // Close input - return editor!.group!.closeEditor(input).then(() => { - assert.equal(didCloseEditorListenerCounter, 1); - assert.equal(activeEditorChangeEventCounter, 2); - assert.equal(visibleEditorChangeEventCounter, 2); - assert.ok(input.gotDisposed); + assert.ok(editor instanceof TestEditorControl); + assert.equal(editor, service.activeControl); + assert.equal(input, service.activeEditor); + assert.equal(service.visibleControls.length, 1); + assert.equal(service.visibleControls[0], editor); + assert.ok(!service.activeTextEditorWidget); + assert.equal(service.visibleTextEditorWidgets.length, 0); + assert.equal(service.isOpen(input), true); + assert.equal(service.getOpened({ resource: input.getResource() }), input); + assert.equal(service.isOpen(input, part.activeGroup), true); + assert.equal(activeEditorChangeEventCounter, 1); + assert.equal(visibleEditorChangeEventCounter, 1); - // Open again 2 inputs - return service.openEditor(input, { pinned: true }).then(editor => { - return service.openEditor(otherInput, { pinned: true }).then(editor => { - assert.equal(service.visibleControls.length, 1); - assert.equal(service.isOpen(input), true); - assert.equal(service.isOpen(otherInput), true); + // Close input + await editor!.group!.closeEditor(input); - assert.equal(activeEditorChangeEventCounter, 4); - assert.equal(visibleEditorChangeEventCounter, 4); + assert.equal(didCloseEditorListenerCounter, 1); + assert.equal(activeEditorChangeEventCounter, 2); + assert.equal(visibleEditorChangeEventCounter, 2); + assert.ok(input.gotDisposed); - activeEditorChangeListener.dispose(); - visibleEditorChangeListener.dispose(); - didCloseEditorListener.dispose(); - }); - }); - }); - }); - }); + // Open again 2 inputs + await service.openEditor(input, { pinned: true }); + editor = await service.openEditor(otherInput, { pinned: true }); + + assert.equal(service.visibleControls.length, 1); + assert.equal(service.isOpen(input), true); + assert.equal(service.isOpen(otherInput), true); + + assert.equal(activeEditorChangeEventCounter, 4); + assert.equal(visibleEditorChangeEventCounter, 4); + + activeEditorChangeListener.dispose(); + visibleEditorChangeListener.dispose(); + didCloseEditorListener.dispose(); }); - test('openEditors() / replaceEditors()', function () { + test('openEditors() / replaceEditors()', async () => { const partInstantiator = workbenchInstantiationService(); const part = partInstantiator.createInstance(EditorPart); @@ -161,18 +173,16 @@ suite('Editor service', () => { const otherInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource2-openEditors')); const replaceInput = testInstantiationService.createInstance(TestEditorInput, URI.parse('my://resource3-openEditors')); - return part.whenRestored.then(() => { + await part.whenRestored; - // Open editors - return service.openEditors([{ editor: input }, { editor: otherInput }]).then(() => { - assert.equal(part.activeGroup.count, 2); + // Open editors + await service.openEditors([{ editor: input }, { editor: otherInput }]); + assert.equal(part.activeGroup.count, 2); - return service.replaceEditors([{ editor: input, replacement: replaceInput }], part.activeGroup).then(() => { - assert.equal(part.activeGroup.count, 2); - assert.equal(part.activeGroup.getIndexOfEditor(replaceInput), 0); - }); - }); - }); + // Replace editors + await service.replaceEditors([{ editor: input, replacement: replaceInput }], part.activeGroup); + assert.equal(part.activeGroup.count, 2); + assert.equal(part.activeGroup.getIndexOfEditor(replaceInput), 0); }); test('caching', function () { @@ -224,10 +234,15 @@ suite('Editor service', () => { assert.ok(!input1AgainAndAgain!.isDisposed()); }); - test('createInput', function () { + test('createInput', async function () { const instantiationService = workbenchInstantiationService(); const service: EditorService = instantiationService.createInstance(EditorService); + const mode = 'create-input-test'; + ModesRegistry.registerLanguage({ + id: mode, + }); + // Untyped Input (file) let input = service.createInput({ resource: toResource.call(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof FileEditorInput); @@ -240,6 +255,18 @@ suite('Editor service', () => { contentInput = input; assert.equal(contentInput.getPreferredEncoding(), 'utf16le'); + // Untyped Input (file, mode) + input = service.createInput({ resource: toResource.call(this, '/index.html'), mode }); + assert(input instanceof FileEditorInput); + contentInput = input; + assert.equal(contentInput.getPreferredMode(), mode); + + // Untyped Input (file, different mode) + input = service.createInput({ resource: toResource.call(this, '/index.html'), mode: 'text' }); + assert(input instanceof FileEditorInput); + contentInput = input; + assert.equal(contentInput.getPreferredMode(), 'text'); + // Untyped Input (untitled) input = service.createInput({ options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledEditorInput); @@ -247,11 +274,37 @@ suite('Editor service', () => { // Untyped Input (untitled with contents) input = service.createInput({ contents: 'Hello Untitled', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledEditorInput); + let model = await input.resolve() as UntitledEditorModel; + assert.equal(model.textEditorModel!.getValue(), 'Hello Untitled'); + + // Untyped Input (untitled with mode) + input = service.createInput({ mode, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + assert(input instanceof UntitledEditorInput); + model = await input.resolve() as UntitledEditorModel; + assert.equal(model.getMode(), mode); // Untyped Input (untitled with file path) - input = service.createInput({ filePath: '/some/path.txt', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createInput({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof UntitledEditorInput); assert.ok((input as UntitledEditorInput).hasAssociatedFilePath); + + // Untyped Input (untitled with untitled resource) + input = service.createInput({ resource: URI.parse('untitled://Untitled-1'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + assert(input instanceof UntitledEditorInput); + assert.ok(!(input as UntitledEditorInput).hasAssociatedFilePath); + + // Untyped Input (untitled with custom resource) + const provider = instantiationService.createInstance(FileServiceProvider, 'untitled-custom'); + + input = service.createInput({ resource: URI.parse('untitled-custom://some/path'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + assert(input instanceof UntitledEditorInput); + assert.ok((input as UntitledEditorInput).hasAssociatedFilePath); + + provider.dispose(); + + // Untyped Input (resource) + input = service.createInput({ resource: URI.parse('custom:resource') }); + assert(input instanceof ResourceEditorInput); }); test('delegate', function (done) { @@ -274,7 +327,7 @@ suite('Editor service', () => { const ed = instantiationService.createInstance(MyEditor, 'my.editor'); - const inp = instantiationService.createInstance(ResourceEditorInput, 'name', 'description', URI.parse('my://resource-delegate')); + const inp = instantiationService.createInstance(ResourceEditorInput, 'name', 'description', URI.parse('my://resource-delegate'), undefined); const delegate = instantiationService.createInstance(DelegatingEditorService); delegate.setEditorOpenHandler((group: IEditorGroup, input: IEditorInput, options?: EditorOptions) => { assert.strictEqual(input, inp); @@ -287,7 +340,7 @@ suite('Editor service', () => { delegate.openEditor(inp); }); - test('close editor does not dispose when editor opened in other group', function () { + test('close editor does not dispose when editor opened in other group', async () => { const partInstantiator = workbenchInstantiationService(); const part = partInstantiator.createInstance(EditorPart); @@ -303,30 +356,26 @@ suite('Editor service', () => { const rootGroup = part.activeGroup; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); - return part.whenRestored.then(() => { + await part.whenRestored; - // Open input - return service.openEditor(input, { pinned: true }).then(editor => { - return service.openEditor(input, { pinned: true }, rightGroup).then(editor => { - const editors = service.editors; - assert.equal(editors.length, 2); - assert.equal(editors[0], input); - assert.equal(editors[1], input); + // Open input + await service.openEditor(input, { pinned: true }); + await service.openEditor(input, { pinned: true }, rightGroup); - // Close input - return rootGroup.closeEditor(input).then(() => { - assert.equal(input.isDisposed(), false); + const editors = service.editors; + assert.equal(editors.length, 2); + assert.equal(editors[0], input); + assert.equal(editors[1], input); - return rightGroup.closeEditor(input).then(() => { - assert.equal(input.isDisposed(), true); - }); - }); - }); - }); - }); + // Close input + await rootGroup.closeEditor(input); + assert.equal(input.isDisposed(), false); + + await rightGroup.closeEditor(input); + assert.equal(input.isDisposed(), true); }); - test('open to the side', function () { + test('open to the side', async () => { const partInstantiator = workbenchInstantiationService(); const part = partInstantiator.createInstance(EditorPart); @@ -342,22 +391,20 @@ suite('Editor service', () => { const rootGroup = part.activeGroup; - return part.whenRestored.then(() => { - return service.openEditor(input1, { pinned: true }, rootGroup).then(editor => { - return service.openEditor(input1, { pinned: true, preserveFocus: true }, SIDE_GROUP).then(editor => { - assert.equal(part.activeGroup, rootGroup); - assert.equal(part.count, 2); - assert.equal(editor!.group, part.groups[1]); + await part.whenRestored; - // Open to the side uses existing neighbour group if any - return service.openEditor(input2, { pinned: true, preserveFocus: true }, SIDE_GROUP).then(editor => { - assert.equal(part.activeGroup, rootGroup); - assert.equal(part.count, 2); - assert.equal(editor!.group, part.groups[1]); - }); - }); - }); - }); + await service.openEditor(input1, { pinned: true }, rootGroup); + let editor = await service.openEditor(input1, { pinned: true, preserveFocus: true }, SIDE_GROUP); + + assert.equal(part.activeGroup, rootGroup); + assert.equal(part.count, 2); + assert.equal(editor!.group, part.groups[1]); + + // Open to the side uses existing neighbour group if any + editor = await service.openEditor(input2, { pinned: true, preserveFocus: true }, SIDE_GROUP); + assert.equal(part.activeGroup, rootGroup); + assert.equal(part.count, 2); + assert.equal(editor!.group, part.groups[1]); }); test('active editor change / visible editor change events', async function () { diff --git a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts index 33146f77b23..4e63fa92494 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts @@ -14,7 +14,7 @@ import { ExtHostCustomersRegistry } from 'vs/workbench/api/common/extHostCustome import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol'; -import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ResolvedAuthority, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; @@ -51,6 +51,7 @@ export class ExtensionHostProcessManager extends Disposable { * winjs believes a proxy is a promise because it has a `then` method, so wrap the result in an object. */ private _extensionHostProcessProxy: Promise<{ value: ExtHostExtensionServiceShape; } | null> | null; + private _resolveAuthorityAttempt: number; constructor( extensionHostProcessWorker: IExtensionHostStarter, @@ -82,6 +83,7 @@ export class ExtensionHostProcessManager extends Disposable { measure: () => this.measure() })); }); + this._resolveAuthorityAttempt = 0; } public dispose(): void { @@ -259,7 +261,13 @@ export class ExtensionHostProcessManager extends Disposable { if (!proxy) { throw new Error(`Cannot resolve authority`); } - return proxy.$resolveAuthority(remoteAuthority); + this._resolveAuthorityAttempt++; + const result = await proxy.$resolveAuthority(remoteAuthority, this._resolveAuthorityAttempt); + if (result.type === 'ok') { + return result.value; + } else { + throw new RemoteAuthorityResolverError(result.error.message, result.error.code, result.error.detail); + } } public async start(enabledExtensionIds: ExtensionIdentifier[]): Promise { diff --git a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts index f2ba4eaa8e2..bfba4c2300e 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts @@ -24,9 +24,9 @@ export function createMessageOfType(type: MessageType): VSBuffer { const result = VSBuffer.alloc(1); switch (type) { - case MessageType.Initialized: result.writeUint8(1, 0); break; - case MessageType.Ready: result.writeUint8(2, 0); break; - case MessageType.Terminate: result.writeUint8(3, 0); break; + case MessageType.Initialized: result.writeUInt8(1, 0); break; + case MessageType.Ready: result.writeUInt8(2, 0); break; + case MessageType.Terminate: result.writeUInt8(3, 0); break; } return result; @@ -37,7 +37,7 @@ export function isMessageOfType(message: VSBuffer, type: MessageType): boolean { return false; } - switch (message.readUint8(0)) { + switch (message.readUInt8(0)) { case 1: return type === MessageType.Initialized; case 2: return type === MessageType.Ready; case 3: return type === MessageType.Terminate; diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index aa3e0aeedec..6fc035817de 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -12,8 +12,8 @@ import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/c import { Registry } from 'vs/platform/registry/common/platform'; import { IMessage } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { values } from 'vs/base/common/map'; -const hasOwnProperty = Object.hasOwnProperty; const schemaRegistry = Registry.as(Extensions.JSONContribution); export type ExtensionKind = 'workspace' | 'ui' | undefined; @@ -370,18 +370,14 @@ export interface IExtensionPointDescriptor { export class ExtensionsRegistryImpl { - private _extensionPoints: { [extPoint: string]: ExtensionPoint; }; - - constructor() { - this._extensionPoints = {}; - } + private readonly _extensionPoints = new Map>(); public registerExtensionPoint(desc: IExtensionPointDescriptor): IExtensionPoint { - if (hasOwnProperty.call(this._extensionPoints, desc.extensionPoint)) { + if (this._extensionPoints.has(desc.extensionPoint)) { throw new Error('Duplicate extension point: ' + desc.extensionPoint); } - let result = new ExtensionPoint(desc.extensionPoint, desc.defaultExtensionKind); - this._extensionPoints[desc.extensionPoint] = result; + const result = new ExtensionPoint(desc.extensionPoint, desc.defaultExtensionKind); + this._extensionPoints.set(desc.extensionPoint, result); schema.properties['contributes'].properties[desc.extensionPoint] = desc.jsonSchema; schemaRegistry.registerSchema(schemaId, schema); @@ -390,11 +386,7 @@ export class ExtensionsRegistryImpl { } public getExtensionPoints(): ExtensionPoint[] { - return Object.keys(this._extensionPoints).map(point => this._extensionPoints[point]); - } - - public getExtensionPointsMap(): { [extPoint: string]: ExtensionPoint; } { - return this._extensionPoints; + return values(this._extensionPoints); } } diff --git a/src/vs/workbench/services/extensions/common/rpcProtocol.ts b/src/vs/workbench/services/extensions/common/rpcProtocol.ts index 94e105fc6d4..c3cd43edd6a 100644 --- a/src/vs/workbench/services/extensions/common/rpcProtocol.ts +++ b/src/vs/workbench/services/extensions/common/rpcProtocol.ts @@ -463,20 +463,20 @@ class MessageBuffer { } public writeUInt8(n: number): void { - this._buff.writeUint8(n, this._offset); this._offset += 1; + this._buff.writeUInt8(n, this._offset); this._offset += 1; } public readUInt8(): number { - const n = this._buff.readUint8(this._offset); this._offset += 1; + const n = this._buff.readUInt8(this._offset); this._offset += 1; return n; } public writeUInt32(n: number): void { - this._buff.writeUint32BE(n, this._offset); this._offset += 4; + this._buff.writeUInt32BE(n, this._offset); this._offset += 4; } public readUInt32(): number { - const n = this._buff.readUint32BE(this._offset); this._offset += 4; + const n = this._buff.readUInt32BE(this._offset); this._offset += 4; return n; } @@ -485,12 +485,12 @@ class MessageBuffer { } public writeShortString(str: VSBuffer): void { - this._buff.writeUint8(str.byteLength, this._offset); this._offset += 1; + this._buff.writeUInt8(str.byteLength, this._offset); this._offset += 1; this._buff.set(str, this._offset); this._offset += str.byteLength; } public readShortString(): string { - const strByteLength = this._buff.readUint8(this._offset); this._offset += 1; + const strByteLength = this._buff.readUInt8(this._offset); this._offset += 1; const strBuff = this._buff.slice(this._offset, this._offset + strByteLength); const str = strBuff.toString(); this._offset += strByteLength; return str; @@ -501,19 +501,19 @@ class MessageBuffer { } public writeLongString(str: VSBuffer): void { - this._buff.writeUint32BE(str.byteLength, this._offset); this._offset += 4; + this._buff.writeUInt32BE(str.byteLength, this._offset); this._offset += 4; this._buff.set(str, this._offset); this._offset += str.byteLength; } public readLongString(): string { - const strByteLength = this._buff.readUint32BE(this._offset); this._offset += 4; + const strByteLength = this._buff.readUInt32BE(this._offset); this._offset += 4; const strBuff = this._buff.slice(this._offset, this._offset + strByteLength); const str = strBuff.toString(); this._offset += strByteLength; return str; } public writeBuffer(buff: VSBuffer): void { - this._buff.writeUint32BE(buff.byteLength, this._offset); this._offset += 4; + this._buff.writeUInt32BE(buff.byteLength, this._offset); this._offset += 4; this._buff.set(buff, this._offset); this._offset += buff.byteLength; } @@ -522,12 +522,12 @@ class MessageBuffer { } public writeVSBuffer(buff: VSBuffer): void { - this._buff.writeUint32BE(buff.byteLength, this._offset); this._offset += 4; + this._buff.writeUInt32BE(buff.byteLength, this._offset); this._offset += 4; this._buff.set(buff, this._offset); this._offset += buff.byteLength; } public readVSBuffer(): VSBuffer { - const buffLength = this._buff.readUint32BE(this._offset); this._offset += 4; + const buffLength = this._buff.readUInt32BE(this._offset); this._offset += 4; const buff = this._buff.slice(this._offset, this._offset + buffLength); this._offset += buffLength; return buff; } @@ -549,7 +549,7 @@ class MessageBuffer { } public writeMixedArray(arr: VSBuffer[], arrType: ArgType[]): void { - this._buff.writeUint8(arr.length, this._offset); this._offset += 1; + this._buff.writeUInt8(arr.length, this._offset); this._offset += 1; for (let i = 0, len = arr.length; i < len; i++) { const el = arr[i]; const elType = arrType[i]; @@ -564,7 +564,7 @@ class MessageBuffer { } public readMixedArray(): Array { - const arrLen = this._buff.readUint8(this._offset); this._offset += 1; + const arrLen = this._buff.readUInt8(this._offset); this._offset += 1; let arr: Array = new Array(arrLen); for (let i = 0; i < arrLen; i++) { const argType = this.readUInt8(); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 40bde4ea7b3..1707b523b1a 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -38,6 +38,7 @@ import { parseExtensionDevOptions } from '../common/extensionDevOptions'; import { VSBuffer } from 'vs/base/common/buffer'; import { IExtensionHostDebugService } from 'vs/workbench/services/extensions/common/extensionHostDebug'; import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions'; +import { isEqualOrParent } from 'vs/base/common/resources'; export class ExtensionHostProcessWorker implements IExtensionHostStarter { @@ -400,7 +401,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : { configuration: withNullAsUndefined(workspace.configuration), id: workspace.id, - name: this._labelService.getWorkspaceLabel(workspace) + name: this._labelService.getWorkspaceLabel(workspace), + isUntitled: workspace.configuration ? isEqualOrParent(workspace.configuration, this._environmentService.untitledWorkspacesHome) : false }, resolvedExtensions: [], hostExtensions: [], diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts index f261d4fa98c..9014546ff67 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts @@ -9,6 +9,7 @@ import { realpathSync } from 'vs/base/node/extpath'; import { IExtensionHostProfile, IExtensionService, ProfileSegmentId, ProfileSession } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { Schemas } from 'vs/base/common/network'; export class ExtensionHostProfiler { @@ -30,7 +31,9 @@ export class ExtensionHostProfiler { private distill(profile: Profile, extensions: IExtensionDescription[]): IExtensionHostProfile { let searchTree = TernarySearchTree.forPaths(); for (let extension of extensions) { - searchTree.set(realpathSync(extension.extensionLocation.fsPath), extension); + if (extension.extensionLocation.scheme === Schemas.file) { + searchTree.set(realpathSync(extension.extensionLocation.fsPath), extension); + } } let nodes = profile.nodes; diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index 6e2179ddd8e..6a450c7a9f4 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -251,7 +251,7 @@ class ExtensionManifestNLSReplacer extends ExtensionManifestHandler { * This routine makes the following assumptions: * The root element is an object literal */ - private static _replaceNLStrings(nlsConfig: NlsConfiguration, literal: T, messages: { [key: string]: string; }, originalMessages: { [key: string]: string } | null, log: ILog, messageScope: string): void { + private static _replaceNLStrings(nlsConfig: NlsConfiguration, literal: T, messages: { [key: string]: string; }, originalMessages: { [key: string]: string } | null, log: ILog, messageScope: string): void { function processEntry(obj: any, key: string | number, command?: boolean) { let value = obj[key]; if (types.isString(value)) { diff --git a/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts b/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts index 5d9270a6e6b..c430a811ac5 100644 --- a/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts +++ b/src/vs/workbench/services/extensions/node/multiExtensionManagement.ts @@ -8,7 +8,6 @@ import { IExtensionManagementService, ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata, IExtensionManagementServerService, IExtensionManagementServer, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { flatten } from 'vs/base/common/arrays'; import { ExtensionType, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -46,8 +45,10 @@ export class MultiExtensionManagementService extends Disposable implements IExte } getInstalled(type?: ExtensionType): Promise { - return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type))) - .then(result => flatten(result)); + const installedExtensions: ILocalExtension[] = []; + return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type).then(extensions => installedExtensions.push(...extensions)))) + .then(_ => installedExtensions) + .catch(e => installedExtensions); } async uninstall(extension: ILocalExtension, force?: boolean): Promise { diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 37157f6fdae..d85ae8c07b3 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -481,7 +481,7 @@ function readWindowsCaCertificates() { } async function readMacCaCertificates() { - const stdout = (await promisify(cp.execFile)('/usr/bin/security', ['find-certificate', '-a', '-p'], { encoding: 'utf8' })).stdout; + const stdout = (await promisify(cp.execFile)('/usr/bin/security', ['find-certificate', '-a', '-p'], { encoding: 'utf8', maxBuffer: 1024 * 1024 })).stdout; const seen = {}; const certs = stdout.split(/(?=-----BEGIN CERTIFICATE-----)/g) .filter(pem => !!pem.length && !seen[pem] && (seen[pem] = true)); diff --git a/src/vs/workbench/services/files2/common/fileService2.ts b/src/vs/workbench/services/files/common/fileService.ts similarity index 71% rename from src/vs/workbench/services/files2/common/fileService2.ts rename to src/vs/workbench/services/files/common/fileService.ts index df43c4771c5..7e06caa3de1 100644 --- a/src/vs/workbench/services/files2/common/fileService2.ts +++ b/src/vs/workbench/services/files/common/fileService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, toDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IFileService, IResolveFileOptions, IResourceEncodings, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, IResolveContentOptions, IContent, IStreamContent, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, ILegacyFileService, IWriteFileOptions } from 'vs/platform/files/common/files'; +import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; @@ -14,43 +14,19 @@ import { TernarySearchTree } from 'vs/base/common/map'; import { isNonEmptyArray, coalesce } from 'vs/base/common/arrays'; import { getBaseLabel } from 'vs/base/common/labels'; import { ILogService } from 'vs/platform/log/common/log'; -import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable } from 'vs/base/common/buffer'; +import { VSBuffer, VSBufferReadable, readableToBuffer, bufferToReadable, streamToBuffer, bufferToStream, VSBufferReadableStream, writeableBufferStream, VSBufferWriteableStream } from 'vs/base/common/buffer'; import { Queue } from 'vs/base/common/async'; +import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; +import { Schemas } from 'vs/base/common/network'; -export class FileService2 extends Disposable implements IFileService { - - //#region TODO@Ben HACKS - - private _legacy: ILegacyFileService | null; - private joinOnLegacy: Promise; - private joinOnImplResolve: (service: ILegacyFileService) => void; - - get whenReady(): Promise { return this.joinOnLegacy.then(() => undefined); } - - setLegacyService(legacy: ILegacyFileService): void { - this._legacy = this._register(legacy); - - this._register(legacy.onAfterOperation(e => this._onAfterOperation.fire(e))); - - this.provider.forEach((provider, scheme) => { - legacy.registerProvider(scheme, provider); - }); - - this.joinOnImplResolve(legacy); - } - - //#endregion +export class FileService extends Disposable implements IFileService { _serviceBrand: ServiceIdentifier; - private readonly BUFFER_SIZE = 16 * 1024; + private readonly BUFFER_SIZE = 64 * 1024; constructor(@ILogService private logService: ILogService) { super(); - - this.joinOnLegacy = new Promise(resolve => { - this.joinOnImplResolve = resolve; - }); } //#region File System Provider @@ -68,13 +44,6 @@ export class FileService2 extends Disposable implements IFileService { throw new Error(`A provider for the scheme ${scheme} is already registered.`); } - let legacyDisposal: IDisposable; - if (this._legacy) { - legacyDisposal = this._legacy.registerProvider(scheme, provider); - } else { - legacyDisposal = Disposable.None; - } - // Add provider with event this.provider.set(scheme, provider); this._onDidChangeFileSystemProviderRegistrations.fire({ added: true, scheme, provider }); @@ -92,8 +61,7 @@ export class FileService2 extends Disposable implements IFileService { this.provider.delete(scheme); dispose(providerDisposables); - }), - legacyDisposal + }) ]); } @@ -134,7 +102,7 @@ export class FileService2 extends Disposable implements IFileService { // Assert path is absolute if (!isAbsolutePath(resource)) { - throw new FileOperationError(localize('invalidPath', "The path of resource '{0}' must be absolute", resource.toString(true)), FileOperationResult.FILE_INVALID_PATH); + throw new FileOperationError(localize('invalidPath', "The path of resource '{0}' must be absolute", this.resourceForError(resource)), FileOperationResult.FILE_INVALID_PATH); } // Activate provider @@ -143,16 +111,26 @@ export class FileService2 extends Disposable implements IFileService { // Assert provider const provider = this.provider.get(resource.scheme); if (!provider) { - const err = new Error(); - err.name = 'ENOPRO'; - err.message = `No provider found for ${resource.toString()}`; + const error = new Error(); + error.name = 'ENOPRO'; + error.message = localize('noProviderFound', "No file system provider found for {0}", resource.toString()); - throw err; + throw error; } return provider; } + private async withReadWriteProvider(resource: URI): Promise { + const provider = await this.withProvider(resource); + + if (hasOpenReadWriteCloseCapability(provider) || hasReadWriteCapability(provider)) { + return provider; + } + + throw new Error('Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed for the operation.'); + } + //#endregion private _onAfterOperation: Emitter = this._register(new Emitter()); @@ -173,13 +151,13 @@ export class FileService2 extends Disposable implements IFileService { // Specially handle file not found case as file operation result if (toFileSystemProviderErrorCode(error) === FileSystemProviderErrorCode.FileNotFound) { throw new FileOperationError( - localize('fileNotFoundError', "File not found ({0})", resource.toString(true)), + localize('fileNotFoundError', "File not found ({0})", this.resourceForError(resource)), FileOperationResult.FILE_NOT_FOUND ); } // Bubble up any other error as is - throw error; + throw this.ensureError(error); } } @@ -228,7 +206,7 @@ export class FileService2 extends Disposable implements IFileService { isReadonly: !!(provider.capabilities & FileSystemProviderCapabilities.Readonly), mtime: stat.mtime, size: stat.size, - etag: etag(stat.mtime, stat.size) + etag: etag({ mtime: stat.mtime, size: stat.size }) }; // check to recurse for directories @@ -288,20 +266,12 @@ export class FileService2 extends Disposable implements IFileService { //#region File Reading/Writing - get encoding(): IResourceEncodings { - if (!this._legacy) { - throw new Error('Legacy file service not ready yet'); - } - - return this._legacy.encoding; - } - async createFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable = VSBuffer.fromString(''), options?: ICreateFileOptions): Promise { // validate overwrite const overwrite = !!(options && options.overwrite); if (!overwrite && await this.exists(resource)) { - throw new FileOperationError(localize('fileExists', "File to create already exists ({0})", resource.toString(true)), FileOperationResult.FILE_MODIFIED_SINCE, options); + throw new FileOperationError(localize('fileExists', "File to create already exists ({0})", this.resourceForError(resource)), FileOperationResult.FILE_MODIFIED_SINCE, options); } // do write into file (this will create it too) @@ -314,13 +284,13 @@ export class FileService2 extends Disposable implements IFileService { } async writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise { - const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource)); - - // validate write - const stat = await this.validateWriteFile(provider, resource, options); + const provider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(resource)); try { + // validate write + const stat = await this.validateWriteFile(provider, resource, options); + // mkdir recursively as needed if (!stat) { await this.mkdirp(provider, dirname(resource)); @@ -332,16 +302,11 @@ export class FileService2 extends Disposable implements IFileService { } // write file: unbuffered - else if (hasReadWriteCapability(provider)) { + else { await this.doWriteUnbuffered(provider, resource, bufferOrReadable); } - - // give up if provider has insufficient capabilities - else { - return Promise.reject('Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed to support creating a file.'); - } } catch (error) { - throw new FileOperationError(localize('err.write', "Failed to write file {0}", resource.toString(false)), toFileOperationResult(error), options); + throw new FileOperationError(localize('err.write', "Unable to write file ({0})", this.ensureError(error).toString()), toFileOperationResult(error), options); } return this.resolve(resource, { resolveMetadata: true }); @@ -357,7 +322,7 @@ export class FileService2 extends Disposable implements IFileService { // file cannot be directory if ((stat.type & FileType.Directory) !== 0) { - throw new Error(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", resource.toString())); + throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options); } // Dirty write prevention: if the file on disk has been changed and does not match our expected @@ -372,19 +337,182 @@ export class FileService2 extends Disposable implements IFileService { // check for size is a weaker check because it can return a false negative if the file has changed // but to the same length. This is a compromise we take to avoid having to produce checksums of // the file content for comparison which would be much slower to compute. - if (options && typeof options.mtime === 'number' && typeof options.etag === 'string' && options.mtime < stat.mtime && options.etag !== etag(stat.size, options.mtime)) { + if ( + options && typeof options.mtime === 'number' && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED && + typeof stat.mtime === 'number' && typeof stat.size === 'number' && + options.mtime < stat.mtime && options.etag !== etag({ mtime: options.mtime /* not using stat.mtime for a reason, see above */, size: stat.size }) + ) { throw new FileOperationError(localize('fileModifiedError', "File Modified Since"), FileOperationResult.FILE_MODIFIED_SINCE, options); } return stat; } - resolveContent(resource: URI, options?: IResolveContentOptions): Promise { - return this.joinOnLegacy.then(legacy => legacy.resolveContent(resource, options)); + async readFile(resource: URI, options?: IReadFileOptions): Promise { + const stream = await this.readFileStream(resource, options); + + return { + ...stream, + value: await streamToBuffer(stream.value) + }; } - async resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise { - return this.joinOnLegacy.then(legacy => legacy.resolveStreamContent(resource, options)); + async readFileStream(resource: URI, options?: IReadFileOptions): Promise { + const provider = await this.withReadWriteProvider(resource); + + // install a cancellation token that gets cancelled + // when any error occurs. this allows us to resolve + // the content of the file while resolving metadata + // but still cancel the operation in certain cases. + const cancellableSource = new CancellationTokenSource(); + + // validate read operation + const statPromise = this.validateReadFile(resource, options).then(stat => stat, error => { + cancellableSource.cancel(); + + throw error; + }); + + try { + + // if the etag is provided, we await the result of the validation + // due to the likelyhood of hitting a NOT_MODIFIED_SINCE result. + // otherwise, we let it run in parallel to the file reading for + // optimal startup performance. + if (options && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED) { + await statPromise; + } + + let fileStreamPromise: Promise; + + // read buffered + if (hasOpenReadWriteCloseCapability(provider)) { + fileStreamPromise = Promise.resolve(this.readFileBuffered(provider, resource, cancellableSource.token, options)); + } + + // read unbuffered + else { + fileStreamPromise = this.readFileUnbuffered(provider, resource, options); + } + + const [fileStat, fileStream] = await Promise.all([statPromise, fileStreamPromise]); + + return { + ...fileStat, + value: fileStream + }; + } catch (error) { + throw new FileOperationError(localize('err.read', "Unable to read file ({0})", this.ensureError(error).toString()), toFileOperationResult(error), options); + } + } + + private readFileBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, token: CancellationToken, options?: IReadFileOptions): VSBufferReadableStream { + const stream = writeableBufferStream(); + + // do not await reading but simply return + // the stream directly since it operates + // via events. finally end the stream and + // send through the possible error + let error: Error | undefined = undefined; + this.doReadFileBuffered(provider, resource, stream, token, options).then(undefined, err => error = err).finally(() => stream.end(error)); + + return stream; + } + + private async doReadFileBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, stream: VSBufferWriteableStream, token: CancellationToken, options?: IReadFileOptions): Promise { + + // open handle through provider + const handle = await provider.open(resource, { create: false }); + + try { + let totalBytesRead = 0; + let bytesRead = 0; + let allowedRemainingBytes = (options && typeof options.length === 'number') ? options.length : undefined; + + let buffer = VSBuffer.alloc(Math.min(this.BUFFER_SIZE, typeof allowedRemainingBytes === 'number' ? allowedRemainingBytes : this.BUFFER_SIZE)); + + let posInFile = options && typeof options.position === 'number' ? options.position : 0; + let posInBuffer = 0; + do { + // read from source (handle) at current position (pos) into buffer (buffer) at + // buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength). + bytesRead = await provider.read(handle, posInFile, buffer.buffer, posInBuffer, buffer.byteLength - posInBuffer); + + posInFile += bytesRead; + posInBuffer += bytesRead; + totalBytesRead += bytesRead; + + if (typeof allowedRemainingBytes === 'number') { + allowedRemainingBytes -= bytesRead; + } + + // when buffer full, create a new one and emit it through stream + if (posInBuffer === buffer.byteLength) { + stream.write(buffer); + + buffer = VSBuffer.alloc(Math.min(this.BUFFER_SIZE, typeof allowedRemainingBytes === 'number' ? allowedRemainingBytes : this.BUFFER_SIZE)); + + posInBuffer = 0; + } + } while (bytesRead > 0 && (typeof allowedRemainingBytes !== 'number' || allowedRemainingBytes > 0) && this.throwIfCancelled(token) && this.throwIfTooLarge(totalBytesRead, options)); + + // wrap up with last buffer (also respect maxBytes if provided) + if (posInBuffer > 0) { + let lastChunkLength = posInBuffer; + if (typeof allowedRemainingBytes === 'number') { + lastChunkLength = Math.min(posInBuffer, allowedRemainingBytes); + } + + stream.write(buffer.slice(0, lastChunkLength)); + } + } catch (error) { + throw this.ensureError(error); + } finally { + await provider.close(handle); + } + } + + private async readFileUnbuffered(provider: IFileSystemProviderWithFileReadWriteCapability, resource: URI, options?: IReadFileOptions): Promise { + let buffer = await provider.readFile(resource); + + // respect position option + if (options && typeof options.position === 'number') { + buffer = buffer.slice(options.position); + } + + // respect length option + if (options && typeof options.length === 'number') { + buffer = buffer.slice(0, options.length); + } + + return bufferToStream(VSBuffer.wrap(buffer)); + } + + private async validateReadFile(resource: URI, options?: IReadFileOptions): Promise { + const stat = await this.resolve(resource, { resolveMetadata: true }); + + // Return early if resource is a directory + if (stat.isDirectory) { + throw new FileOperationError(localize('fileIsDirectoryError', "Expected file {0} is actually a directory", this.resourceForError(resource)), FileOperationResult.FILE_IS_DIRECTORY, options); + } + + // Return early if file not modified since (unless disabled) + if (options && typeof options.etag === 'string' && options.etag !== ETAG_DISABLED && options.etag === stat.etag) { + throw new FileOperationError(localize('fileNotModifiedError', "File not modified since"), FileOperationResult.FILE_NOT_MODIFIED_SINCE, options); + } + + // Return early if file is too large to load + if (options && options.limits) { + if (typeof options.limits.memory === 'number' && stat.size > options.limits.memory) { + throw new FileOperationError(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); + } + + if (typeof options.limits.size === 'number' && stat.size > options.limits.size) { + throw new FileOperationError(localize('fileTooLargeError', "File is too large to open"), FileOperationResult.FILE_TOO_LARGE); + } + } + + return stat; } //#endregion @@ -392,8 +520,8 @@ export class FileService2 extends Disposable implements IFileService { //#region Move/Copy/Delete/Create Folder async move(source: URI, target: URI, overwrite?: boolean): Promise { - const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withProvider(source)); - const targetProvider = this.throwIfFileSystemIsReadonly(await this.withProvider(target)); + const sourceProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(source)); + const targetProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(target)); // move const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'move', overwrite); @@ -406,8 +534,8 @@ export class FileService2 extends Disposable implements IFileService { } async copy(source: URI, target: URI, overwrite?: boolean): Promise { - const sourceProvider = await this.withProvider(source); - const targetProvider = this.throwIfFileSystemIsReadonly(await this.withProvider(target)); + const sourceProvider = await this.withReadWriteProvider(source); + const targetProvider = this.throwIfFileSystemIsReadonly(await this.withReadWriteProvider(target)); // copy const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', overwrite); @@ -419,7 +547,7 @@ export class FileService2 extends Disposable implements IFileService { return fileStat; } - private async doMoveCopy(sourceProvider: IFileSystemProvider, source: URI, targetProvider: IFileSystemProvider, target: URI, mode: 'move' | 'copy', overwrite?: boolean): Promise<'move' | 'copy'> { + private async doMoveCopy(sourceProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability | IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI, mode: 'move' | 'copy', overwrite?: boolean): Promise<'move' | 'copy'> { // validation const { exists, isCaseChange } = await this.doValidateMoveCopy(sourceProvider, source, targetProvider, target, overwrite); @@ -437,25 +565,21 @@ export class FileService2 extends Disposable implements IFileService { // same provider with fast copy: leverage copy() functionality if (sourceProvider === targetProvider && hasFileFolderCopyCapability(sourceProvider)) { - return sourceProvider.copy(source, target, { overwrite: !!overwrite }).then(() => mode); - } - - // otherwise, ensure we got the capabilities to do this - if ( - !(hasOpenReadWriteCloseCapability(sourceProvider) || hasReadWriteCapability(sourceProvider)) || - !(hasOpenReadWriteCloseCapability(targetProvider) || hasReadWriteCapability(targetProvider)) - ) { - throw new Error('Provider neither has FileReadWrite nor FileOpenReadWriteClose capability which is needed to support copy.'); + await sourceProvider.copy(source, target, { overwrite: !!overwrite }); } // when copying via buffer/unbuffered, we have to manually // traverse the source if it is a folder and not a file - const sourceFile = await this.resolve(source); - if (sourceFile.isDirectory) { - return this.doCopyFolder(sourceProvider, sourceFile, targetProvider, target).then(() => mode); - } else { - return this.doCopyFile(sourceProvider, source, targetProvider, target).then(() => mode); + else { + const sourceFile = await this.resolve(source); + if (sourceFile.isDirectory) { + await this.doCopyFolder(sourceProvider, sourceFile, targetProvider, target); + } else { + await this.doCopyFile(sourceProvider, source, targetProvider, target); + } } + + return mode; } // move source => target @@ -463,14 +587,18 @@ export class FileService2 extends Disposable implements IFileService { // same provider: leverage rename() functionality if (sourceProvider === targetProvider) { - return sourceProvider.rename(source, target, { overwrite: !!overwrite }).then(() => mode); + await sourceProvider.rename(source, target, { overwrite: !!overwrite }); + + return mode; } // across providers: copy to target & delete at source else { await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', overwrite); - return this.del(source, { recursive: true }).then(() => 'copy' as 'move' | 'copy'); + await this.del(source, { recursive: true }); + + return 'copy'; } } } @@ -569,7 +697,7 @@ export class FileService2 extends Disposable implements IFileService { try { const stat = await provider.stat(directory); if ((stat.type & FileType.Directory) === 0) { - throw new Error(localize('mkdirExistsError', "{0} exists, but is not a directory", directory.toString())); + throw new Error(localize('mkdirExistsError', "{0} exists, but is not a directory", this.resourceForError(directory))); } break; // we have hit a directory that exists -> good @@ -609,7 +737,7 @@ export class FileService2 extends Disposable implements IFileService { if (!recursive && await this.exists(resource)) { const stat = await this.resolve(resource); if (stat.isDirectory && Array.isArray(stat.children) && stat.children.length > 0) { - throw new Error(localize('deleteFailed', "Failed to delete non-empty folder '{0}'.", resource.toString())); + throw new Error(localize('deleteFailed', "Unable to delete non-empty folder '{0}'.", this.resourceForError(resource))); } } @@ -739,7 +867,7 @@ export class FileService2 extends Disposable implements IFileService { posInFile += chunk.byteLength; } } catch (error) { - throw error; + throw this.ensureError(error); } finally { await provider.close(handle); } @@ -771,6 +899,7 @@ export class FileService2 extends Disposable implements IFileService { private async doPipeBuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise { return this.ensureWriteQueue(targetProvider, target).queue(() => this.doPipeBufferedQueued(sourceProvider, source, targetProvider, target)); } + private async doPipeBufferedQueued(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise { let sourceHandle: number | undefined = undefined; let targetHandle: number | undefined = undefined; @@ -804,7 +933,7 @@ export class FileService2 extends Disposable implements IFileService { } } while (bytesRead > 0); } catch (error) { - throw error; + throw this.ensureError(error); } finally { await Promise.all([ typeof sourceHandle === 'number' ? sourceProvider.close(sourceHandle) : Promise.resolve(), @@ -835,7 +964,7 @@ export class FileService2 extends Disposable implements IFileService { const buffer = await sourceProvider.readFile(source); await this.doWriteBuffer(targetProvider, targetHandle, VSBuffer.wrap(buffer), buffer.byteLength, 0, 0); } catch (error) { - throw error; + throw this.ensureError(error); } finally { await targetProvider.close(targetHandle); } @@ -843,46 +972,14 @@ export class FileService2 extends Disposable implements IFileService { private async doPipeBufferedToUnbuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI): Promise { - // Open handle - const sourceHandle = await sourceProvider.open(source, { create: false }); + // Read buffer via stream buffered + const buffer = await streamToBuffer(this.readFileBuffered(sourceProvider, source, CancellationToken.None)); - try { - const buffers: VSBuffer[] = []; - - let buffer = VSBuffer.alloc(this.BUFFER_SIZE); - - let posInFile = 0; - let totalBytesRead = 0; - let bytesRead = 0; - let posInBuffer = 0; - do { - // read from source (sourceHandle) at current position (pos) into buffer (buffer) at - // buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength). - bytesRead = await sourceProvider.read(sourceHandle, posInFile, buffer.buffer, posInBuffer, buffer.byteLength - posInBuffer); - - posInFile += bytesRead; - posInBuffer += bytesRead; - totalBytesRead += bytesRead; - - // when buffer full, create a new one - if (posInBuffer === buffer.byteLength) { - buffers.push(buffer); - buffer = VSBuffer.alloc(this.BUFFER_SIZE); - - posInBuffer = 0; - } - } while (bytesRead > 0); - - // Write buffer into target at once - await this.doWriteUnbuffered(targetProvider, target, VSBuffer.concat([...buffers, buffer.slice(0, posInBuffer)], totalBytesRead)); - } catch (error) { - throw error; - } finally { - await sourceProvider.close(sourceHandle); - } + // Write buffer into target at once + await this.doWriteUnbuffered(targetProvider, target, buffer); } - protected throwIfFileSystemIsReadonly(provider: IFileSystemProvider): IFileSystemProvider { + protected throwIfFileSystemIsReadonly(provider: T): T { if (provider.capabilities & FileSystemProviderCapabilities.Readonly) { throw new FileOperationError(localize('err.readonly', "Resource can not be modified."), FileOperationResult.FILE_PERMISSION_DENIED); } @@ -890,5 +987,45 @@ export class FileService2 extends Disposable implements IFileService { return provider; } + private throwIfCancelled(token: CancellationToken): boolean { + if (token.isCancellationRequested) { + throw new Error('cancelled'); + } + + return true; + } + + private ensureError(error?: Error): Error { + if (!error) { + return new Error(localize('unknownError', "Unknown Error")); // https://github.com/Microsoft/vscode/issues/72798 + } + + return error; + } + + private throwIfTooLarge(totalBytesRead: number, options?: IReadFileOptions): boolean { + + // Return early if file is too large to load + if (options && options.limits) { + if (typeof options.limits.memory === 'number' && totalBytesRead > options.limits.memory) { + throw new FileOperationError(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow it to use more memory"), FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); + } + + if (typeof options.limits.size === 'number' && totalBytesRead > options.limits.size) { + throw new FileOperationError(localize('fileTooLargeError', "File is too large to open"), FileOperationResult.FILE_TOO_LARGE); + } + } + + return true; + } + + private resourceForError(resource: URI): string { + if (resource.scheme === Schemas.file) { + return resource.fsPath; + } + + return resource.toString(true); + } + //#endregion } \ No newline at end of file diff --git a/src/vs/workbench/services/files2/common/workspaceWatcher.ts b/src/vs/workbench/services/files/common/workspaceWatcher.ts similarity index 97% rename from src/vs/workbench/services/files2/common/workspaceWatcher.ts rename to src/vs/workbench/services/files/common/workspaceWatcher.ts index 28f9e25d903..a16c82e3594 100644 --- a/src/vs/workbench/services/files2/common/workspaceWatcher.ts +++ b/src/vs/workbench/services/files/common/workspaceWatcher.ts @@ -16,14 +16,14 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; -import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { FileService } from 'vs/workbench/services/files/common/fileService'; export class WorkspaceWatcher extends Disposable { private watches = new ResourceMap(); constructor( - @IFileService private readonly fileService: FileService2, + @IFileService private readonly fileService: FileService, @IConfigurationService private readonly configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @INotificationService private readonly notificationService: INotificationService, diff --git a/src/vs/workbench/services/files2/electron-browser/diskFileSystemProvider.ts b/src/vs/workbench/services/files/electron-browser/diskFileSystemProvider.ts similarity index 95% rename from src/vs/workbench/services/files2/electron-browser/diskFileSystemProvider.ts rename to src/vs/workbench/services/files/electron-browser/diskFileSystemProvider.ts index baf23be07b0..118eb6c762f 100644 --- a/src/vs/workbench/services/files2/electron-browser/diskFileSystemProvider.ts +++ b/src/vs/workbench/services/files/electron-browser/diskFileSystemProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { shell } from 'electron'; -import { DiskFileSystemProvider as NodeDiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; +import { DiskFileSystemProvider as NodeDiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; import { FileDeleteOptions, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { isWindows } from 'vs/base/common/platform'; import { localize } from 'vs/nls'; diff --git a/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts b/src/vs/workbench/services/files/node/diskFileSystemProvider.ts similarity index 89% rename from src/vs/workbench/services/files2/node/diskFileSystemProvider.ts rename to src/vs/workbench/services/files/node/diskFileSystemProvider.ts index c0b013d37e0..714a1dad27c 100644 --- a/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts +++ b/src/vs/workbench/services/files/node/diskFileSystemProvider.ts @@ -10,18 +10,18 @@ import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatc import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isLinux, isWindows } from 'vs/base/common/platform'; -import { statLink, readdir, unlink, move, copy, readFile, writeFile, truncate, rimraf, RimRafMode, exists } from 'vs/base/node/pfs'; +import { statLink, readdir, unlink, move, copy, readFile, truncate, rimraf, RimRafMode, exists } from 'vs/base/node/pfs'; import { normalize, basename, dirname } from 'vs/base/common/path'; import { joinPath } from 'vs/base/common/resources'; import { isEqual } from 'vs/base/common/extpath'; import { retry, ThrottledDelayer } from 'vs/base/common/async'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { localize } from 'vs/nls'; -import { IDiskFileChange, toFileChanges } from 'vs/workbench/services/files2/node/watcher/watcher'; -import { FileWatcher as UnixWatcherService } from 'vs/workbench/services/files2/node/watcher/unix/watcherService'; -import { FileWatcher as WindowsWatcherService } from 'vs/workbench/services/files2/node/watcher/win32/watcherService'; -import { FileWatcher as NsfwWatcherService } from 'vs/workbench/services/files2/node/watcher/nsfw/watcherService'; -import { FileWatcher as NodeJSWatcherService } from 'vs/workbench/services/files2/node/watcher/nodejs/watcherService'; +import { IDiskFileChange, toFileChanges } from 'vs/workbench/services/files/node/watcher/watcher'; +import { FileWatcher as UnixWatcherService } from 'vs/workbench/services/files/node/watcher/unix/watcherService'; +import { FileWatcher as WindowsWatcherService } from 'vs/workbench/services/files/node/watcher/win32/watcherService'; +import { FileWatcher as NsfwWatcherService } from 'vs/workbench/services/files/node/watcher/nsfw/watcherService'; +import { FileWatcher as NodeJSWatcherService } from 'vs/workbench/services/files/node/watcher/nodejs/watcherService'; export class DiskFileSystemProvider extends Disposable implements IFileSystemProvider { @@ -80,16 +80,14 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro const children = await readdir(this.toFilePath(resource)); const result: [string, FileType][] = []; - for (let i = 0; i < children.length; i++) { - const child = children[i]; - + await Promise.all(children.map(async child => { try { const stat = await this.stat(joinPath(resource, child)); result.push([child, stat.type]); } catch (error) { this.logService.trace(error); // ignore errors for individual entries that can arise from permission denied } - } + })); return result; } catch (error) { @@ -112,6 +110,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { + let handle: number | undefined = undefined; try { const filePath = this.toFilePath(resource); @@ -123,34 +122,17 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro throw createFileSystemProviderError(new Error(localize('fileNotExists', "File does not exist")), FileSystemProviderErrorCode.FileNotFound); } - if (fileExists && isWindows) { - try { - // On Windows and if the file exists, we use a different strategy of saving the file - // by first truncating the file and then writing with r+ flag. This helps to save hidden files on Windows - // (see https://github.com/Microsoft/vscode/issues/931) and prevent removing alternate data streams - // (see https://github.com/Microsoft/vscode/issues/6363) - await truncate(filePath, 0); + // Open + handle = await this.open(resource, { create: true }); - // We heard from one user that fs.truncate() succeeds, but the save fails (https://github.com/Microsoft/vscode/issues/61310) - // In that case, the file is now entirely empty and the contents are gone. This can happen if an external file watcher is - // installed that reacts on the truncate and keeps the file busy right after. Our workaround is to retry to save after a - // short timeout, assuming that the file is free to write then. - await retry(() => writeFile(filePath, content, { flag: 'r+' }), 100 /* ms delay */, 3 /* retries */); - } catch (error) { - this.logService.trace(error); - - // we heard from users that fs.truncate() fails (https://github.com/Microsoft/vscode/issues/59561) - // in that case we simply save the file without truncating first (same as macOS and Linux) - await writeFile(filePath, content); - } - } - - // macOS/Linux: just write directly - else { - await writeFile(filePath, content); - } + // Write content at once + await this.write(handle, 0, content, 0, content.byteLength); } catch (error) { throw this.toFileSystemProviderError(error); + } finally { + if (typeof handle === 'number') { + await this.close(handle); + } } } diff --git a/src/vs/workbench/services/files/node/encoding.ts b/src/vs/workbench/services/files/node/encoding.ts deleted file mode 100644 index 65d55db2c0a..00000000000 --- a/src/vs/workbench/services/files/node/encoding.ts +++ /dev/null @@ -1,144 +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 { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; -import * as encoding from 'vs/base/node/encoding'; -import { URI } from 'vs/base/common/uri'; -import { IResolveContentOptions, isParent, IResourceEncodings, IResourceEncoding } from 'vs/platform/files/common/files'; -import { isLinux } from 'vs/base/common/platform'; -import { extname } from 'vs/base/common/path'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { joinPath } from 'vs/base/common/resources'; - -export interface IEncodingOverride { - parent?: URI; - extension?: string; - encoding: string; -} - -// TODO@Ben debt - encodings should move one layer up from the file service into the text file -// service and then ideally be passed in as option to the file service -// the file service should talk about string | Buffer for reading and writing and only convert -// to strings if a encoding is provided -export class ResourceEncodings extends Disposable implements IResourceEncodings { - private encodingOverride: IEncodingOverride[]; - - constructor( - private textResourceConfigurationService: ITextResourceConfigurationService, - private environmentService: IEnvironmentService, - private contextService: IWorkspaceContextService, - encodingOverride?: IEncodingOverride[] - ) { - super(); - - this.encodingOverride = encodingOverride || this.getEncodingOverrides(); - - this.registerListeners(); - } - - private registerListeners(): void { - - // Workspace Folder Change - this._register(this.contextService.onDidChangeWorkspaceFolders(() => { - this.encodingOverride = this.getEncodingOverrides(); - })); - } - - getReadEncoding(resource: URI, options: IResolveContentOptions | undefined, detected: encoding.IDetectedEncodingResult): string { - let preferredEncoding: string | undefined; - - // Encoding passed in as option - if (options && options.encoding) { - if (detected.encoding === encoding.UTF8 && options.encoding === encoding.UTF8) { - preferredEncoding = encoding.UTF8_with_bom; // indicate the file has BOM if we are to resolve with UTF 8 - } else { - preferredEncoding = options.encoding; // give passed in encoding highest priority - } - } - - // Encoding detected - else if (detected.encoding) { - if (detected.encoding === encoding.UTF8) { - preferredEncoding = encoding.UTF8_with_bom; // if we detected UTF-8, it can only be because of a BOM - } else { - preferredEncoding = detected.encoding; - } - } - - // Encoding configured - else if (this.textResourceConfigurationService.getValue(resource, 'files.encoding') === encoding.UTF8_with_bom) { - preferredEncoding = encoding.UTF8; // if we did not detect UTF 8 BOM before, this can only be UTF 8 then - } - - return this.getEncodingForResource(resource, preferredEncoding); - } - - getWriteEncoding(resource: URI, preferredEncoding?: string): IResourceEncoding { - const resourceEncoding = this.getEncodingForResource(resource, preferredEncoding); - - return { - encoding: resourceEncoding, - hasBOM: resourceEncoding === encoding.UTF16be || resourceEncoding === encoding.UTF16le || resourceEncoding === encoding.UTF8_with_bom // enforce BOM for certain encodings - }; - } - - private getEncodingForResource(resource: URI, preferredEncoding?: string): string { - let fileEncoding: string; - - const override = this.getEncodingOverride(resource); - if (override) { - fileEncoding = override; // encoding override always wins - } else if (preferredEncoding) { - fileEncoding = preferredEncoding; // preferred encoding comes second - } else { - fileEncoding = this.textResourceConfigurationService.getValue(resource, 'files.encoding'); // and last we check for settings - } - - if (!fileEncoding || !encoding.encodingExists(fileEncoding)) { - fileEncoding = encoding.UTF8; // the default is UTF 8 - } - - return fileEncoding; - } - - private getEncodingOverrides(): IEncodingOverride[] { - const encodingOverride: IEncodingOverride[] = []; - - // Global settings - encodingOverride.push({ parent: URI.file(this.environmentService.appSettingsHome), encoding: encoding.UTF8 }); - - // Workspace files - encodingOverride.push({ extension: WORKSPACE_EXTENSION, encoding: encoding.UTF8 }); - - // Folder Settings - this.contextService.getWorkspace().folders.forEach(folder => { - encodingOverride.push({ parent: joinPath(folder.uri, '.vscode'), encoding: encoding.UTF8 }); - }); - - return encodingOverride; - } - - private getEncodingOverride(resource: URI): string | null { - if (resource && this.encodingOverride && this.encodingOverride.length) { - for (const override of this.encodingOverride) { - - // check if the resource is child of encoding override path - if (override.parent && isParent(resource.fsPath, override.parent.fsPath, !isLinux /* ignorecase */)) { - return override.encoding; - } - - // check if the resource extension is equal to encoding override - if (override.extension && extname(resource.fsPath) === `.${override.extension}`) { - return override.encoding; - } - } - } - - return null; - } -} diff --git a/src/vs/workbench/services/files/node/fileService.ts b/src/vs/workbench/services/files/node/fileService.ts deleted file mode 100644 index f0b4f8e66fe..00000000000 --- a/src/vs/workbench/services/files/node/fileService.ts +++ /dev/null @@ -1,392 +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 paths from 'vs/base/common/path'; -import * as fs from 'fs'; -import * as assert from 'assert'; -import { FileOperationEvent, IContent, IResolveContentOptions, IFileStat, IStreamContent, FileOperationError, FileOperationResult, IContentData, ILegacyFileService, IFileService, IFileSystemProvider } from 'vs/platform/files/common/files'; -import { MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/platform/files/node/fileConstants'; -import { URI as uri } from 'vs/base/common/uri'; -import * as nls from 'vs/nls'; -import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { detectEncodingFromBuffer, decodeStream } from 'vs/base/node/encoding'; -import { Event, Emitter } from 'vs/base/common/event'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { Schemas } from 'vs/base/common/network'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { IEncodingOverride, ResourceEncodings } from 'vs/workbench/services/files/node/encoding'; -import { withUndefinedAsNull } from 'vs/base/common/types'; - -export interface IFileServiceTestOptions { - encodingOverride?: IEncodingOverride[]; -} - -export class LegacyFileService extends Disposable implements ILegacyFileService { - - _serviceBrand: any; - - registerProvider(scheme: string, provider: IFileSystemProvider): IDisposable { return Disposable.None; } - - protected readonly _onAfterOperation: Emitter = this._register(new Emitter()); - get onAfterOperation(): Event { return this._onAfterOperation.event; } - - private _encoding: ResourceEncodings; - - constructor( - protected fileService: IFileService, - contextService: IWorkspaceContextService, - private environmentService: IEnvironmentService, - private textResourceConfigurationService: ITextResourceConfigurationService, - private options: IFileServiceTestOptions = Object.create(null) - ) { - super(); - - this._encoding = new ResourceEncodings(textResourceConfigurationService, environmentService, contextService, this.options.encodingOverride); - } - - get encoding(): ResourceEncodings { - return this._encoding; - } - - //#region Read File - - resolveContent(resource: uri, options?: IResolveContentOptions): Promise { - return this.resolveStreamContent(resource, options).then(streamContent => { - return new Promise((resolve, reject) => { - - const result: IContent = { - resource: streamContent.resource, - name: streamContent.name, - mtime: streamContent.mtime, - etag: streamContent.etag, - encoding: streamContent.encoding, - isReadonly: streamContent.isReadonly, - size: streamContent.size, - value: '' - }; - - streamContent.value.on('data', chunk => result.value += chunk); - streamContent.value.on('error', err => reject(err)); - streamContent.value.on('end', () => resolve(result)); - - return result; - }); - }); - } - - resolveStreamContent(resource: uri, options?: IResolveContentOptions): Promise { - - // Guard early against attempts to resolve an invalid file path - if (resource.scheme !== Schemas.file || !resource.fsPath) { - return Promise.reject(new FileOperationError( - nls.localize('fileInvalidPath', "Invalid file resource ({0})", resource.toString(true)), - FileOperationResult.FILE_INVALID_PATH, - options - )); - } - - const result: Partial = { - resource: undefined, - name: undefined, - mtime: undefined, - etag: undefined, - encoding: undefined, - isReadonly: false, - value: undefined - }; - - const contentResolverTokenSource = new CancellationTokenSource(); - - const onStatError = (error: Error) => { - - // error: stop reading the file the stat and content resolve call - // usually race, mostly likely the stat call will win and cancel - // the content call - contentResolverTokenSource.cancel(); - - // forward error - return Promise.reject(error); - }; - - const statsPromise = this.fileService.resolve(resource).then(stat => { - result.resource = stat.resource; - result.name = stat.name; - result.mtime = stat.mtime; - result.etag = stat.etag; - result.size = stat.size; - - // Return early if resource is a directory - if (stat.isDirectory) { - return onStatError(new FileOperationError( - nls.localize('fileIsDirectoryError', "File is directory"), - FileOperationResult.FILE_IS_DIRECTORY, - options - )); - } - - // Return early if file not modified since - if (options && options.etag && options.etag === stat.etag) { - return onStatError(new FileOperationError( - nls.localize('fileNotModifiedError', "File not modified since"), - FileOperationResult.FILE_NOT_MODIFIED_SINCE, - options - )); - } - - // Return early if file is too large to load - if (typeof stat.size === 'number') { - if (stat.size > Math.max(typeof this.environmentService.args['max-memory'] === 'string' ? parseInt(this.environmentService.args['max-memory']) * 1024 * 1024 || 0 : 0, MAX_HEAP_SIZE)) { - return onStatError(new FileOperationError( - nls.localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart VS Code and allow it to use more memory"), - FileOperationResult.FILE_EXCEED_MEMORY_LIMIT - )); - } - - if (stat.size > MAX_FILE_SIZE) { - return onStatError(new FileOperationError( - nls.localize('fileTooLargeError', "File too large to open"), - FileOperationResult.FILE_TOO_LARGE - )); - } - } - - return undefined; - }, err => { - - // Wrap file not found errors - if (err.code === 'ENOENT') { - return onStatError(new FileOperationError( - nls.localize('fileNotFoundError', "File not found ({0})", resource.toString(true)), - FileOperationResult.FILE_NOT_FOUND, - options - )); - } - - return onStatError(err); - }); - - let completePromise: Promise; - - // await the stat iff we already have an etag so that we compare the - // etag from the stat before we actually read the file again. - if (options && options.etag) { - completePromise = statsPromise.then(() => { - return this.fillInContents(result, resource, options, contentResolverTokenSource.token); // Waterfall -> only now resolve the contents - }); - } - - // a fresh load without a previous etag which means we can resolve the file stat - // and the content at the same time, avoiding the waterfall. - else { - let statsError: Error; - let contentsError: Error; - - completePromise = Promise.all([ - statsPromise.then(() => undefined, error => statsError = error), - this.fillInContents(result, resource, options, contentResolverTokenSource.token).then(() => undefined, error => contentsError = error) - ]).then(() => { - // Since each file operation can return a FileOperationError - // we want to prefer that one if possible. Otherwise we just - // return with the first error we get. - if (FileOperationError.isFileOperationError(statsError)) { - return Promise.reject(statsError); - } - - if (FileOperationError.isFileOperationError(contentsError)) { - return Promise.reject(contentsError); - } - - if (statsError || contentsError) { - return Promise.reject(statsError || contentsError); - } - - return undefined; - }); - } - - return completePromise.then(() => { - contentResolverTokenSource.dispose(); - - return result; - }, error => { - contentResolverTokenSource.dispose(); - - return Promise.reject(error); - }); - } - - private fillInContents(content: Partial, resource: uri, options: IResolveContentOptions | undefined, token: CancellationToken): Promise { - return this.resolveFileData(resource, options, token).then(data => { - content.encoding = data.encoding; - content.value = data.stream; - }); - } - - private resolveFileData(resource: uri, options: IResolveContentOptions | undefined, token: CancellationToken): Promise { - const chunkBuffer = Buffer.allocUnsafe(64 * 1024); - - const result: Partial = { - encoding: undefined, - stream: undefined - }; - - return new Promise((resolve, reject) => { - fs.open(this.toAbsolutePath(resource), 'r', (err, fd) => { - if (err) { - if (err.code === 'ENOENT') { - // Wrap file not found errors - err = new FileOperationError( - nls.localize('fileNotFoundError', "File not found ({0})", resource.toString(true)), - FileOperationResult.FILE_NOT_FOUND, - options - ); - } - - return reject(err); - } - - let decoder: NodeJS.ReadWriteStream; - let totalBytesRead = 0; - - const finish = (err?: any) => { - if (err) { - if (err.code === 'EISDIR') { - - // Wrap EISDIR errors (fs.open on a directory works, but you cannot read from it) - err = new FileOperationError( - nls.localize('fileIsDirectoryError', "File is directory"), - FileOperationResult.FILE_IS_DIRECTORY, - options - ); - } - if (decoder) { - // If the decoder already started, we have to emit the error through it as - // event because the promise is already resolved! - decoder.emit('error', err); - } else { - reject(err); - } - } - - if (decoder) { - decoder.end(); - } - - if (fd) { - fs.close(fd, err => { - if (err) { - onUnexpectedError(`resolveFileData#close(): ${err.toString()}`); - } - }); - } - }; - - const handleChunk = (bytesRead: number) => { - if (token.isCancellationRequested) { - // cancellation -> finish - finish(new Error('cancelled')); - } else if (bytesRead === 0) { - // no more data -> finish - finish(); - } else if (bytesRead < chunkBuffer.length) { - // write the sub-part of data we received -> repeat - decoder.write(chunkBuffer.slice(0, bytesRead), readChunk); - } else { - // write all data we received -> repeat - decoder.write(chunkBuffer, readChunk); - } - }; - - let currentPosition: number | null = withUndefinedAsNull(options && options.position); - - const readChunk = () => { - fs.read(fd, chunkBuffer, 0, chunkBuffer.length, currentPosition, (err, bytesRead) => { - totalBytesRead += bytesRead; - - if (typeof currentPosition === 'number') { - // if we received a position argument as option we need to ensure that - // we advance the position by the number of bytesread - currentPosition += bytesRead; - } - - if (totalBytesRead > Math.max(typeof this.environmentService.args['max-memory'] === 'number' ? parseInt(this.environmentService.args['max-memory']) * 1024 * 1024 || 0 : 0, MAX_HEAP_SIZE)) { - finish(new FileOperationError( - nls.localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart VS Code and allow it to use more memory"), - FileOperationResult.FILE_EXCEED_MEMORY_LIMIT - )); - } - - if (totalBytesRead > MAX_FILE_SIZE) { - // stop when reading too much - finish(new FileOperationError( - nls.localize('fileTooLargeError', "File too large to open"), - FileOperationResult.FILE_TOO_LARGE, - options - )); - } else if (err) { - // some error happened - finish(err); - - } else if (decoder) { - // pass on to decoder - handleChunk(bytesRead); - - } else { - // when receiving the first chunk of data we need to create the - // decoding stream which is then used to drive the string stream. - Promise.resolve(detectEncodingFromBuffer( - { buffer: chunkBuffer, bytesRead }, - (options && options.autoGuessEncoding) || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding') - )).then(detected => { - if (options && options.acceptTextOnly && detected.seemsBinary) { - // Return error early if client only accepts text and this is not text - finish(new FileOperationError( - nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), - FileOperationResult.FILE_IS_BINARY, - options - )); - - } else { - result.encoding = this._encoding.getReadEncoding(resource, options, detected); - result.stream = decoder = decodeStream(result.encoding); - resolve(result as IContentData); - handleChunk(bytesRead); - } - }).then(undefined, err => { - // failed to get encoding - finish(err); - }); - } - }); - }; - - // start reading - readChunk(); - }); - }); - } - - //#endregion - - //#region Helpers - - private toAbsolutePath(arg1: uri | IFileStat): string { - let resource: uri; - if (arg1 instanceof uri) { - resource = arg1; - } else { - resource = (arg1).resource; - } - - assert.ok(resource && resource.scheme === Schemas.file, `Invalid resource: ${resource}`); - - return paths.normalize(resource.fsPath); - } - - //#endregion -} diff --git a/src/vs/workbench/services/files/node/remoteFileService.ts b/src/vs/workbench/services/files/node/remoteFileService.ts deleted file mode 100644 index 3a1560465e8..00000000000 --- a/src/vs/workbench/services/files/node/remoteFileService.ts +++ /dev/null @@ -1,173 +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 { IDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; -import * as resources from 'vs/base/common/resources'; -import { URI } from 'vs/base/common/uri'; -import { IDecodeStreamOptions, toDecodeStream } from 'vs/base/node/encoding'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { localize } from 'vs/nls'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { FileOperationError, FileOperationResult, IContent, IFileSystemProvider, IResolveContentOptions, IStreamContent, ILegacyFileService, IFileService } from 'vs/platform/files/common/files'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { LegacyFileService } from 'vs/workbench/services/files/node/fileService'; -import { createReadableOfProvider } from 'vs/workbench/services/files/node/streams'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; - -export class LegacyRemoteFileService extends LegacyFileService { - - private readonly _provider: Map; - - constructor( - @IFileService fileService: IFileService, - @IEnvironmentService environmentService: IEnvironmentService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, - ) { - super( - fileService, - contextService, - environmentService, - textResourceConfigurationService - ); - - this._provider = new Map(); - } - - registerProvider(scheme: string, provider: IFileSystemProvider): IDisposable { - if (this._provider.has(scheme)) { - throw new Error('a provider for that scheme is already registered'); - } - - this._provider.set(scheme, provider); - - return { - dispose: () => { - this._provider.delete(scheme); - } - }; - } - - // --- stat - - private _withProvider(resource: URI): Promise { - if (!resources.isAbsolutePath(resource)) { - throw new FileOperationError( - localize('invalidPath', "The path of resource '{0}' must be absolute", resource.toString(true)), - FileOperationResult.FILE_INVALID_PATH - ); - } - - return Promise.all([ - this.fileService.activateProvider(resource.scheme) - ]).then(() => { - const provider = this._provider.get(resource.scheme); - if (!provider) { - const err = new Error(); - err.name = 'ENOPRO'; - err.message = `no provider for ${resource.toString()}`; - throw err; - } - return provider; - }); - } - - // --- resolve - - resolveContent(resource: URI, options?: IResolveContentOptions): Promise { - if (resource.scheme === Schemas.file) { - return super.resolveContent(resource, options); - } else { - return this._readFile(resource, options).then(LegacyRemoteFileService._asContent); - } - } - - resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise { - if (resource.scheme === Schemas.file) { - return super.resolveStreamContent(resource, options); - } else { - return this._readFile(resource, options); - } - } - - private _readFile(resource: URI, options: IResolveContentOptions = Object.create(null)): Promise { - return this._withProvider(resource).then(provider => { - - return this.fileService.resolve(resource).then(fileStat => { - - if (fileStat.isDirectory) { - // todo@joh cannot copy a folder - // https://github.com/Microsoft/vscode/issues/41547 - throw new FileOperationError( - localize('fileIsDirectoryError', "File is directory"), - FileOperationResult.FILE_IS_DIRECTORY, - options - ); - } - if (typeof options.etag === 'string' && fileStat.etag === options.etag) { - throw new FileOperationError( - localize('fileNotModifiedError', "File not modified since"), - FileOperationResult.FILE_NOT_MODIFIED_SINCE, - options - ); - } - - const decodeStreamOpts: IDecodeStreamOptions = { - guessEncoding: options.autoGuessEncoding, - overwriteEncoding: detected => { - return this.encoding.getReadEncoding(resource, options, { encoding: detected, seemsBinary: false }); - } - }; - - const readable = createReadableOfProvider(provider, resource, options.position || 0); - - return toDecodeStream(readable, decodeStreamOpts).then(data => { - - if (options.acceptTextOnly && data.detected.seemsBinary) { - return Promise.reject(new FileOperationError( - localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), - FileOperationResult.FILE_IS_BINARY, - options - )); - } - - return { - encoding: data.detected.encoding, - value: data.stream, - resource: fileStat.resource, - name: fileStat.name, - etag: fileStat.etag, - mtime: fileStat.mtime, - isReadonly: fileStat.isReadonly, - size: fileStat.size - }; - }); - }); - }); - } - - // --- saving - - private static _asContent(content: IStreamContent): Promise { - return new Promise((resolve, reject) => { - let result: IContent = { - value: '', - encoding: content.encoding, - etag: content.etag, - size: content.size, - mtime: content.mtime, - name: content.name, - resource: content.resource, - isReadonly: content.isReadonly - }; - content.value.on('data', chunk => result.value += chunk); - content.value.on('error', reject); - content.value.on('end', () => resolve(result)); - }); - } -} - -registerSingleton(ILegacyFileService, LegacyRemoteFileService); \ No newline at end of file diff --git a/src/vs/workbench/services/files/node/streams.ts b/src/vs/workbench/services/files/node/streams.ts deleted file mode 100644 index 664a94cb53d..00000000000 --- a/src/vs/workbench/services/files/node/streams.ts +++ /dev/null @@ -1,77 +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 { Readable } from 'stream'; -import { URI } from 'vs/base/common/uri'; -import { IFileSystemProvider, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; -import { illegalArgument } from 'vs/base/common/errors'; - -export function createReadableOfProvider(provider: IFileSystemProvider, resource: URI, position: number): Readable { - if (provider.capabilities & FileSystemProviderCapabilities.FileOpenReadWriteClose) { - return createReadable(provider, resource, position); - } else if (provider.capabilities & FileSystemProviderCapabilities.FileReadWrite) { - return createSimpleReadable(provider, resource, position); - } else { - throw illegalArgument(); - } -} - -function createReadable(provider: IFileSystemProvider, resource: URI, position: number): Readable { - return new class extends Readable { - _fd: number; - _pos: number = position; - _reading: boolean = false; - - async _read(size: number = 2 ** 10) { - if (this._reading) { - return; - } - this._reading = true; - try { - if (typeof this._fd !== 'number') { - this._fd = await provider.open!(resource, { create: false }); - } - while (this._reading) { - let buffer = Buffer.allocUnsafe(size); - let bytesRead = await provider.read!(this._fd, this._pos, buffer, 0, buffer.length); - if (bytesRead === 0) { - await provider.close!(this._fd); - this._reading = false; - this.push(null); - } else { - this._reading = this.push(buffer.slice(0, bytesRead)); - this._pos += bytesRead; - } - } - } catch (err) { - // - this.emit('error', err); - } - } - _destroy(_err: any, callback: (err?: any) => any) { - if (typeof this._fd === 'number') { - provider.close!(this._fd).then(callback, callback); - } - } - }; -} - -function createSimpleReadable(provider: IFileSystemProvider, resource: URI, position: number): Readable { - return new class extends Readable { - _readOperation: Promise; - _read(size?: number): void { - if (this._readOperation) { - return; - } - this._readOperation = provider.readFile!(resource).then(data => { - this.push(Buffer.from(data.buffer, data.byteOffset, data.byteLength).slice(position)); - this.push(null); - }, err => { - this.emit('error', err); - this.push(null); - }); - } - }; -} \ No newline at end of file diff --git a/src/vs/workbench/services/files2/node/watcher/nodejs/watcherService.ts b/src/vs/workbench/services/files/node/watcher/nodejs/watcherService.ts similarity index 98% rename from src/vs/workbench/services/files2/node/watcher/nodejs/watcherService.ts rename to src/vs/workbench/services/files/node/watcher/nodejs/watcherService.ts index 0afbd0f8d89..d3fe8552046 100644 --- a/src/vs/workbench/services/files2/node/watcher/nodejs/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nodejs/watcherService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDiskFileChange, normalizeFileChanges } from 'vs/workbench/services/files2/node/watcher/watcher'; +import { IDiskFileChange, normalizeFileChanges } from 'vs/workbench/services/files/node/watcher/watcher'; import { Disposable } from 'vs/base/common/lifecycle'; import { statLink, readlink } from 'vs/base/node/pfs'; import { watchFolder, watchFile, CHANGE_BUFFER_DELAY } from 'vs/base/node/watcher'; diff --git a/src/vs/workbench/services/files2/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts similarity index 98% rename from src/vs/workbench/services/files2/node/watcher/nsfw/nsfwWatcherService.ts rename to src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts index 1ae9b569bd1..0fd504f5ffb 100644 --- a/src/vs/workbench/services/files2/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -7,9 +7,9 @@ import * as glob from 'vs/base/common/glob'; import * as extpath from 'vs/base/common/extpath'; import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; -import { IDiskFileChange, normalizeFileChanges } from 'vs/workbench/services/files2/node/watcher/watcher'; +import { IDiskFileChange, normalizeFileChanges } from 'vs/workbench/services/files/node/watcher/watcher'; import * as nsfw from 'vscode-nsfw'; -import { IWatcherService, IWatcherRequest, IWatcherOptions, IWatchError } from 'vs/workbench/services/files2/node/watcher/nsfw/watcher'; +import { IWatcherService, IWatcherRequest, IWatcherOptions, IWatchError } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; import { ThrottledDelayer } from 'vs/base/common/async'; import { FileChangeType } from 'vs/platform/files/common/files'; import { normalizeNFC } from 'vs/base/common/normalization'; diff --git a/src/vs/workbench/services/files2/node/watcher/nsfw/test/nsfwWatcherService.test.ts b/src/vs/workbench/services/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts similarity index 92% rename from src/vs/workbench/services/files2/node/watcher/nsfw/test/nsfwWatcherService.test.ts rename to src/vs/workbench/services/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts index 0e269a96be8..9350b8b9d68 100644 --- a/src/vs/workbench/services/files2/node/watcher/nsfw/test/nsfwWatcherService.test.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts @@ -6,8 +6,8 @@ import * as assert from 'assert'; import * as platform from 'vs/base/common/platform'; -import { NsfwWatcherService } from 'vs/workbench/services/files2/node/watcher/nsfw/nsfwWatcherService'; -import { IWatcherRequest } from 'vs/workbench/services/files2/node/watcher/nsfw/watcher'; +import { NsfwWatcherService } from 'vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService'; +import { IWatcherRequest } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; class TestNsfwWatcherService extends NsfwWatcherService { public normalizeRoots(roots: string[]): string[] { diff --git a/src/vs/workbench/services/files2/node/watcher/unix/watcher.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts similarity index 90% rename from src/vs/workbench/services/files2/node/watcher/unix/watcher.ts rename to src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts index f6c4544efa0..3e1fb0fdb78 100644 --- a/src/vs/workbench/services/files2/node/watcher/unix/watcher.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcher.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IDiskFileChange } from 'vs/workbench/services/files2/node/watcher/watcher'; +import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; export interface IWatcherRequest { path: string; diff --git a/src/vs/workbench/services/files2/node/watcher/nsfw/watcherApp.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherApp.ts similarity index 74% rename from src/vs/workbench/services/files2/node/watcher/nsfw/watcherApp.ts rename to src/vs/workbench/services/files/node/watcher/nsfw/watcherApp.ts index f2bfc17aec4..49c892fb588 100644 --- a/src/vs/workbench/services/files2/node/watcher/nsfw/watcherApp.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherApp.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; -import { WatcherChannel } from 'vs/workbench/services/files2/node/watcher/nsfw/watcherIpc'; -import { NsfwWatcherService } from 'vs/workbench/services/files2/node/watcher/nsfw/nsfwWatcherService'; +import { WatcherChannel } from 'vs/workbench/services/files/node/watcher/nsfw/watcherIpc'; +import { NsfwWatcherService } from 'vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService'; const server = new Server('watcher'); const service = new NsfwWatcherService(); diff --git a/src/vs/workbench/services/files2/node/watcher/unix/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts similarity index 95% rename from src/vs/workbench/services/files2/node/watcher/unix/watcherIpc.ts rename to src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts index 8bcf2e1a890..c073759b1d2 100644 --- a/src/vs/workbench/services/files2/node/watcher/unix/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts @@ -6,7 +6,7 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from './watcher'; import { Event } from 'vs/base/common/event'; -import { IDiskFileChange } from 'vs/workbench/services/files2/node/watcher/watcher'; +import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; export class WatcherChannel implements IServerChannel { diff --git a/src/vs/workbench/services/files2/node/watcher/nsfw/watcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts similarity index 92% rename from src/vs/workbench/services/files2/node/watcher/nsfw/watcherService.ts rename to src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts index c713fcfcd9b..8924e1216c0 100644 --- a/src/vs/workbench/services/files2/node/watcher/nsfw/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts @@ -5,11 +5,11 @@ import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; -import { IDiskFileChange } from 'vs/workbench/services/files2/node/watcher/watcher'; -import { WatcherChannelClient } from 'vs/workbench/services/files2/node/watcher/nsfw/watcherIpc'; +import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; +import { WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/nsfw/watcherIpc'; import { Disposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; -import { IWatchError, IWatcherRequest } from 'vs/workbench/services/files2/node/watcher/nsfw/watcher'; +import { IWatchError, IWatcherRequest } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; import { getPathFromAmdModule } from 'vs/base/common/amd'; export class FileWatcher extends Disposable { @@ -40,7 +40,7 @@ export class FileWatcher extends Disposable { serverName: 'File Watcher (nsfw)', args: ['--type=watcherService'], env: { - AMD_ENTRYPOINT: 'vs/workbench/services/files2/node/watcher/nsfw/watcherApp', + AMD_ENTRYPOINT: 'vs/workbench/services/files/node/watcher/nsfw/watcherApp', PIPE_LOGGING: 'true', VERBOSE_LOGGING: this.verboseLogging } diff --git a/src/vs/workbench/services/files2/node/watcher/unix/chokidarWatcherService.ts b/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts similarity index 95% rename from src/vs/workbench/services/files2/node/watcher/unix/chokidarWatcherService.ts rename to src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts index 434f98b7218..56fdbb394f4 100644 --- a/src/vs/workbench/services/files2/node/watcher/unix/chokidarWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts @@ -13,9 +13,9 @@ import { FileChangeType } from 'vs/platform/files/common/files'; import { ThrottledDelayer } from 'vs/base/common/async'; import { normalizeNFC } from 'vs/base/common/normalization'; import { realcaseSync } from 'vs/base/node/extpath'; -import { isMacintosh } from 'vs/base/common/platform'; -import { IDiskFileChange, normalizeFileChanges } from 'vs/workbench/services/files2/node/watcher/watcher'; -import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from 'vs/workbench/services/files2/node/watcher/unix/watcher'; +import { isMacintosh, isLinux } from 'vs/base/common/platform'; +import { IDiskFileChange, normalizeFileChanges } from 'vs/workbench/services/files/node/watcher/watcher'; +import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from 'vs/workbench/services/files/node/watcher/unix/watcher'; import { Emitter, Event } from 'vs/base/common/event'; interface IWatcher { @@ -114,12 +114,21 @@ export class ChokidarWatcherService implements IWatcherService { disableGlobbing: true // fix https://github.com/Microsoft/vscode/issues/4586 }; + const excludes: string[] = []; // if there's only one request, use the built-in ignore-filterering const isSingleFolder = requests.length === 1; if (isSingleFolder) { - watcherOpts.ignored = requests[0].excludes; + excludes.push(...requests[0].excludes); } + if ((isMacintosh || isLinux) && (basePath.length === 0 || basePath === '/')) { + excludes.push('/dev/**'); + if (isLinux) { + excludes.push('/proc/**', '/sys/**'); + } + } + watcherOpts.ignored = excludes; + // Chokidar fails when the basePath does not match case-identical to the path on disk // so we have to find the real casing of the path and do some path massaging to fix this // see https://github.com/paulmillr/chokidar/issues/418 diff --git a/src/vs/workbench/services/files2/node/watcher/unix/test/chockidarWatcherService.test.ts b/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts similarity index 99% rename from src/vs/workbench/services/files2/node/watcher/unix/test/chockidarWatcherService.test.ts rename to src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts index cfb91f003f4..cb79e1af37e 100644 --- a/src/vs/workbench/services/files2/node/watcher/unix/test/chockidarWatcherService.test.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts @@ -11,7 +11,7 @@ import { normalizeRoots, ChokidarWatcherService } from '../chokidarWatcherServic import { IWatcherRequest } from '../watcher'; import * as platform from 'vs/base/common/platform'; import { Delayer } from 'vs/base/common/async'; -import { IDiskFileChange } from 'vs/workbench/services/files2/node/watcher/watcher'; +import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; import { FileChangeType } from 'vs/platform/files/common/files'; function newRequest(basePath: string, ignored: string[] = []): IWatcherRequest { diff --git a/src/vs/workbench/services/files2/node/watcher/nsfw/watcher.ts b/src/vs/workbench/services/files/node/watcher/unix/watcher.ts similarity index 90% rename from src/vs/workbench/services/files2/node/watcher/nsfw/watcher.ts rename to src/vs/workbench/services/files/node/watcher/unix/watcher.ts index f6c4544efa0..3e1fb0fdb78 100644 --- a/src/vs/workbench/services/files2/node/watcher/nsfw/watcher.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcher.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IDiskFileChange } from 'vs/workbench/services/files2/node/watcher/watcher'; +import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; export interface IWatcherRequest { path: string; diff --git a/src/vs/workbench/services/files2/node/watcher/unix/watcherApp.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherApp.ts similarity index 82% rename from src/vs/workbench/services/files2/node/watcher/unix/watcherApp.ts rename to src/vs/workbench/services/files/node/watcher/unix/watcherApp.ts index 1f20665820e..01473fb5c88 100644 --- a/src/vs/workbench/services/files2/node/watcher/unix/watcherApp.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherApp.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; -import { WatcherChannel } from 'vs/workbench/services/files2/node/watcher/unix/watcherIpc'; -import { ChokidarWatcherService } from 'vs/workbench/services/files2/node/watcher/unix/chokidarWatcherService'; +import { WatcherChannel } from 'vs/workbench/services/files/node/watcher/unix/watcherIpc'; +import { ChokidarWatcherService } from 'vs/workbench/services/files/node/watcher/unix/chokidarWatcherService'; const server = new Server('watcher'); const service = new ChokidarWatcherService(); diff --git a/src/vs/workbench/services/files2/node/watcher/nsfw/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts similarity index 95% rename from src/vs/workbench/services/files2/node/watcher/nsfw/watcherIpc.ts rename to src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts index 8bcf2e1a890..c073759b1d2 100644 --- a/src/vs/workbench/services/files2/node/watcher/nsfw/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts @@ -6,7 +6,7 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from './watcher'; import { Event } from 'vs/base/common/event'; -import { IDiskFileChange } from 'vs/workbench/services/files2/node/watcher/watcher'; +import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; export class WatcherChannel implements IServerChannel { diff --git a/src/vs/workbench/services/files2/node/watcher/unix/watcherService.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts similarity index 93% rename from src/vs/workbench/services/files2/node/watcher/unix/watcherService.ts rename to src/vs/workbench/services/files/node/watcher/unix/watcherService.ts index 5056e59cea0..04ce4808a85 100644 --- a/src/vs/workbench/services/files2/node/watcher/unix/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts @@ -5,11 +5,11 @@ import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; -import { IDiskFileChange } from 'vs/workbench/services/files2/node/watcher/watcher'; -import { WatcherChannelClient } from 'vs/workbench/services/files2/node/watcher/unix/watcherIpc'; +import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; +import { WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/unix/watcherIpc'; import { Disposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; -import { IWatchError, IWatcherRequest } from 'vs/workbench/services/files2/node/watcher/unix/watcher'; +import { IWatchError, IWatcherRequest } from 'vs/workbench/services/files/node/watcher/unix/watcher'; import { getPathFromAmdModule } from 'vs/base/common/amd'; export class FileWatcher extends Disposable { @@ -40,7 +40,7 @@ export class FileWatcher extends Disposable { serverName: 'File Watcher (chokidar)', args: ['--type=watcherService'], env: { - AMD_ENTRYPOINT: 'vs/workbench/services/files2/node/watcher/unix/watcherApp', + AMD_ENTRYPOINT: 'vs/workbench/services/files/node/watcher/unix/watcherApp', PIPE_LOGGING: 'true', VERBOSE_LOGGING: this.verboseLogging } diff --git a/src/vs/workbench/services/files2/node/watcher/watcher.ts b/src/vs/workbench/services/files/node/watcher/watcher.ts similarity index 100% rename from src/vs/workbench/services/files2/node/watcher/watcher.ts rename to src/vs/workbench/services/files/node/watcher/watcher.ts diff --git a/src/vs/workbench/services/files2/node/watcher/win32/CodeHelper.exe b/src/vs/workbench/services/files/node/watcher/win32/CodeHelper.exe similarity index 100% rename from src/vs/workbench/services/files2/node/watcher/win32/CodeHelper.exe rename to src/vs/workbench/services/files/node/watcher/win32/CodeHelper.exe diff --git a/src/vs/workbench/services/files2/node/watcher/win32/CodeHelper.md b/src/vs/workbench/services/files/node/watcher/win32/CodeHelper.md similarity index 100% rename from src/vs/workbench/services/files2/node/watcher/win32/CodeHelper.md rename to src/vs/workbench/services/files/node/watcher/win32/CodeHelper.md diff --git a/src/vs/workbench/services/files2/node/watcher/win32/csharpWatcherService.ts b/src/vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts similarity index 96% rename from src/vs/workbench/services/files2/node/watcher/win32/csharpWatcherService.ts rename to src/vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts index cfd9ac6e892..95d8797a5c6 100644 --- a/src/vs/workbench/services/files2/node/watcher/win32/csharpWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts @@ -7,7 +7,7 @@ import * as cp from 'child_process'; import { FileChangeType } from 'vs/platform/files/common/files'; import * as decoder from 'vs/base/node/decoder'; import * as glob from 'vs/base/common/glob'; -import { IDiskFileChange } from 'vs/workbench/services/files2/node/watcher/watcher'; +import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; import { getPathFromAmdModule } from 'vs/base/common/amd'; export class OutOfProcessWin32FolderWatcher { @@ -50,7 +50,7 @@ export class OutOfProcessWin32FolderWatcher { args.push('-verbose'); } - this.handle = cp.spawn(getPathFromAmdModule(require, 'vs/workbench/services/files2/node/watcher/win32/CodeHelper.exe'), args); + this.handle = cp.spawn(getPathFromAmdModule(require, 'vs/workbench/services/files/node/watcher/win32/CodeHelper.exe'), args); const stdoutLineDecoder = new decoder.LineDecoder(); diff --git a/src/vs/workbench/services/files2/node/watcher/win32/watcherService.ts b/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts similarity index 93% rename from src/vs/workbench/services/files2/node/watcher/win32/watcherService.ts rename to src/vs/workbench/services/files/node/watcher/win32/watcherService.ts index 56e6c6d4c8c..0d27d68313a 100644 --- a/src/vs/workbench/services/files2/node/watcher/win32/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDiskFileChange } from 'vs/workbench/services/files2/node/watcher/watcher'; -import { OutOfProcessWin32FolderWatcher } from 'vs/workbench/services/files2/node/watcher/win32/csharpWatcherService'; +import { IDiskFileChange } from 'vs/workbench/services/files/node/watcher/watcher'; +import { OutOfProcessWin32FolderWatcher } from 'vs/workbench/services/files/node/watcher/win32/csharpWatcherService'; import { posix } from 'vs/base/common/path'; import { rtrim, endsWith } from 'vs/base/common/strings'; import { Disposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/workbench/services/files2/test/browser/fileService2.test.ts b/src/vs/workbench/services/files/test/browser/fileService.test.ts similarity index 94% rename from src/vs/workbench/services/files2/test/browser/fileService2.test.ts rename to src/vs/workbench/services/files/test/browser/fileService.test.ts index c39e0807ca7..01af38d61c9 100644 --- a/src/vs/workbench/services/files2/test/browser/fileService2.test.ts +++ b/src/vs/workbench/services/files/test/browser/fileService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { FileService } from 'vs/workbench/services/files/common/fileService'; import { URI } from 'vs/base/common/uri'; import { IFileSystemProviderRegistrationEvent, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -12,10 +12,10 @@ import { NullFileSystemProvider } from 'vs/workbench/test/workbenchTestServices' import { NullLogService } from 'vs/platform/log/common/log'; import { timeout } from 'vs/base/common/async'; -suite('File Service 2', () => { +suite('File Service', () => { test('provider registration', async () => { - const service = new FileService2(new NullLogService()); + const service = new FileService(new NullLogService()); const resource = URI.parse('test://foo/bar'); assert.equal(service.canHandleResource(resource), false); @@ -64,7 +64,7 @@ suite('File Service 2', () => { }); test('watch', async () => { - const service = new FileService2(new NullLogService()); + const service = new FileService(new NullLogService()); let disposeCounter = 0; service.registerProvider('test', new NullFileSystemProvider(() => { diff --git a/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts b/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts deleted file mode 100644 index e7e86cb3945..00000000000 --- a/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts +++ /dev/null @@ -1,262 +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 fs from 'fs'; -import * as path from 'vs/base/common/path'; -import * as os from 'os'; -import * as assert from 'assert'; -import { LegacyFileService } from 'vs/workbench/services/files/node/fileService'; -import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; -import { URI as uri } from 'vs/base/common/uri'; -import * as uuid from 'vs/base/common/uuid'; -import * as pfs from 'vs/base/node/pfs'; -import * as encodingLib from 'vs/base/node/encoding'; -import { TestEnvironmentService, TestContextService, TestTextResourceConfigurationService } from 'vs/workbench/test/workbenchTestServices'; -import { getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; -import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { IEncodingOverride } from 'vs/workbench/services/files/node/encoding'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; -import { NullLogService } from 'vs/platform/log/common/log'; -import { Schemas } from 'vs/base/common/network'; -import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; - -suite('LegacyFileService', () => { - let service: LegacyFileService; - const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'fileservice'); - let testDir: string; - - setup(function () { - const id = uuid.generateUuid(); - testDir = path.join(parentDir, id); - const sourceDir = getPathFromAmdModule(require, './fixtures/service'); - - const fileService = new FileService2(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - - return pfs.copy(sourceDir, testDir).then(() => { - service = new LegacyFileService( - fileService, - new TestContextService(new Workspace(testDir, toWorkspaceFolders([{ path: testDir }]))), - TestEnvironmentService, - new TestTextResourceConfigurationService(), - ); - }); - }); - - teardown(() => { - service.dispose(); - return pfs.rimraf(parentDir, pfs.RimRafMode.MOVE); - }); - - test('resolveContent - large file', function () { - const resource = uri.file(path.join(testDir, 'lorem.txt')); - - return service.resolveContent(resource).then(c => { - assert.ok(c.value.length > 64000); - }); - }); - - test('resolveContent - Files are intermingled #38331', function () { - let resource1 = uri.file(path.join(testDir, 'lorem.txt')); - let resource2 = uri.file(path.join(testDir, 'some_utf16le.css')); - let value1: string; - let value2: string; - // load in sequence and keep data - return service.resolveContent(resource1).then(c => value1 = c.value).then(() => { - return service.resolveContent(resource2).then(c => value2 = c.value); - }).then(() => { - // load in parallel in expect the same result - return Promise.all([ - service.resolveContent(resource1).then(c => assert.equal(c.value, value1)), - service.resolveContent(resource2).then(c => assert.equal(c.value, value2)) - ]); - }); - }); - - test('resolveContent - FILE_IS_BINARY', function () { - const resource = uri.file(path.join(testDir, 'binary.txt')); - - return service.resolveContent(resource, { acceptTextOnly: true }).then(undefined, (e: FileOperationError) => { - assert.equal(e.fileOperationResult, FileOperationResult.FILE_IS_BINARY); - - return service.resolveContent(uri.file(path.join(testDir, 'small.txt')), { acceptTextOnly: true }).then(r => { - assert.equal(r.name, 'small.txt'); - }); - }); - }); - - test('resolveContent - FILE_IS_DIRECTORY', function () { - const resource = uri.file(path.join(testDir, 'deep')); - - return service.resolveContent(resource).then(undefined, (e: FileOperationError) => { - assert.equal(e.fileOperationResult, FileOperationResult.FILE_IS_DIRECTORY); - }); - }); - - test('resolveContent - FILE_NOT_FOUND', function () { - const resource = uri.file(path.join(testDir, '404.html')); - - return service.resolveContent(resource).then(undefined, (e: FileOperationError) => { - assert.equal(e.fileOperationResult, FileOperationResult.FILE_NOT_FOUND); - }); - }); - - test('resolveContent - FILE_NOT_MODIFIED_SINCE', function () { - const resource = uri.file(path.join(testDir, 'index.html')); - - return service.resolveContent(resource).then(c => { - return service.resolveContent(resource, { etag: c.etag }).then(undefined, (e: FileOperationError) => { - assert.equal(e.fileOperationResult, FileOperationResult.FILE_NOT_MODIFIED_SINCE); - }); - }); - }); - - // test('resolveContent - FILE_MODIFIED_SINCE', function () { - // const resource = uri.file(path.join(testDir, 'index.html')); - - // return service.resolveContent(resource).then(c => { - // fs.writeFileSync(resource.fsPath, 'Updates Incoming!'); - - // return service.updateContent(resource, c.value, { etag: c.etag, mtime: c.mtime - 1000 }).then(undefined, (e: FileOperationError) => { - // assert.equal(e.fileOperationResult, FileOperationResult.FILE_MODIFIED_SINCE); - // }); - // }); - // }); - - test('resolveContent - encoding picked up', function () { - const resource = uri.file(path.join(testDir, 'index.html')); - const encoding = 'windows1252'; - - return service.resolveContent(resource, { encoding: encoding }).then(c => { - assert.equal(c.encoding, encoding); - }); - }); - - test('resolveContent - user overrides BOM', function () { - const resource = uri.file(path.join(testDir, 'some_utf16le.css')); - - return service.resolveContent(resource, { encoding: 'windows1252' }).then(c => { - assert.equal(c.encoding, 'windows1252'); - }); - }); - - test('resolveContent - BOM removed', function () { - const resource = uri.file(path.join(testDir, 'some_utf8_bom.txt')); - - return service.resolveContent(resource).then(c => { - assert.equal(encodingLib.detectEncodingByBOMFromBuffer(Buffer.from(c.value), 512), null); - }); - }); - - test('resolveContent - invalid encoding', function () { - const resource = uri.file(path.join(testDir, 'index.html')); - - return service.resolveContent(resource, { encoding: 'superduper' }).then(c => { - assert.equal(c.encoding, 'utf8'); - }); - }); - - test('resolveContent - options - encoding override (parent)', function () { - - // setup - const _id = uuid.generateUuid(); - const _testDir = path.join(parentDir, _id); - const _sourceDir = getPathFromAmdModule(require, './fixtures/service'); - - return pfs.copy(_sourceDir, _testDir).then(() => { - const encodingOverride: IEncodingOverride[] = []; - encodingOverride.push({ - parent: uri.file(path.join(testDir, 'deep')), - encoding: 'utf16le' - }); - - const configurationService = new TestConfigurationService(); - configurationService.setUserConfiguration('files', { encoding: 'windows1252' }); - - const textResourceConfigurationService = new TestTextResourceConfigurationService(configurationService); - - const fileService = new FileService2(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - - const _service = new LegacyFileService( - fileService, - new TestContextService(new Workspace(_testDir, toWorkspaceFolders([{ path: _testDir }]))), - TestEnvironmentService, - textResourceConfigurationService, - { encodingOverride }); - - return _service.resolveContent(uri.file(path.join(testDir, 'index.html'))).then(c => { - assert.equal(c.encoding, 'windows1252'); - - return _service.resolveContent(uri.file(path.join(testDir, 'deep', 'conway.js'))).then(c => { - assert.equal(c.encoding, 'utf16le'); - - // teardown - _service.dispose(); - }); - }); - }); - }); - - test('resolveContent - options - encoding override (extension)', function () { - - // setup - const _id = uuid.generateUuid(); - const _testDir = path.join(parentDir, _id); - const _sourceDir = getPathFromAmdModule(require, './fixtures/service'); - - return pfs.copy(_sourceDir, _testDir).then(() => { - const encodingOverride: IEncodingOverride[] = []; - encodingOverride.push({ - extension: 'js', - encoding: 'utf16le' - }); - - const configurationService = new TestConfigurationService(); - configurationService.setUserConfiguration('files', { encoding: 'windows1252' }); - - const textResourceConfigurationService = new TestTextResourceConfigurationService(configurationService); - - const fileService = new FileService2(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - - const _service = new LegacyFileService( - fileService, - new TestContextService(new Workspace(_testDir, toWorkspaceFolders([{ path: _testDir }]))), - TestEnvironmentService, - textResourceConfigurationService, - { encodingOverride }); - - return _service.resolveContent(uri.file(path.join(testDir, 'index.html'))).then(c => { - assert.equal(c.encoding, 'windows1252'); - - return _service.resolveContent(uri.file(path.join(testDir, 'deep', 'conway.js'))).then(c => { - assert.equal(c.encoding, 'utf16le'); - - // teardown - _service.dispose(); - }); - }); - }); - }); - - test('resolveContent - from position (ASCII)', function () { - const resource = uri.file(path.join(testDir, 'small.txt')); - - return service.resolveContent(resource, { position: 6 }).then(content => { - assert.equal(content.value, 'File'); - }); - }); - - test('resolveContent - from position (with umlaut)', function () { - const resource = uri.file(path.join(testDir, 'small_umlaut.txt')); - - return service.resolveContent(resource, { position: Buffer.from('Small File with Ü').length }).then(content => { - assert.equal(content.value, 'mlaut'); - }); - }); -}); diff --git a/src/vs/workbench/services/files2/test/node/diskFileService.test.ts b/src/vs/workbench/services/files/test/node/diskFileService.test.ts similarity index 70% rename from src/vs/workbench/services/files2/test/node/diskFileService.test.ts rename to src/vs/workbench/services/files/test/node/diskFileService.test.ts index 5843fa18ebd..8068df82ed6 100644 --- a/src/vs/workbench/services/files2/test/node/diskFileService.test.ts +++ b/src/vs/workbench/services/files/test/node/diskFileService.test.ts @@ -5,9 +5,9 @@ import * as assert from 'assert'; import { tmpdir } from 'os'; -import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { FileService } from 'vs/workbench/services/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; -import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { generateUuid } from 'vs/base/common/uuid'; import { join, basename, dirname, posix } from 'vs/base/common/path'; @@ -15,7 +15,7 @@ import { getPathFromAmdModule } from 'vs/base/common/amd'; import { copy, rimraf, symlink, RimRafMode, rimrafSync } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { existsSync, statSync, readdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, mkdirSync } from 'fs'; -import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag } from 'vs/platform/files/common/files'; +import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities, FileChangeType, IFileChange, FileChangesEvent, FileOperationError, etag, IStat } from 'vs/platform/files/common/files'; import { NullLogService } from 'vs/platform/log/common/log'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -60,6 +60,10 @@ function toLineByLineReadable(content: string): VSBufferReadable { export class TestDiskFileSystemProvider extends DiskFileSystemProvider { + totalBytesRead: number = 0; + + private invalidStatSize: boolean; + private _testCapabilities: FileSystemProviderCapabilities; get capabilities(): FileSystemProviderCapabilities { if (!this._testCapabilities) { @@ -79,6 +83,36 @@ export class TestDiskFileSystemProvider extends DiskFileSystemProvider { set capabilities(capabilities: FileSystemProviderCapabilities) { this._testCapabilities = capabilities; } + + setInvalidStatSize(disabled: boolean): void { + this.invalidStatSize = disabled; + } + + async stat(resource: URI): Promise { + const res = await super.stat(resource); + + if (this.invalidStatSize) { + res.size = String(res.size) as any; // for https://github.com/Microsoft/vscode/issues/72909 + } + + return res; + } + + async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise { + const bytesRead = await super.read(fd, pos, data, offset, length); + + this.totalBytesRead += bytesRead; + + return bytesRead; + } + + async readFile(resource: URI): Promise { + const res = await super.readFile(resource); + + this.totalBytesRead += res.byteLength; + + return res; + } } suite('Disk File Service', () => { @@ -86,7 +120,7 @@ suite('Disk File Service', () => { const parentDir = getRandomTestPath(tmpdir(), 'vsctests', 'diskfileservice'); const testSchema = 'test'; - let service: FileService2; + let service: FileService; let fileProvider: TestDiskFileSystemProvider; let testProvider: TestDiskFileSystemProvider; let testDir: string; @@ -96,7 +130,7 @@ suite('Disk File Service', () => { setup(async () => { const logService = new NullLogService(); - service = new FileService2(logService); + service = new FileService(logService); disposables.push(service); fileProvider = new TestDiskFileSystemProvider(logService); @@ -337,7 +371,7 @@ suite('Disk File Service', () => { test('resolve - folder symbolic link', async () => { if (isWindows) { - return; // not happy + return; // not reliable on windows } const link = URI.file(join(testDir, 'deep-link')); @@ -351,7 +385,7 @@ suite('Disk File Service', () => { test('resolve - file symbolic link', async () => { if (isWindows) { - return; // not happy + return; // not reliable on windows } const link = URI.file(join(testDir, 'lorem.txt-linked')); @@ -364,7 +398,7 @@ suite('Disk File Service', () => { test('resolve - invalid symbolic link does not break', async () => { if (isWindows) { - return; // not happy + return; // not reliable on windows } const link = URI.file(join(testDir, 'foo')); @@ -800,6 +834,339 @@ suite('Disk File Service', () => { } }); + test('readFile - small file - buffered', () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + return testReadFile(URI.file(join(testDir, 'small.txt'))); + }); + + test('readFile - small file - buffered / readonly', () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.Readonly); + + return testReadFile(URI.file(join(testDir, 'small.txt'))); + }); + + test('readFile - small file - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + return testReadFile(URI.file(join(testDir, 'small.txt'))); + }); + + test('readFile - small file - unbuffered / readonly', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.Readonly); + + return testReadFile(URI.file(join(testDir, 'small.txt'))); + }); + + test('readFile - large file - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + return testReadFile(URI.file(join(testDir, 'lorem.txt'))); + }); + + test('readFile - large file - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + return testReadFile(URI.file(join(testDir, 'lorem.txt'))); + }); + + async function testReadFile(resource: URI): Promise { + const content = await service.readFile(resource); + + assert.equal(content.value.toString(), readFileSync(resource.fsPath)); + } + + test('readFile - Files are intermingled #38331 - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + let resource1 = URI.file(join(testDir, 'lorem.txt')); + let resource2 = URI.file(join(testDir, 'some_utf16le.css')); + + // load in sequence and keep data + const value1 = await service.readFile(resource1); + const value2 = await service.readFile(resource2); + + // load in parallel in expect the same result + const result = await Promise.all([ + service.readFile(resource1), + service.readFile(resource2) + ]); + + assert.equal(result[0].value.toString(), value1.value.toString()); + assert.equal(result[1].value.toString(), value2.value.toString()); + }); + + test('readFile - Files are intermingled #38331 - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + let resource1 = URI.file(join(testDir, 'lorem.txt')); + let resource2 = URI.file(join(testDir, 'some_utf16le.css')); + + // load in sequence and keep data + const value1 = await service.readFile(resource1); + const value2 = await service.readFile(resource2); + + // load in parallel in expect the same result + const result = await Promise.all([ + service.readFile(resource1), + service.readFile(resource2) + ]); + + assert.equal(result[0].value.toString(), value1.value.toString()); + assert.equal(result[1].value.toString(), value2.value.toString()); + }); + + test('readFile - from position (ASCII) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + const resource = URI.file(join(testDir, 'small.txt')); + + const contents = await service.readFile(resource, { position: 6 }); + + assert.equal(contents.value.toString(), 'File'); + }); + + test('readFile - from position (with umlaut) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + const resource = URI.file(join(testDir, 'small_umlaut.txt')); + + const contents = await service.readFile(resource, { position: Buffer.from('Small File with Ü').length }); + + assert.equal(contents.value.toString(), 'mlaut'); + }); + + test('readFile - from position (ASCII) - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + const resource = URI.file(join(testDir, 'small.txt')); + + const contents = await service.readFile(resource, { position: 6 }); + + assert.equal(contents.value.toString(), 'File'); + }); + + test('readFile - from position (with umlaut) - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + const resource = URI.file(join(testDir, 'small_umlaut.txt')); + + const contents = await service.readFile(resource, { position: Buffer.from('Small File with Ü').length }); + + assert.equal(contents.value.toString(), 'mlaut'); + }); + + + test('readFile - 3 bytes (ASCII) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + const resource = URI.file(join(testDir, 'small.txt')); + + const contents = await service.readFile(resource, { length: 3 }); + + assert.equal(contents.value.toString(), 'Sma'); + }); + + test('readFile - 3 bytes (ASCII) - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + const resource = URI.file(join(testDir, 'small.txt')); + + const contents = await service.readFile(resource, { length: 3 }); + + assert.equal(contents.value.toString(), 'Sma'); + }); + + test('readFile - 20000 bytes (large) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + const resource = URI.file(join(testDir, 'lorem.txt')); + + const contents = await service.readFile(resource, { length: 20000 }); + + assert.equal(contents.value.byteLength, 20000); + }); + + test('readFile - 20000 bytes (large) - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + const resource = URI.file(join(testDir, 'lorem.txt')); + + const contents = await service.readFile(resource, { length: 20000 }); + + assert.equal(contents.value.byteLength, 20000); + }); + + test('readFile - 80000 bytes (large) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + const resource = URI.file(join(testDir, 'lorem.txt')); + + const contents = await service.readFile(resource, { length: 80000 }); + + assert.equal(contents.value.byteLength, 80000); + }); + + test('readFile - 80000 bytes (large) - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + const resource = URI.file(join(testDir, 'lorem.txt')); + + const contents = await service.readFile(resource, { length: 80000 }); + + assert.equal(contents.value.byteLength, 80000); + }); + + test('readFile - FILE_IS_DIRECTORY', async () => { + const resource = URI.file(join(testDir, 'deep')); + + let error: FileOperationError | undefined = undefined; + try { + await service.readFile(resource); + } catch (err) { + error = err; + } + + assert.ok(error); + assert.equal(error!.fileOperationResult, FileOperationResult.FILE_IS_DIRECTORY); + }); + + test('readFile - FILE_NOT_FOUND', async () => { + const resource = URI.file(join(testDir, '404.html')); + + let error: FileOperationError | undefined = undefined; + try { + await service.readFile(resource); + } catch (err) { + error = err; + } + + assert.ok(error); + assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_FOUND); + }); + + test('readFile - FILE_NOT_MODIFIED_SINCE - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + const resource = URI.file(join(testDir, 'index.html')); + + const contents = await service.readFile(resource); + fileProvider.totalBytesRead = 0; + + let error: FileOperationError | undefined = undefined; + try { + await service.readFile(resource, { etag: contents.etag }); + } catch (err) { + error = err; + } + + assert.ok(error); + assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_MODIFIED_SINCE); + assert.equal(fileProvider.totalBytesRead, 0); + }); + + test('readFile - FILE_NOT_MODIFIED_SINCE does not fire wrongly - https://github.com/Microsoft/vscode/issues/72909', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + fileProvider.setInvalidStatSize(true); + + const resource = URI.file(join(testDir, 'index.html')); + + await service.readFile(resource); + + let error: FileOperationError | undefined = undefined; + try { + await service.readFile(resource, { etag: undefined }); + } catch (err) { + error = err; + } + + assert.ok(!error); + }); + + test('readFile - FILE_NOT_MODIFIED_SINCE - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + const resource = URI.file(join(testDir, 'index.html')); + + const contents = await service.readFile(resource); + fileProvider.totalBytesRead = 0; + + let error: FileOperationError | undefined = undefined; + try { + await service.readFile(resource, { etag: contents.etag }); + } catch (err) { + error = err; + } + + assert.ok(error); + assert.equal(error!.fileOperationResult, FileOperationResult.FILE_NOT_MODIFIED_SINCE); + assert.equal(fileProvider.totalBytesRead, 0); + }); + + test('readFile - FILE_EXCEED_MEMORY_LIMIT - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + const resource = URI.file(join(testDir, 'index.html')); + + let error: FileOperationError | undefined = undefined; + try { + await service.readFile(resource, { limits: { memory: 10 } }); + } catch (err) { + error = err; + } + + assert.ok(error); + assert.equal(error!.fileOperationResult, FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); + }); + + test('readFile - FILE_EXCEED_MEMORY_LIMIT - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + const resource = URI.file(join(testDir, 'index.html')); + + let error: FileOperationError | undefined = undefined; + try { + await service.readFile(resource, { limits: { memory: 10 } }); + } catch (err) { + error = err; + } + + assert.ok(error); + assert.equal(error!.fileOperationResult, FileOperationResult.FILE_EXCEED_MEMORY_LIMIT); + }); + + test('readFile - FILE_TOO_LARGE - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + const resource = URI.file(join(testDir, 'index.html')); + + let error: FileOperationError | undefined = undefined; + try { + await service.readFile(resource, { limits: { size: 10 } }); + } catch (err) { + error = err; + } + + assert.ok(error); + assert.equal(error!.fileOperationResult, FileOperationResult.FILE_TOO_LARGE); + }); + + test('readFile - FILE_TOO_LARGE - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + const resource = URI.file(join(testDir, 'index.html')); + + let error: FileOperationError | undefined = undefined; + try { + await service.readFile(resource, { limits: { size: 10 } }); + } catch (err) { + error = err; + } + + assert.ok(error); + assert.equal(error!.fileOperationResult, FileOperationResult.FILE_TOO_LARGE); + }); + test('createFile', async () => { let event: FileOperationEvent; disposables.push(service.onAfterOperation(e => event = e)); @@ -851,7 +1218,9 @@ suite('Disk File Service', () => { assert.equal(event!.target!.resource.fsPath, resource.fsPath); }); - test('writeFile', async () => { + test('writeFile - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + const resource = URI.file(join(testDir, 'small.txt')); const content = readFileSync(resource.fsPath); @@ -863,7 +1232,9 @@ suite('Disk File Service', () => { assert.equal(readFileSync(resource.fsPath), newContent); }); - test('writeFile (large file)', async () => { + test('writeFile (large file) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + const resource = URI.file(join(testDir, 'lorem.txt')); const content = readFileSync(resource.fsPath); @@ -875,6 +1246,74 @@ suite('Disk File Service', () => { assert.equal(readFileSync(resource.fsPath), newContent); }); + test('writeFile - buffered - readonly throws', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose | FileSystemProviderCapabilities.Readonly); + + const resource = URI.file(join(testDir, 'small.txt')); + + const content = readFileSync(resource.fsPath); + assert.equal(content, 'Small File'); + + const newContent = 'Updates to the small file'; + + let error: Error; + try { + await service.writeFile(resource, VSBuffer.fromString(newContent)); + } catch (err) { + error = err; + } + + assert.ok(error!); + }); + + test('writeFile - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + const resource = URI.file(join(testDir, 'small.txt')); + + const content = readFileSync(resource.fsPath); + assert.equal(content, 'Small File'); + + const newContent = 'Updates to the small file'; + await service.writeFile(resource, VSBuffer.fromString(newContent)); + + assert.equal(readFileSync(resource.fsPath), newContent); + }); + + test('writeFile (large file) - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + const resource = URI.file(join(testDir, 'lorem.txt')); + + const content = readFileSync(resource.fsPath); + const newContent = content.toString() + content.toString(); + + const fileStat = await service.writeFile(resource, VSBuffer.fromString(newContent)); + assert.equal(fileStat.name, 'lorem.txt'); + + assert.equal(readFileSync(resource.fsPath), newContent); + }); + + test('writeFile - unbuffered - readonly throws', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.Readonly); + + const resource = URI.file(join(testDir, 'small.txt')); + + const content = readFileSync(resource.fsPath); + assert.equal(content, 'Small File'); + + const newContent = 'Updates to the small file'; + + let error: Error; + try { + await service.writeFile(resource, VSBuffer.fromString(newContent)); + } catch (err) { + error = err; + } + + assert.ok(error!); + }); + test('writeFile (large file) - multiple parallel writes queue up', async () => { const resource = URI.file(join(testDir, 'lorem.txt')); @@ -890,7 +1329,9 @@ suite('Disk File Service', () => { assert.ok(['0', '00', '000', '0000', '00000'].some(offset => fileContent === offset + newContent)); }); - test('writeFile (readable)', async () => { + test('writeFile (readable) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + const resource = URI.file(join(testDir, 'small.txt')); const content = readFileSync(resource.fsPath); @@ -902,7 +1343,37 @@ suite('Disk File Service', () => { assert.equal(readFileSync(resource.fsPath), newContent); }); - test('writeFile (large file - readable)', async () => { + test('writeFile (large file - readable) - buffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileOpenReadWriteClose); + + const resource = URI.file(join(testDir, 'lorem.txt')); + + const content = readFileSync(resource.fsPath); + const newContent = content.toString() + content.toString(); + + const fileStat = await service.writeFile(resource, toLineByLineReadable(newContent)); + assert.equal(fileStat.name, 'lorem.txt'); + + assert.equal(readFileSync(resource.fsPath), newContent); + }); + + test('writeFile (readable) - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + + const resource = URI.file(join(testDir, 'small.txt')); + + const content = readFileSync(resource.fsPath); + assert.equal(content, 'Small File'); + + const newContent = 'Updates to the small file'; + await service.writeFile(resource, toLineByLineReadable(newContent)); + + assert.equal(readFileSync(resource.fsPath), newContent); + }); + + test('writeFile (large file - readable) - unbuffered', async () => { + setCapabilities(fileProvider, FileSystemProviderCapabilities.FileReadWrite); + const resource = URI.file(join(testDir, 'lorem.txt')); const content = readFileSync(resource.fsPath); @@ -951,20 +1422,25 @@ suite('Disk File Service', () => { assert.equal(readFileSync(resource.fsPath), newContent); }); - test('writeFile (error when writing to file that has been updated meanwhile)', async () => { + test('writeFile - error when writing to file that has been updated meanwhile', async () => { const resource = URI.file(join(testDir, 'small.txt')); const stat = await service.resolve(resource); - const content = readFileSync(resource.fsPath); + const content = readFileSync(resource.fsPath).toString(); assert.equal(content, 'Small File'); const newContent = 'Updates to the small file'; await service.writeFile(resource, VSBuffer.fromString(newContent), { etag: stat.etag, mtime: stat.mtime }); + const newContentLeadingToError = newContent + newContent; + + const fakeMtime = 1000; + const fakeSize = 1000; + let error: FileOperationError | undefined = undefined; try { - await service.writeFile(resource, VSBuffer.fromString(newContent), { etag: etag(0, 0), mtime: 0 }); + await service.writeFile(resource, VSBuffer.fromString(newContentLeadingToError), { etag: etag({ mtime: fakeMtime, size: fakeSize }), mtime: fakeMtime }); } catch (err) { error = err; } @@ -974,6 +1450,32 @@ suite('Disk File Service', () => { assert.equal(error!.fileOperationResult, FileOperationResult.FILE_MODIFIED_SINCE); }); + test('writeFile - no error when writing to file where size is the same', async () => { + const resource = URI.file(join(testDir, 'small.txt')); + + const stat = await service.resolve(resource); + + const content = readFileSync(resource.fsPath).toString(); + assert.equal(content, 'Small File'); + + const newContent = content; // same content + await service.writeFile(resource, VSBuffer.fromString(newContent), { etag: stat.etag, mtime: stat.mtime }); + + const newContentLeadingToNoError = newContent; // writing the same content should be OK + + const fakeMtime = 1000; + const actualSize = newContent.length; + + let error: FileOperationError | undefined = undefined; + try { + await service.writeFile(resource, VSBuffer.fromString(newContentLeadingToNoError), { etag: etag({ mtime: fakeMtime, size: actualSize }), mtime: fakeMtime }); + } catch (err) { + error = err; + } + + assert.ok(!error); + }); + test('watch - file', done => { const toWatch = URI.file(join(testDir, 'index-watch1.html')); writeFileSync(toWatch.fsPath, 'Init'); @@ -985,7 +1487,7 @@ suite('Disk File Service', () => { test('watch - file symbolic link', async done => { if (isWindows) { - return done(); // not happy + return done(); // watch tests are flaky on other platforms } const toWatch = URI.file(join(testDir, 'lorem.txt-linked')); @@ -997,6 +1499,10 @@ suite('Disk File Service', () => { }); test('watch - file - multiple writes', done => { + if (isWindows) { + return done(); // watch tests are flaky on other platforms + } + const toWatch = URI.file(join(testDir, 'index-watch1.html')); writeFileSync(toWatch.fsPath, 'Init'); @@ -1017,6 +1523,10 @@ suite('Disk File Service', () => { }); test('watch - file - rename file', done => { + if (isWindows) { + return done(); // watch tests are flaky on other platforms + } + const toWatch = URI.file(join(testDir, 'index-watch1.html')); const toWatchRenamed = URI.file(join(testDir, 'index-watch1-renamed.html')); writeFileSync(toWatch.fsPath, 'Init'); @@ -1057,6 +1567,10 @@ suite('Disk File Service', () => { }); test('watch - folder (non recursive) - change file', done => { + if (!isLinux) { + return done(); // watch tests are flaky on other platforms + } + const watchDir = URI.file(join(testDir, 'watch3')); mkdirSync(watchDir.fsPath); @@ -1069,6 +1583,10 @@ suite('Disk File Service', () => { }); test('watch - folder (non recursive) - add file', done => { + if (!isLinux) { + return done(); // watch tests are flaky on other platforms + } + const watchDir = URI.file(join(testDir, 'watch4')); mkdirSync(watchDir.fsPath); @@ -1080,6 +1598,10 @@ suite('Disk File Service', () => { }); test('watch - folder (non recursive) - delete file', done => { + if (!isLinux) { + return done(); // watch tests are flaky on other platforms + } + const watchDir = URI.file(join(testDir, 'watch5')); mkdirSync(watchDir.fsPath); @@ -1092,6 +1614,10 @@ suite('Disk File Service', () => { }); test('watch - folder (non recursive) - add folder', done => { + if (!isLinux) { + return done(); // watch tests are flaky on other platforms + } + const watchDir = URI.file(join(testDir, 'watch6')); mkdirSync(watchDir.fsPath); @@ -1103,6 +1629,10 @@ suite('Disk File Service', () => { }); test('watch - folder (non recursive) - delete folder', done => { + if (!isLinux) { + return done(); // watch tests are flaky on other platforms + } + const watchDir = URI.file(join(testDir, 'watch7')); mkdirSync(watchDir.fsPath); @@ -1115,8 +1645,8 @@ suite('Disk File Service', () => { }); test('watch - folder (non recursive) - symbolic link - change file', async done => { - if (isWindows) { - return done(); // not happy + if (!isLinux) { + return done(); // watch tests are flaky on other platforms } const watchDir = URI.file(join(testDir, 'deep-link')); @@ -1132,7 +1662,7 @@ suite('Disk File Service', () => { test('watch - folder (non recursive) - rename file', done => { if (!isLinux) { - return done(); // not happy + return done(); // watch tests are flaky on other platforms } const watchDir = URI.file(join(testDir, 'watch8')); @@ -1150,7 +1680,7 @@ suite('Disk File Service', () => { test('watch - folder (non recursive) - rename file (different case)', done => { if (!isLinux) { - return done(); // not happy + return done(); // watch tests are flaky on other platforms } const watchDir = URI.file(join(testDir, 'watch8')); diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/examples/company.js b/src/vs/workbench/services/files/test/node/fixtures/resolver/examples/company.js similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/examples/company.js rename to src/vs/workbench/services/files/test/node/fixtures/resolver/examples/company.js diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/examples/conway.js b/src/vs/workbench/services/files/test/node/fixtures/resolver/examples/conway.js similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/examples/conway.js rename to src/vs/workbench/services/files/test/node/fixtures/resolver/examples/conway.js diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/examples/employee.js b/src/vs/workbench/services/files/test/node/fixtures/resolver/examples/employee.js similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/examples/employee.js rename to src/vs/workbench/services/files/test/node/fixtures/resolver/examples/employee.js diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/examples/small.js b/src/vs/workbench/services/files/test/node/fixtures/resolver/examples/small.js similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/examples/small.js rename to src/vs/workbench/services/files/test/node/fixtures/resolver/examples/small.js diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/index.html b/src/vs/workbench/services/files/test/node/fixtures/resolver/index.html similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/index.html rename to src/vs/workbench/services/files/test/node/fixtures/resolver/index.html diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/other/deep/company.js b/src/vs/workbench/services/files/test/node/fixtures/resolver/other/deep/company.js similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/other/deep/company.js rename to src/vs/workbench/services/files/test/node/fixtures/resolver/other/deep/company.js diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/other/deep/conway.js b/src/vs/workbench/services/files/test/node/fixtures/resolver/other/deep/conway.js similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/other/deep/conway.js rename to src/vs/workbench/services/files/test/node/fixtures/resolver/other/deep/conway.js diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/other/deep/employee.js b/src/vs/workbench/services/files/test/node/fixtures/resolver/other/deep/employee.js similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/other/deep/employee.js rename to src/vs/workbench/services/files/test/node/fixtures/resolver/other/deep/employee.js diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/other/deep/small.js b/src/vs/workbench/services/files/test/node/fixtures/resolver/other/deep/small.js similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/other/deep/small.js rename to src/vs/workbench/services/files/test/node/fixtures/resolver/other/deep/small.js diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/site.css b/src/vs/workbench/services/files/test/node/fixtures/resolver/site.css similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/resolver/site.css rename to src/vs/workbench/services/files/test/node/fixtures/resolver/site.css diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/service/binary.txt b/src/vs/workbench/services/files/test/node/fixtures/service/binary.txt similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/service/binary.txt rename to src/vs/workbench/services/files/test/node/fixtures/service/binary.txt diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/service/deep/company.js b/src/vs/workbench/services/files/test/node/fixtures/service/deep/company.js similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/service/deep/company.js rename to src/vs/workbench/services/files/test/node/fixtures/service/deep/company.js diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/service/deep/conway.js b/src/vs/workbench/services/files/test/node/fixtures/service/deep/conway.js similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/service/deep/conway.js rename to src/vs/workbench/services/files/test/node/fixtures/service/deep/conway.js diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/service/deep/employee.js b/src/vs/workbench/services/files/test/node/fixtures/service/deep/employee.js similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/service/deep/employee.js rename to src/vs/workbench/services/files/test/node/fixtures/service/deep/employee.js diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/service/deep/small.js b/src/vs/workbench/services/files/test/node/fixtures/service/deep/small.js similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/service/deep/small.js rename to src/vs/workbench/services/files/test/node/fixtures/service/deep/small.js diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/service/index.html b/src/vs/workbench/services/files/test/node/fixtures/service/index.html similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/service/index.html rename to src/vs/workbench/services/files/test/node/fixtures/service/index.html diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/service/lorem.txt b/src/vs/workbench/services/files/test/node/fixtures/service/lorem.txt similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/service/lorem.txt rename to src/vs/workbench/services/files/test/node/fixtures/service/lorem.txt diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/service/small.txt b/src/vs/workbench/services/files/test/node/fixtures/service/small.txt similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/service/small.txt rename to src/vs/workbench/services/files/test/node/fixtures/service/small.txt diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/service/small_umlaut.txt b/src/vs/workbench/services/files/test/node/fixtures/service/small_umlaut.txt similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/service/small_umlaut.txt rename to src/vs/workbench/services/files/test/node/fixtures/service/small_umlaut.txt diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/service/some_utf16le.css b/src/vs/workbench/services/files/test/node/fixtures/service/some_utf16le.css similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/service/some_utf16le.css rename to src/vs/workbench/services/files/test/node/fixtures/service/some_utf16le.css diff --git a/src/vs/workbench/services/files/test/electron-browser/fixtures/service/some_utf8_bom.txt b/src/vs/workbench/services/files/test/node/fixtures/service/some_utf8_bom.txt similarity index 100% rename from src/vs/workbench/services/files/test/electron-browser/fixtures/service/some_utf8_bom.txt rename to src/vs/workbench/services/files/test/node/fixtures/service/some_utf8_bom.txt diff --git a/src/vs/workbench/services/files2/test/node/normalizer.test.ts b/src/vs/workbench/services/files/test/node/normalizer.test.ts similarity index 99% rename from src/vs/workbench/services/files2/test/node/normalizer.test.ts rename to src/vs/workbench/services/files/test/node/normalizer.test.ts index e2b1a942220..d08747713aa 100644 --- a/src/vs/workbench/services/files2/test/node/normalizer.test.ts +++ b/src/vs/workbench/services/files/test/node/normalizer.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as platform from 'vs/base/common/platform'; import { FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { URI as uri } from 'vs/base/common/uri'; -import { IDiskFileChange, normalizeFileChanges, toFileChanges } from 'vs/workbench/services/files2/node/watcher/watcher'; +import { IDiskFileChange, normalizeFileChanges, toFileChanges } from 'vs/workbench/services/files/node/watcher/watcher'; import { Event, Emitter } from 'vs/base/common/event'; function toFileChangesEvent(changes: IDiskFileChange[]): FileChangesEvent { diff --git a/src/vs/workbench/services/files2/browser/fileService2.ts b/src/vs/workbench/services/files2/browser/fileService2.ts deleted file mode 100644 index 725b40098be..00000000000 --- a/src/vs/workbench/services/files2/browser/fileService2.ts +++ /dev/null @@ -1,78 +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 { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; -import { URI } from 'vs/base/common/uri'; -import { IResolveContentOptions, IStreamContent, IStringStream, IContent, IFileSystemProvider, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { basename } from 'vs/base/common/resources'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { localize } from 'vs/nls'; - -// TODO@ben temporary for testing only -export class FileService3 extends FileService2 { - - async resolveContent(resource: URI, options?: IResolveContentOptions): Promise { - return this.resolveStreamContent(resource, options).then(streamContent => { - return new Promise((resolve, reject) => { - - const result: IContent = { - resource: streamContent.resource, - name: streamContent.name, - mtime: streamContent.mtime, - etag: streamContent.etag, - encoding: streamContent.encoding, - isReadonly: streamContent.isReadonly, - size: streamContent.size, - value: '' - }; - - streamContent.value.on('data', chunk => result.value += chunk); - streamContent.value.on('error', err => reject(err)); - streamContent.value.on('end', () => resolve(result)); - - return result; - }); - }); - } - - async resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise { - const provider = await this.withProvider(resource); - if (provider && provider.readFile) { - const listeners: { [type: string]: any[]; } = Object.create(null); - const stringStream: IStringStream = { - on(event: string, callback: any): void { - listeners[event] = listeners[event] || []; - listeners[event].push(callback); - } - }; - const stat = await this.resolve(resource, { resolveMetadata: true }); - - const r: IStreamContent = { - mtime: stat.mtime, - size: stat.size, - etag: stat.etag, - value: stringStream, - resource: resource, - encoding: 'utf8', - name: basename(resource) - }; - - provider.readFile(resource).then((contents) => { - const str = VSBuffer.wrap(contents).toString(); - listeners['data'].forEach((listener) => listener(str)); - listeners['end'].forEach((listener) => listener()); - }); - - return r; - } - - return super.resolveStreamContent(resource, options); - } - - protected throwIfFileSystemIsReadonly(provider: IFileSystemProvider): IFileSystemProvider { - // we really do not want to allow for changes currently - throw new FileOperationError(localize('err.readonly', "Resource can not be modified."), FileOperationResult.FILE_PERMISSION_DENIED); - } -} \ No newline at end of file diff --git a/src/vs/workbench/services/files2/test/node/fixtures/resolver/examples/company.js b/src/vs/workbench/services/files2/test/node/fixtures/resolver/examples/company.js deleted file mode 100644 index b65b52ade69..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/resolver/examples/company.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; -/// -var Workforce; -(function (Workforce_1) { - var Company = (function () { - function Company() { - } - return Company; - })(); - (function (property, Workforce, IEmployee) { - if (property === undefined) { property = employees; } - if (IEmployee === undefined) { IEmployee = []; } - property; - calculateMonthlyExpenses(); - { - var result = 0; - for (var i = 0; i < employees.length; i++) { - result += employees[i].calculatePay(); - } - return result; - } - }); -})(Workforce || (Workforce = {})); diff --git a/src/vs/workbench/services/files2/test/node/fixtures/resolver/examples/conway.js b/src/vs/workbench/services/files2/test/node/fixtures/resolver/examples/conway.js deleted file mode 100644 index 96892a1f689..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/resolver/examples/conway.js +++ /dev/null @@ -1,117 +0,0 @@ -'use strict'; -var Conway; -(function (Conway) { - var Cell = (function () { - function Cell() { - } - return Cell; - })(); - (function (property, number, property, number, property, boolean) { - if (property === undefined) { property = row; } - if (property === undefined) { property = col; } - if (property === undefined) { property = live; } - }); - var GameOfLife = (function () { - function GameOfLife() { - } - return GameOfLife; - })(); - (function () { - property; - gridSize = 50; - property; - canvasSize = 600; - property; - lineColor = '#cdcdcd'; - property; - liveColor = '#666'; - property; - deadColor = '#eee'; - property; - initialLifeProbability = 0.5; - property; - animationRate = 60; - property; - cellSize = 0; - property; - context: ICanvasRenderingContext2D; - property; - world = createWorld(); - circleOfLife(); - function createWorld() { - return travelWorld(function (cell) { - cell.live = Math.random() < initialLifeProbability; - return cell; - }); - } - function circleOfLife() { - world = travelWorld(function (cell) { - cell = world[cell.row][cell.col]; - draw(cell); - return resolveNextGeneration(cell); - }); - setTimeout(function () { circleOfLife(); }, animationRate); - } - function resolveNextGeneration(cell) { - var count = countNeighbors(cell); - var newCell = new Cell(cell.row, cell.col, cell.live); - if (count < 2 || count > 3) - newCell.live = false; - else if (count == 3) - newCell.live = true; - return newCell; - } - function countNeighbors(cell) { - var neighbors = 0; - for (var row = -1; row <= 1; row++) { - for (var col = -1; col <= 1; col++) { - if (row == 0 && col == 0) - continue; - if (isAlive(cell.row + row, cell.col + col)) { - neighbors++; - } - } - } - return neighbors; - } - function isAlive(row, col) { - // todo - need to guard with worl[row] exists? - if (row < 0 || col < 0 || row >= gridSize || col >= gridSize) - return false; - return world[row][col].live; - } - function travelWorld(callback) { - var result = []; - for (var row = 0; row < gridSize; row++) { - var rowData = []; - for (var col = 0; col < gridSize; col++) { - rowData.push(callback(new Cell(row, col, false))); - } - result.push(rowData); - } - return result; - } - function draw(cell) { - if (context == null) - context = createDrawingContext(); - if (cellSize == 0) - cellSize = canvasSize / gridSize; - context.strokeStyle = lineColor; - context.strokeRect(cell.row * cellSize, cell.col * cellSize, cellSize, cellSize); - context.fillStyle = cell.live ? liveColor : deadColor; - context.fillRect(cell.row * cellSize, cell.col * cellSize, cellSize, cellSize); - } - function createDrawingContext() { - var canvas = document.getElementById('conway-canvas'); - if (canvas == null) { - canvas = document.createElement('canvas'); - canvas.id = "conway-canvas"; - canvas.width = canvasSize; - canvas.height = canvasSize; - document.body.appendChild(canvas); - } - return canvas.getContext('2d'); - } - }); -})(Conway || (Conway = {})); -var game = new Conway.GameOfLife(); diff --git a/src/vs/workbench/services/files2/test/node/fixtures/resolver/examples/employee.js b/src/vs/workbench/services/files2/test/node/fixtures/resolver/examples/employee.js deleted file mode 100644 index 69c58aa5c87..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/resolver/examples/employee.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; -var Workforce; -(function (Workforce) { - var Employee = (function () { - function Employee() { - } - return Employee; - })(); - (property); - name: string, property; - basepay: number; - implements; - IEmployee; - { - name; - basepay; - } - var SalesEmployee = (function () { - function SalesEmployee() { - } - return SalesEmployee; - })(); - (); - Employee(name, basepay); - { - function calculatePay() { - var multiplier = (document.getElementById("mult")), as = any, value; - return _super.calculatePay.call(this) * multiplier + bonus; - } - } - var employee = new Employee('Bob', 1000); - var salesEmployee = new SalesEmployee('Jim', 800, 400); - salesEmployee.calclatePay(); // error: No member 'calclatePay' on SalesEmployee -})(Workforce || (Workforce = {})); -extern; -var $; -var s = Workforce.salesEmployee.calculatePay(); -$('#results').text(s); diff --git a/src/vs/workbench/services/files2/test/node/fixtures/resolver/examples/small.js b/src/vs/workbench/services/files2/test/node/fixtures/resolver/examples/small.js deleted file mode 100644 index 2fb478319a5..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/resolver/examples/small.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; -var M; -(function (M) { - var C = (function () { - function C() { - } - return C; - })(); - (function (x, property, number) { - if (property === undefined) { property = w; } - var local = 1; - // unresolved symbol because x is local - //self.x++; - self.w--; // ok because w is a property - property; - f = function (y) { - return y + x + local + w + self.w; - }; - function sum(z) { - return z + f(z) + w + self.w; - } - }); -})(M || (M = {})); -var c = new M.C(12, 5); diff --git a/src/vs/workbench/services/files2/test/node/fixtures/resolver/index.html b/src/vs/workbench/services/files2/test/node/fixtures/resolver/index.html deleted file mode 100644 index bccd24d9272..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/resolver/index.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - Strada - - - - - - - - -

TypeScript

-
- - -
- - -
- -
Press 'run' to execute code...
-
...write your results into #results...
-
- - - diff --git a/src/vs/workbench/services/files2/test/node/fixtures/resolver/other/deep/company.js b/src/vs/workbench/services/files2/test/node/fixtures/resolver/other/deep/company.js deleted file mode 100644 index b65b52ade69..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/resolver/other/deep/company.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; -/// -var Workforce; -(function (Workforce_1) { - var Company = (function () { - function Company() { - } - return Company; - })(); - (function (property, Workforce, IEmployee) { - if (property === undefined) { property = employees; } - if (IEmployee === undefined) { IEmployee = []; } - property; - calculateMonthlyExpenses(); - { - var result = 0; - for (var i = 0; i < employees.length; i++) { - result += employees[i].calculatePay(); - } - return result; - } - }); -})(Workforce || (Workforce = {})); diff --git a/src/vs/workbench/services/files2/test/node/fixtures/resolver/other/deep/conway.js b/src/vs/workbench/services/files2/test/node/fixtures/resolver/other/deep/conway.js deleted file mode 100644 index 96892a1f689..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/resolver/other/deep/conway.js +++ /dev/null @@ -1,117 +0,0 @@ -'use strict'; -var Conway; -(function (Conway) { - var Cell = (function () { - function Cell() { - } - return Cell; - })(); - (function (property, number, property, number, property, boolean) { - if (property === undefined) { property = row; } - if (property === undefined) { property = col; } - if (property === undefined) { property = live; } - }); - var GameOfLife = (function () { - function GameOfLife() { - } - return GameOfLife; - })(); - (function () { - property; - gridSize = 50; - property; - canvasSize = 600; - property; - lineColor = '#cdcdcd'; - property; - liveColor = '#666'; - property; - deadColor = '#eee'; - property; - initialLifeProbability = 0.5; - property; - animationRate = 60; - property; - cellSize = 0; - property; - context: ICanvasRenderingContext2D; - property; - world = createWorld(); - circleOfLife(); - function createWorld() { - return travelWorld(function (cell) { - cell.live = Math.random() < initialLifeProbability; - return cell; - }); - } - function circleOfLife() { - world = travelWorld(function (cell) { - cell = world[cell.row][cell.col]; - draw(cell); - return resolveNextGeneration(cell); - }); - setTimeout(function () { circleOfLife(); }, animationRate); - } - function resolveNextGeneration(cell) { - var count = countNeighbors(cell); - var newCell = new Cell(cell.row, cell.col, cell.live); - if (count < 2 || count > 3) - newCell.live = false; - else if (count == 3) - newCell.live = true; - return newCell; - } - function countNeighbors(cell) { - var neighbors = 0; - for (var row = -1; row <= 1; row++) { - for (var col = -1; col <= 1; col++) { - if (row == 0 && col == 0) - continue; - if (isAlive(cell.row + row, cell.col + col)) { - neighbors++; - } - } - } - return neighbors; - } - function isAlive(row, col) { - // todo - need to guard with worl[row] exists? - if (row < 0 || col < 0 || row >= gridSize || col >= gridSize) - return false; - return world[row][col].live; - } - function travelWorld(callback) { - var result = []; - for (var row = 0; row < gridSize; row++) { - var rowData = []; - for (var col = 0; col < gridSize; col++) { - rowData.push(callback(new Cell(row, col, false))); - } - result.push(rowData); - } - return result; - } - function draw(cell) { - if (context == null) - context = createDrawingContext(); - if (cellSize == 0) - cellSize = canvasSize / gridSize; - context.strokeStyle = lineColor; - context.strokeRect(cell.row * cellSize, cell.col * cellSize, cellSize, cellSize); - context.fillStyle = cell.live ? liveColor : deadColor; - context.fillRect(cell.row * cellSize, cell.col * cellSize, cellSize, cellSize); - } - function createDrawingContext() { - var canvas = document.getElementById('conway-canvas'); - if (canvas == null) { - canvas = document.createElement('canvas'); - canvas.id = "conway-canvas"; - canvas.width = canvasSize; - canvas.height = canvasSize; - document.body.appendChild(canvas); - } - return canvas.getContext('2d'); - } - }); -})(Conway || (Conway = {})); -var game = new Conway.GameOfLife(); diff --git a/src/vs/workbench/services/files2/test/node/fixtures/resolver/other/deep/employee.js b/src/vs/workbench/services/files2/test/node/fixtures/resolver/other/deep/employee.js deleted file mode 100644 index 69c58aa5c87..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/resolver/other/deep/employee.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; -var Workforce; -(function (Workforce) { - var Employee = (function () { - function Employee() { - } - return Employee; - })(); - (property); - name: string, property; - basepay: number; - implements; - IEmployee; - { - name; - basepay; - } - var SalesEmployee = (function () { - function SalesEmployee() { - } - return SalesEmployee; - })(); - (); - Employee(name, basepay); - { - function calculatePay() { - var multiplier = (document.getElementById("mult")), as = any, value; - return _super.calculatePay.call(this) * multiplier + bonus; - } - } - var employee = new Employee('Bob', 1000); - var salesEmployee = new SalesEmployee('Jim', 800, 400); - salesEmployee.calclatePay(); // error: No member 'calclatePay' on SalesEmployee -})(Workforce || (Workforce = {})); -extern; -var $; -var s = Workforce.salesEmployee.calculatePay(); -$('#results').text(s); diff --git a/src/vs/workbench/services/files2/test/node/fixtures/resolver/other/deep/small.js b/src/vs/workbench/services/files2/test/node/fixtures/resolver/other/deep/small.js deleted file mode 100644 index 2fb478319a5..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/resolver/other/deep/small.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; -var M; -(function (M) { - var C = (function () { - function C() { - } - return C; - })(); - (function (x, property, number) { - if (property === undefined) { property = w; } - var local = 1; - // unresolved symbol because x is local - //self.x++; - self.w--; // ok because w is a property - property; - f = function (y) { - return y + x + local + w + self.w; - }; - function sum(z) { - return z + f(z) + w + self.w; - } - }); -})(M || (M = {})); -var c = new M.C(12, 5); diff --git a/src/vs/workbench/services/files2/test/node/fixtures/resolver/site.css b/src/vs/workbench/services/files2/test/node/fixtures/resolver/site.css deleted file mode 100644 index f7b51e752bb..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/resolver/site.css +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/*---------------------------------------------------------- -The base color for this template is #5c87b2. If you'd like -to use a different color start by replacing all instances of -#5c87b2 with your new color. -----------------------------------------------------------*/ -body -{ - background-color: #5c87b2; - font-size: .75em; - font-family: Segoe UI, Verdana, Helvetica, Sans-Serif; - margin: 8px; - padding: 0; - color: #696969; -} - -h1, h2, h3, h4, h5, h6 -{ - color: #000; - font-size: 40px; - margin: 0px; -} - -textarea -{ - font-family: Consolas -} - -#results -{ - margin-top: 2em; - margin-left: 2em; - color: black; - font-size: medium; -} - diff --git a/src/vs/workbench/services/files2/test/node/fixtures/service/deep/company.js b/src/vs/workbench/services/files2/test/node/fixtures/service/deep/company.js deleted file mode 100644 index b65b52ade69..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/service/deep/company.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; -/// -var Workforce; -(function (Workforce_1) { - var Company = (function () { - function Company() { - } - return Company; - })(); - (function (property, Workforce, IEmployee) { - if (property === undefined) { property = employees; } - if (IEmployee === undefined) { IEmployee = []; } - property; - calculateMonthlyExpenses(); - { - var result = 0; - for (var i = 0; i < employees.length; i++) { - result += employees[i].calculatePay(); - } - return result; - } - }); -})(Workforce || (Workforce = {})); diff --git a/src/vs/workbench/services/files2/test/node/fixtures/service/deep/conway.js b/src/vs/workbench/services/files2/test/node/fixtures/service/deep/conway.js deleted file mode 100644 index 96892a1f689..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/service/deep/conway.js +++ /dev/null @@ -1,117 +0,0 @@ -'use strict'; -var Conway; -(function (Conway) { - var Cell = (function () { - function Cell() { - } - return Cell; - })(); - (function (property, number, property, number, property, boolean) { - if (property === undefined) { property = row; } - if (property === undefined) { property = col; } - if (property === undefined) { property = live; } - }); - var GameOfLife = (function () { - function GameOfLife() { - } - return GameOfLife; - })(); - (function () { - property; - gridSize = 50; - property; - canvasSize = 600; - property; - lineColor = '#cdcdcd'; - property; - liveColor = '#666'; - property; - deadColor = '#eee'; - property; - initialLifeProbability = 0.5; - property; - animationRate = 60; - property; - cellSize = 0; - property; - context: ICanvasRenderingContext2D; - property; - world = createWorld(); - circleOfLife(); - function createWorld() { - return travelWorld(function (cell) { - cell.live = Math.random() < initialLifeProbability; - return cell; - }); - } - function circleOfLife() { - world = travelWorld(function (cell) { - cell = world[cell.row][cell.col]; - draw(cell); - return resolveNextGeneration(cell); - }); - setTimeout(function () { circleOfLife(); }, animationRate); - } - function resolveNextGeneration(cell) { - var count = countNeighbors(cell); - var newCell = new Cell(cell.row, cell.col, cell.live); - if (count < 2 || count > 3) - newCell.live = false; - else if (count == 3) - newCell.live = true; - return newCell; - } - function countNeighbors(cell) { - var neighbors = 0; - for (var row = -1; row <= 1; row++) { - for (var col = -1; col <= 1; col++) { - if (row == 0 && col == 0) - continue; - if (isAlive(cell.row + row, cell.col + col)) { - neighbors++; - } - } - } - return neighbors; - } - function isAlive(row, col) { - // todo - need to guard with worl[row] exists? - if (row < 0 || col < 0 || row >= gridSize || col >= gridSize) - return false; - return world[row][col].live; - } - function travelWorld(callback) { - var result = []; - for (var row = 0; row < gridSize; row++) { - var rowData = []; - for (var col = 0; col < gridSize; col++) { - rowData.push(callback(new Cell(row, col, false))); - } - result.push(rowData); - } - return result; - } - function draw(cell) { - if (context == null) - context = createDrawingContext(); - if (cellSize == 0) - cellSize = canvasSize / gridSize; - context.strokeStyle = lineColor; - context.strokeRect(cell.row * cellSize, cell.col * cellSize, cellSize, cellSize); - context.fillStyle = cell.live ? liveColor : deadColor; - context.fillRect(cell.row * cellSize, cell.col * cellSize, cellSize, cellSize); - } - function createDrawingContext() { - var canvas = document.getElementById('conway-canvas'); - if (canvas == null) { - canvas = document.createElement('canvas'); - canvas.id = "conway-canvas"; - canvas.width = canvasSize; - canvas.height = canvasSize; - document.body.appendChild(canvas); - } - return canvas.getContext('2d'); - } - }); -})(Conway || (Conway = {})); -var game = new Conway.GameOfLife(); diff --git a/src/vs/workbench/services/files2/test/node/fixtures/service/deep/employee.js b/src/vs/workbench/services/files2/test/node/fixtures/service/deep/employee.js deleted file mode 100644 index 69c58aa5c87..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/service/deep/employee.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; -var Workforce; -(function (Workforce) { - var Employee = (function () { - function Employee() { - } - return Employee; - })(); - (property); - name: string, property; - basepay: number; - implements; - IEmployee; - { - name; - basepay; - } - var SalesEmployee = (function () { - function SalesEmployee() { - } - return SalesEmployee; - })(); - (); - Employee(name, basepay); - { - function calculatePay() { - var multiplier = (document.getElementById("mult")), as = any, value; - return _super.calculatePay.call(this) * multiplier + bonus; - } - } - var employee = new Employee('Bob', 1000); - var salesEmployee = new SalesEmployee('Jim', 800, 400); - salesEmployee.calclatePay(); // error: No member 'calclatePay' on SalesEmployee -})(Workforce || (Workforce = {})); -extern; -var $; -var s = Workforce.salesEmployee.calculatePay(); -$('#results').text(s); diff --git a/src/vs/workbench/services/files2/test/node/fixtures/service/deep/small.js b/src/vs/workbench/services/files2/test/node/fixtures/service/deep/small.js deleted file mode 100644 index 2fb478319a5..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/service/deep/small.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; -var M; -(function (M) { - var C = (function () { - function C() { - } - return C; - })(); - (function (x, property, number) { - if (property === undefined) { property = w; } - var local = 1; - // unresolved symbol because x is local - //self.x++; - self.w--; // ok because w is a property - property; - f = function (y) { - return y + x + local + w + self.w; - }; - function sum(z) { - return z + f(z) + w + self.w; - } - }); -})(M || (M = {})); -var c = new M.C(12, 5); diff --git a/src/vs/workbench/services/files2/test/node/fixtures/service/index.html b/src/vs/workbench/services/files2/test/node/fixtures/service/index.html deleted file mode 100644 index bccd24d9272..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/service/index.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - Strada - - - - - - - - -

TypeScript

-
- - -
- - -
- -
Press 'run' to execute code...
-
...write your results into #results...
-
- - - diff --git a/src/vs/workbench/services/files2/test/node/fixtures/service/small.txt b/src/vs/workbench/services/files2/test/node/fixtures/service/small.txt deleted file mode 100644 index da2e8042fb4..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/service/small.txt +++ /dev/null @@ -1 +0,0 @@ -Small File \ No newline at end of file diff --git a/src/vs/workbench/services/files2/test/node/fixtures/service/small_umlaut.txt b/src/vs/workbench/services/files2/test/node/fixtures/service/small_umlaut.txt deleted file mode 100644 index a01c1626b30..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/service/small_umlaut.txt +++ /dev/null @@ -1 +0,0 @@ -Small File with Ümlaut \ No newline at end of file diff --git a/src/vs/workbench/services/files2/test/node/fixtures/service/some_utf16le.css b/src/vs/workbench/services/files2/test/node/fixtures/service/some_utf16le.css deleted file mode 100644 index aea04aa2cd1..00000000000 Binary files a/src/vs/workbench/services/files2/test/node/fixtures/service/some_utf16le.css and /dev/null differ diff --git a/src/vs/workbench/services/files2/test/node/fixtures/service/some_utf8_bom.txt b/src/vs/workbench/services/files2/test/node/fixtures/service/some_utf8_bom.txt deleted file mode 100644 index 36cdec0c88f..00000000000 --- a/src/vs/workbench/services/files2/test/node/fixtures/service/some_utf8_bom.txt +++ /dev/null @@ -1 +0,0 @@ -This is some UTF 8 with BOM file. \ No newline at end of file diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 74ef7b96867..3a1c48cc97e 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -958,7 +958,7 @@ export class HistoryService extends Disposable implements IHistoryService { getLastActiveFile(filterByScheme: string): URI | undefined { const history = this.getHistory(); for (const input of history) { - let resource: URI | null; + let resource: URI | undefined; if (input instanceof EditorInput) { resource = toResource(input, { filterByScheme }); } else { diff --git a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts index ffc63601409..33b5fedb99f 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts @@ -210,7 +210,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding private resolveModelReference(): Promise> { return this.fileService.exists(this.resource) .then(exists => { - const EOL = this.configurationService.getValue('files', { overrideIdentifier: 'json' })['eol']; + const EOL = this.configurationService.getValue<{}>('files', { overrideIdentifier: 'json' })['eol']; const result: Promise = exists ? Promise.resolve(null) : this.textFileService.write(this.resource, this.getEmptyContent(EOL), { encoding: 'utf8' }); return result.then(() => this.textModelResolverService.createModelReference(this.resource)); }); diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts index 0c3d16f07b2..85e921e9229 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -396,7 +396,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { const keybinding = item.keybinding; if (!keybinding) { // This might be a removal keybinding item in user settings => accept it - result[resultLen++] = new ResolvedKeybindingItem(null, item.command, item.commandArgs, when, isDefault); + result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault); } else { const resolvedKeybindings = this.resolveKeybinding(keybinding); for (const resolvedKeybinding of resolvedKeybindings) { @@ -415,7 +415,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { const parts = item.parts; if (parts.length === 0) { // This might be a removal keybinding item in user settings => accept it - result[resultLen++] = new ResolvedKeybindingItem(null, item.command, item.commandArgs, when, isDefault); + result[resultLen++] = new ResolvedKeybindingItem(undefined, item.command, item.commandArgs, when, isDefault); } else { const resolvedKeybindings = this._keyboardMapper.resolveUserBinding(parts); for (const resolvedKeybinding of resolvedKeybindings) { diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index 81b9251db5c..1c560bd3d57 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -32,19 +32,18 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { IWorkspaceContextService, Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { LegacyFileService } from 'vs/workbench/services/files/node/fileService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { TestBackupFileService, TestContextService, TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestLifecycleService, TestLogService, TestTextFileService, TestTextResourceConfigurationService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; -import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { TestBackupFileService, TestContextService, TestEditorGroupsService, TestEditorService, TestLifecycleService, TestLogService, TestTextFileService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; +import { FileService } from 'vs/workbench/services/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; -import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; interface Modifiers { metaKey?: boolean; @@ -82,14 +81,8 @@ suite('KeybindingsEditing', () => { instantiationService.stub(ILogService, new TestLogService()); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(instantiationService.get(IConfigurationService))); instantiationService.stub(IModelService, instantiationService.createInstance(ModelServiceImpl)); - const fileService = new FileService2(new NullLogService()); + const fileService = new FileService(new NullLogService()); fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - fileService.setLegacyService(new LegacyFileService( - fileService, - new TestContextService(new Workspace(testDir, toWorkspaceFolders([{ path: testDir }]))), - TestEnvironmentService, - new TestTextResourceConfigurationService(), - )); instantiationService.stub(IFileService, fileService); instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); @@ -281,7 +274,7 @@ suite('KeybindingsEditing', () => { parts.push(aSimpleKeybinding(chordPart)); } } - let keybinding = parts.length > 0 ? new USLayoutResolvedKeybinding(new ChordKeybinding(parts), OS) : null; + const keybinding = parts.length > 0 ? new USLayoutResolvedKeybinding(new ChordKeybinding(parts), OS) : undefined; return new ResolvedKeybindingItem(keybinding, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : undefined, isDefault === undefined ? true : isDefault); } diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 910cde97607..b4f9a8bd23c 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -160,7 +160,7 @@ export class LabelService implements ILabelService { } getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string { - if (!isWorkspaceIdentifier(workspace) && !isSingleFolderWorkspaceIdentifier(workspace)) { + if (IWorkspace.isIWorkspace(workspace)) { const identifier = toWorkspaceIdentifier(workspace); if (!identifier) { return ''; @@ -176,23 +176,27 @@ export class LabelService implements ILabelService { return this.appendWorkspaceSuffix(label, workspace); } - // Workspace: Untitled - if (isEqualOrParent(workspace.configPath, this.environmentService.untitledWorkspacesHome)) { - return localize('untitledWorkspace', "Untitled (Workspace)"); - } + if (isWorkspaceIdentifier(workspace)) { + // Workspace: Untitled + if (isEqualOrParent(workspace.configPath, this.environmentService.untitledWorkspacesHome)) { + return localize('untitledWorkspace', "Untitled (Workspace)"); + } - // Workspace: Saved - let filename = basename(workspace.configPath); - if (endsWith(filename, WORKSPACE_EXTENSION)) { - filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1); + // Workspace: Saved + let filename = basename(workspace.configPath); + if (endsWith(filename, WORKSPACE_EXTENSION)) { + filename = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1); + } + let label; + if (options && options.verbose) { + label = localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(joinPath(dirname(workspace.configPath), filename))); + } else { + label = localize('workspaceName', "{0} (Workspace)", filename); + } + return this.appendWorkspaceSuffix(label, workspace.configPath); } - let label; - if (options && options.verbose) { - label = localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(joinPath(dirname(workspace.configPath), filename))); - } else { - label = localize('workspaceName', "{0} (Workspace)", filename); - } - return this.appendWorkspaceSuffix(label, workspace.configPath); + return ''; + } getSeparator(scheme: string, authority?: string): '/' | '\\' { diff --git a/src/vs/workbench/services/output/common/outputChannelModel.ts b/src/vs/workbench/services/output/common/outputChannelModel.ts index 9f8f84fe8bb..007ead80d15 100644 --- a/src/vs/workbench/services/output/common/outputChannelModel.ts +++ b/src/vs/workbench/services/output/common/outputChannelModel.ts @@ -210,11 +210,11 @@ class FileOutputChannelModel extends AbstractFileOutputChannelModel implements I } loadModel(): Promise { - this.loadModelPromise = this.fileService.resolveContent(this.file, { position: this.startOffset, encoding: 'utf8' }) + this.loadModelPromise = this.fileService.readFile(this.file, { position: this.startOffset }) .then(content => { - this.endOffset = this.startOffset + this.getByteLength(content.value); + this.endOffset = this.startOffset + content.value.byteLength; this.etag = content.etag; - return this.createModel(content.value); + return this.createModel(content.value.toString()); }); return this.loadModelPromise; } @@ -233,12 +233,12 @@ class FileOutputChannelModel extends AbstractFileOutputChannelModel implements I protected updateModel(): void { if (this.model) { - this.fileService.resolveContent(this.file, { position: this.endOffset, encoding: 'utf8' }) + this.fileService.readFile(this.file, { position: this.endOffset }) .then(content => { this.etag = content.etag; if (content.value) { - this.endOffset = this.endOffset + this.getByteLength(content.value); - this.appendToModel(content.value); + this.endOffset = this.endOffset + content.value.byteLength; + this.appendToModel(content.value.toString()); } this.updateInProgress = false; }, () => this.updateInProgress = false); diff --git a/src/vs/workbench/services/output/node/outputChannelModelService.ts b/src/vs/workbench/services/output/node/outputChannelModelService.ts index 004e4860981..7f540132bce 100644 --- a/src/vs/workbench/services/output/node/outputChannelModelService.ts +++ b/src/vs/workbench/services/output/node/outputChannelModelService.ts @@ -117,8 +117,8 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement } private loadFile(): Promise { - return this.fileService.resolveContent(this.file, { position: this.startOffset, encoding: 'utf8' }) - .then(content => this.appendedMessage ? content.value + this.appendedMessage : content.value); + return this.fileService.readFile(this.file, { position: this.startOffset }) + .then(content => this.appendedMessage ? content.value + this.appendedMessage : content.value.toString()); } protected updateModel(): void { diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 6f002c8a35f..0ca6f7d17f4 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -21,7 +21,7 @@ import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -59,7 +59,6 @@ export class PreferencesService extends Disposable implements IPreferencesServic constructor( @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IFileService private readonly fileService: IFileService, @ITextFileService private readonly textFileService: ITextFileService, @IConfigurationService private readonly configurationService: IConfigurationService, @INotificationService private readonly notificationService: INotificationService, @@ -138,7 +137,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } if (this.defaultSettingsRawResource.toString() === uri.toString()) { - const defaultRawSettingsEditorModel = this.instantiationService.createInstance(DefaultRawSettingsEditorModel, this.getDefaultSettings(ConfigurationTarget.USER)); + const defaultRawSettingsEditorModel = this.instantiationService.createInstance(DefaultRawSettingsEditorModel, this.getDefaultSettings(ConfigurationTarget.USER_LOCAL)); const languageSelection = this.modeService.create('jsonc'); const model = this._register(this.modelService.createModel(defaultRawSettingsEditorModel.content, languageSelection, uri)); return Promise.resolve(model); @@ -160,7 +159,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } if (this.userSettingsResource.toString() === uri.toString()) { - return this.createEditableSettingsEditorModel(ConfigurationTarget.USER, uri); + return this.createEditableSettingsEditorModel(ConfigurationTarget.USER_LOCAL, uri); } const workspaceSettingsUri = this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE); @@ -210,8 +209,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic jsonEditor; return jsonEditor ? - this.openOrSwitchSettings(ConfigurationTarget.USER, this.userSettingsResource, options, group) : - this.openOrSwitchSettings2(ConfigurationTarget.USER, undefined, options, group); + this.openOrSwitchSettings(ConfigurationTarget.USER_LOCAL, this.userSettingsResource, options, group) : + this.openOrSwitchSettings2(ConfigurationTarget.USER_LOCAL, undefined, options, group); } async openRemoteSettings(): Promise { @@ -378,7 +377,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } public createSettings2EditorModel(): Settings2EditorModel { - return this.instantiationService.createInstance(Settings2EditorModel, this.getDefaultSettings(ConfigurationTarget.USER)); + return this.instantiationService.createInstance(Settings2EditorModel, this.getDefaultSettings(ConfigurationTarget.USER_LOCAL)); } private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Promise { @@ -420,7 +419,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic private getConfigurationTargetFromSettingsResource(resource: URI): ConfigurationTarget { if (this.userSettingsResource.toString() === resource.toString()) { - return ConfigurationTarget.USER; + return ConfigurationTarget.USER_LOCAL; } const workspaceSettingsResource = this.workspaceSettingsResource; @@ -433,11 +432,15 @@ export class PreferencesService extends Disposable implements IPreferencesServic return ConfigurationTarget.WORKSPACE_FOLDER; } - return ConfigurationTarget.USER; + return ConfigurationTarget.USER_LOCAL; } private getConfigurationTargetFromDefaultSettingsResource(uri: URI) { - return this.isDefaultWorkspaceSettingsResource(uri) ? ConfigurationTarget.WORKSPACE : this.isDefaultFolderSettingsResource(uri) ? ConfigurationTarget.WORKSPACE_FOLDER : ConfigurationTarget.USER; + return this.isDefaultWorkspaceSettingsResource(uri) ? + ConfigurationTarget.WORKSPACE : + this.isDefaultFolderSettingsResource(uri) ? + ConfigurationTarget.WORKSPACE_FOLDER : + ConfigurationTarget.USER_LOCAL; } private isDefaultSettingsResource(uri: URI): boolean { @@ -546,7 +549,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return Promise.resolve(undefined); } - return this.fileService.resolveContent(workspaceConfig) + return this.textFileService.read(workspaceConfig) .then(content => { if (Object.keys(parse(content.value)).indexOf('settings') === -1) { return this.jsonEditingService.write(resource, { key: 'settings', value: {} }, true).then(undefined, () => { }); @@ -558,7 +561,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } private createIfNotExists(resource: URI, contents: string): Promise { - return this.fileService.resolveContent(resource, { acceptTextOnly: true }).then(undefined, error => { + return this.textFileService.read(resource, { acceptTextOnly: true }).then(undefined, error => { if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { return this.textFileService.write(resource, contents).then(undefined, error => { return Promise.reject(new Error(nls.localize('fail.createSettings', "Unable to create '{0}' ({1}).", this.labelService.getUriLabel(resource, { relative: true }), error))); diff --git a/src/vs/workbench/services/preferences/common/keybindingsEditorModel.ts b/src/vs/workbench/services/preferences/common/keybindingsEditorModel.ts index fed784141bf..0f80e25b1d2 100644 --- a/src/vs/workbench/services/preferences/common/keybindingsEditorModel.ts +++ b/src/vs/workbench/services/preferences/common/keybindingsEditorModel.ts @@ -174,7 +174,7 @@ export class KeybindingsEditorModel extends EditorModel { const commandsWithDefaultKeybindings = this.keybindingsService.getDefaultKeybindings().map(keybinding => keybinding.command); for (const command of KeybindingResolver.getAllUnboundCommands(boundCommands)) { - const keybindingItem = new ResolvedKeybindingItem(null, command, null, undefined, commandsWithDefaultKeybindings.indexOf(command) === -1); + const keybindingItem = new ResolvedKeybindingItem(undefined, command, null, undefined, commandsWithDefaultKeybindings.indexOf(command) === -1); this._keybindingItemsSortedByPrecedence.push(KeybindingsEditorModel.toKeybindingEntry(command, keybindingItem, workbenchActionsRegistry, editorActionsLabels)); } this._keybindingItems = this._keybindingItemsSortedByPrecedence.slice(0).sort((a, b) => KeybindingsEditorModel.compareKeybindingData(a, b)); diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 2ee91bd2e37..008d85de813 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -214,6 +214,7 @@ export interface IPreferencesService { export function getSettingsTargetName(target: ConfigurationTarget, resource: URI, workspaceContextService: IWorkspaceContextService): string { switch (target) { + case ConfigurationTarget.USER: case ConfigurationTarget.USER_LOCAL: return localize('userSettingsTarget', "User Settings"); case ConfigurationTarget.WORKSPACE: diff --git a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts index 002a49d799c..ee813e14fe5 100644 --- a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts +++ b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts @@ -31,7 +31,7 @@ export class DefaultPreferencesEditorInput extends ResourceEditorInput { constructor(defaultSettingsResource: URI, @ITextModelService textModelResolverService: ITextModelService ) { - super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, textModelResolverService); + super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, undefined, textModelResolverService); } getTypeId(): string { diff --git a/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts b/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts index 4c256284b39..c70d6d94927 100644 --- a/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts +++ b/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts @@ -617,7 +617,7 @@ suite('KeybindingsEditorModel test', () => { parts.push(aSimpleKeybinding(chordPart)); } } - let keybinding = parts.length > 0 ? new USLayoutResolvedKeybinding(new ChordKeybinding(parts), OS) : null; + const keybinding = parts.length > 0 ? new USLayoutResolvedKeybinding(new ChordKeybinding(parts), OS) : undefined; return new ResolvedKeybindingItem(keybinding, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : undefined, isDefault === undefined ? true : isDefault); } diff --git a/src/vs/workbench/services/progress/browser/progressService2.ts b/src/vs/workbench/services/progress/browser/progressService2.ts index 3dddd649b7d..d2e4eee1263 100644 --- a/src/vs/workbench/services/progress/browser/progressService2.ts +++ b/src/vs/workbench/services/progress/browser/progressService2.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/progressService2'; import { localize } from 'vs/nls'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IProgressService2, IProgressOptions, IProgressStep, ProgressLocation, IProgress, emptyProgress, Progress } from 'vs/platform/progress/common/progress'; +import { IProgressService2, IProgressOptions, IProgressStep, ProgressLocation, IProgress, emptyProgress, Progress, IProgressNotificationOptions } from 'vs/platform/progress/common/progress'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { StatusbarAlignment, IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { timeout } from 'vs/base/common/async'; @@ -20,6 +20,9 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Dialog } from 'vs/base/browser/ui/dialog/dialog'; import { attachDialogStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { EventHelper } from 'vs/base/browser/dom'; export class ProgressService2 implements IProgressService2 { @@ -34,7 +37,8 @@ export class ProgressService2 implements IProgressService2 { @INotificationService private readonly _notificationService: INotificationService, @IStatusbarService private readonly _statusbarService: IStatusbarService, @ILayoutService private readonly _layoutService: ILayoutService, - @IThemeService private readonly _themeService: IThemeService + @IThemeService private readonly _themeService: IThemeService, + @IKeybindingService private readonly _keybindingService: IKeybindingService ) { } withProgress(options: IProgressOptions, task: (progress: IProgress) => Promise, onDidCancel?: () => void): Promise { @@ -50,7 +54,7 @@ export class ProgressService2 implements IProgressService2 { switch (location) { case ProgressLocation.Notification: - return this._withNotificationProgress(options, task, onDidCancel); + return this._withNotificationProgress({ ...options, location: ProgressLocation.Notification }, task, onDidCancel); case ProgressLocation.Window: return this._withWindowProgress(options, task); case ProgressLocation.Explorer: @@ -134,7 +138,7 @@ export class ProgressService2 implements IProgressService2 { } } - private _withNotificationProgress

, R = unknown>(options: IProgressOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { + private _withNotificationProgress

, R = unknown>(options: IProgressNotificationOptions, callback: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { const toDispose: IDisposable[] = []; const createNotification = (message: string | undefined, increment?: number): INotificationHandle | undefined => { @@ -142,7 +146,7 @@ export class ProgressService2 implements IProgressService2 { return undefined; // we need a message at least } - const actions: INotificationActions = { primary: [] }; + const actions: INotificationActions = { primary: options.primaryActions || [], secondary: options.secondaryActions || [] }; if (options.cancellable) { const cancelAction = new class extends Action { constructor() { @@ -276,6 +280,10 @@ export class ProgressService2 implements IProgressService2 { private _withDialogProgress

, R = unknown>(options: IProgressOptions, task: (progress: IProgress<{ message?: string, increment?: number }>) => P, onDidCancel?: () => void): P { const disposables: IDisposable[] = []; + const allowableCommands = [ + 'workbench.action.quit', + 'workbench.action.reloadWindow' + ]; let dialog: Dialog; @@ -284,14 +292,24 @@ export class ProgressService2 implements IProgressService2 { this._layoutService.container, message, [options.cancellable ? localize('cancel', "Cancel") : localize('dismiss', "Dismiss")], - { type: 'pending' } + { + type: 'pending', + keyEventProcessor: (event: StandardKeyboardEvent) => { + const resolved = this._keybindingService.softDispatch(event, this._layoutService.container); + if (resolved && resolved.commandId) { + if (allowableCommands.indexOf(resolved.commandId) === -1) { + EventHelper.stop(event, true); + } + } + } + } ); disposables.push(dialog); disposables.push(attachDialogStyler(dialog, this._themeService)); dialog.show().then(() => { - if (options.cancellable && typeof onDidCancel === 'function') { + if (typeof onDidCancel === 'function') { onDidCancel(); } diff --git a/src/vs/workbench/services/progress/test/progressService.test.ts b/src/vs/workbench/services/progress/test/progressService.test.ts index 58ac7214e7a..205b49e082e 100644 --- a/src/vs/workbench/services/progress/test/progressService.test.ts +++ b/src/vs/workbench/services/progress/test/progressService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IAction, IActionItem } from 'vs/base/common/actions'; +import { IAction, IActionViewItem } from 'vs/base/common/actions'; import { IEditorControl } from 'vs/workbench/common/editor'; import { ScopedProgressService, ScopedService } from 'vs/workbench/services/progress/browser/progressService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; @@ -51,7 +51,7 @@ class TestViewlet implements IViewlet { /** * Returns the action item for a specific action. */ - getActionItem(action: IAction): IActionItem { + getActionViewItem(action: IAction): IActionViewItem { return null!; } diff --git a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts index c2e838965b1..c1cd786ebb1 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts @@ -6,20 +6,25 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IRemoteAgentConnection } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { AbstractRemoteAgentService } from 'vs/workbench/services/remote/common/abstractRemoteAgentService'; +import { AbstractRemoteAgentService, RemoteAgentConnection } from 'vs/workbench/services/remote/common/abstractRemoteAgentService'; import { IProductService } from 'vs/platform/product/common/product'; +import { browserWebSocketFactory } from 'vs/platform/remote/browser/browserWebSocketFactory'; export class RemoteAgentService extends AbstractRemoteAgentService { + private readonly _connection: IRemoteAgentConnection | null = null; + constructor( @IEnvironmentService environmentService: IEnvironmentService, @IProductService productService: IProductService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService ) { super(environmentService); + const authority = document.location.host; + this._connection = this._register(new RemoteAgentConnection(authority, productService.commit, browserWebSocketFactory, environmentService, remoteAuthorityResolverService)); } getConnection(): IRemoteAgentConnection | null { - return null; + return this._connection; } } diff --git a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts index 17b852ec404..79b10c15b65 100644 --- a/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/abstractRemoteAgentService.ts @@ -10,7 +10,7 @@ import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { connectRemoteAgentManagement, IConnectionOptions, IWebSocketFactory, PersistenConnectionEvent } from 'vs/platform/remote/common/remoteAgentConnection'; import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { RemoteAgentConnectionContext, IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; @@ -56,6 +56,16 @@ export abstract class AbstractRemoteAgentService extends Disposable implements I return Promise.resolve(undefined); } + + disableTelemetry(): Promise { + const connection = this.getConnection(); + if (connection) { + const client = new RemoteExtensionEnvironmentChannelClient(connection.getChannel('remoteextensionsenvironment')); + return client.disableTelemetry(); + } + + return Promise.resolve(undefined); + } } export class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection { @@ -128,7 +138,11 @@ class RemoteConnectionFailureNotificationContribution implements IWorkbenchContr ) { // Let's cover the case where connecting to fetch the remote extension info fails remoteAgentService.getEnvironment(true) - .then(undefined, err => notificationService.error(nls.localize('connectionError', "Failed to connect to the remote extension host server (Error: {0})", err ? err.message : ''))); + .then(undefined, err => { + if (!RemoteAuthorityResolverError.isHandledNotAvailable(err)) { + notificationService.error(nls.localize('connectionError', "Failed to connect to the remote extension host server (Error: {0})", err ? err.message : '')); + } + }); } } diff --git a/src/vs/workbench/services/remote/common/remoteAgentService.ts b/src/vs/workbench/services/remote/common/remoteAgentService.ts index b3d3570480f..6fd204af018 100644 --- a/src/vs/workbench/services/remote/common/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentService.ts @@ -20,6 +20,7 @@ export interface IRemoteAgentService { getConnection(): IRemoteAgentConnection | null; getEnvironment(bail?: boolean): Promise; getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise; + disableTelemetry(): Promise; } export interface IRemoteAgentConnection { diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 498b12773bb..a3d5311a7ea 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -3,16 +3,111 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as net from 'net'; +import { Barrier } from 'vs/base/common/async'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import product from 'vs/platform/product/node/product'; +import { connectRemoteAgentTunnel, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection'; +import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; +import { nodeWebSocketFactory } from 'vs/platform/remote/node/nodeWebSocketFactory'; + +export async function createRemoteTunnel(options: IConnectionOptions, tunnelRemotePort: number): Promise { + const tunnel = new NodeRemoteTunnel(options, tunnelRemotePort); + return tunnel.waitForReady(); +} + +class NodeRemoteTunnel extends Disposable implements RemoteTunnel { + + public readonly tunnelRemotePort: number; + public readonly tunnelLocalPort: number; + + private readonly _options: IConnectionOptions; + private readonly _server: net.Server; + private readonly _barrier: Barrier; + + private readonly _listeningListener: () => void; + private readonly _connectionListener: (socket: net.Socket) => void; + + constructor(options: IConnectionOptions, tunnelRemotePort: number) { + super(); + this._options = options; + this._server = net.createServer(); + this._barrier = new Barrier(); + + this._listeningListener = () => this._barrier.open(); + this._server.on('listening', this._listeningListener); + + this._connectionListener = (socket) => this._onConnection(socket); + this._server.on('connection', this._connectionListener); + + this.tunnelRemotePort = tunnelRemotePort; + this.tunnelLocalPort = (this._server.listen(0).address()).port; + } + + public dispose(): void { + super.dispose(); + this._server.removeListener('listening', this._listeningListener); + this._server.removeListener('connection', this._connectionListener); + this._server.close(); + } + + public async waitForReady(): Promise { + await this._barrier.wait(); + return this; + } + + private async _onConnection(localSocket: net.Socket): Promise { + // pause reading on the socket until we have a chance to forward its data + localSocket.pause(); + + const protocol = await connectRemoteAgentTunnel(this._options, this.tunnelRemotePort); + const remoteSocket = (protocol.getSocket()).socket; + const dataChunk = protocol.readEntireBuffer(); + protocol.dispose(); + + if (dataChunk.byteLength > 0) { + localSocket.write(dataChunk.buffer); + } + + localSocket.on('end', () => remoteSocket.end()); + localSocket.on('close', () => remoteSocket.end()); + remoteSocket.on('end', () => localSocket.end()); + remoteSocket.on('close', () => localSocket.end()); + + localSocket.pipe(remoteSocket); + remoteSocket.pipe(localSocket); + } +} export class TunnelService implements ITunnelService { _serviceBrand: any; public constructor( + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, ) { } openTunnel(remotePort: number): Promise | undefined { - return undefined; + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + if (!remoteAuthority) { + return undefined; + } + + const options: IConnectionOptions = { + isBuilt: this.environmentService.isBuilt, + commit: product.commit, + webSocketFactory: nodeWebSocketFactory, + addressProvider: { + getAddress: async () => { + const { host, port } = await this.remoteAuthorityResolverService.resolveAuthority(remoteAuthority); + return { host, port }; + } + } + }; + return createRemoteTunnel(options, remotePort); } } diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index 612bd746dfb..5ade08b60ca 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -22,6 +22,7 @@ import { URI } from 'vs/base/common/uri'; import { readdir } from 'vs/base/node/pfs'; import { IFileQuery, IFolderQuery, IProgressMessage, ISearchEngineStats, IRawFileMatch, ISearchEngine, ISearchEngineSuccess } from 'vs/workbench/services/search/common/search'; import { spawnRipgrepCmd } from './ripgrepFileSearch'; +import { prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; interface IDirectoryEntry { base: string; @@ -76,7 +77,7 @@ export class FileWalker { this.errors = []; if (this.filePattern) { - this.normalizedFilePatternLowercase = strings.stripWildcards(this.filePattern).toLowerCase(); + this.normalizedFilePatternLowercase = prepareQuery(this.filePattern).lowercase; } this.globalExcludePattern = config.excludePattern && glob.parse(config.excludePattern); diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index 1d5ed2ca55e..2a27d205aac 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -16,7 +16,7 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import * as strings from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer'; -import { MAX_FILE_SIZE } from 'vs/platform/files/node/fileConstants'; +import { MAX_FILE_SIZE } from 'vs/base/node/pfs'; import { ICachedSearchStats, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, IRawFileQuery, IRawQuery, IRawTextQuery, ITextQuery, IFileSearchProgressItem, IRawFileMatch, IRawSearchService, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess } from 'vs/workbench/services/search/common/search'; import { Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch'; import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; @@ -312,7 +312,7 @@ export class SearchService implements IRawSearchService { // Pattern match on results const results: IRawFileMatch[] = []; - const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase(); + const normalizedSearchValueLowercase = prepareQuery(searchValue).lowercase; for (const entry of cachedEntries) { // Check if this entry is a match for the search value diff --git a/src/vs/workbench/services/textMate/electron-browser/textMateService.ts b/src/vs/workbench/services/textMate/electron-browser/textMateService.ts index 0fd4d880811..94e175490fa 100644 --- a/src/vs/workbench/services/textMate/electron-browser/textMateService.ts +++ b/src/vs/workbench/services/textMate/electron-browser/textMateService.ts @@ -248,8 +248,8 @@ export class TextMateService extends Disposable implements ITextMateService { return null; } try { - const content = await this._fileService.resolveContent(location, { encoding: 'utf8' }); - return parseRawGrammar(content.value, location.path); + const content = await this._fileService.readFile(location); + return parseRawGrammar(content.value.toString(), location.path); } catch (e) { this._logService.error(`Unable to load and parse grammar for scope ${scopeName} from ${location}`, e); return null; diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts new file mode 100644 index 00000000000..d15549e21cc --- /dev/null +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; +import { ITextFileService, IResourceEncodings, IResourceEncoding } from 'vs/workbench/services/textfile/common/textfiles'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class BrowserTextFileService extends TextFileService { + + readonly encoding: IResourceEncodings = { + getPreferredWriteEncoding(): IResourceEncoding { + return { encoding: 'utf8', hasBOM: false }; + } + }; +} + +registerSingleton(ITextFileService, BrowserTextFileService); \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 2ae22f2aea4..c114ccbe3b9 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -9,28 +9,35 @@ import { Event, Emitter } from 'vs/base/common/event'; import { guessMimeTypes } from 'vs/base/common/mime'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { URI } from 'vs/base/common/uri'; -import { isUndefinedOrNull, withUndefinedAsNull } from 'vs/base/common/types'; +import { isUndefinedOrNull } from 'vs/base/common/types'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, IRawTextContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, ITextFileStreamContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { EncodingMode } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IFileService, FileOperationError, FileOperationResult, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType, IFileStatWithMetadata, etag } from 'vs/platform/files/common/files'; +import { IFileService, FileOperationError, FileOperationResult, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; +import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { RunOnceScheduler, timeout } from 'vs/base/common/async'; import { ITextBufferFactory } from 'vs/editor/common/model'; import { hash } from 'vs/base/common/hash'; -import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { isLinux } from 'vs/base/common/platform'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { isEqual, isEqualOrParent, extname, basename } from 'vs/base/common/resources'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { Schemas } from 'vs/base/common/network'; + +export interface IBackupMetaData { + mtime: number; + size: number; + etag: string; + orphaned: boolean; +} /** * The text file editor model listens to changes to its underlying code editor model and saves these changes through the file service back to the disk. @@ -55,29 +62,39 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil get onDidStateChange(): Event { return this._onDidStateChange.event; } private resource: URI; - private contentEncoding: string; // encoding as reported from disk - private preferredEncoding: string; // encoding as chosen by the user - private dirty: boolean; + + private contentEncoding: string; // encoding as reported from disk + private preferredEncoding: string; // encoding as chosen by the user + + private preferredMode: string; // mode as chosen by the user + private versionId: number; private bufferSavedVersionId: number; - private lastResolvedDiskStat: IFileStatWithMetadata; private blockModelContentChange: boolean; + + private lastResolvedFileStat: IFileStatWithMetadata; + private autoSaveAfterMillies?: number; private autoSaveAfterMilliesEnabled: boolean; private autoSaveDisposable?: IDisposable; + + private saveSequentializer: SaveSequentializer; + private lastSaveAttemptTime: number; + private contentChangeEventScheduler: RunOnceScheduler; private orphanedChangeEventScheduler: RunOnceScheduler; - private saveSequentializer: SaveSequentializer; - private disposed: boolean; - private lastSaveAttemptTime: number; - private createTextEditorModelPromise: Promise | null; + + private dirty: boolean; private inConflictMode: boolean; private inOrphanMode: boolean; private inErrorMode: boolean; + private disposed: boolean; + constructor( resource: URI, preferredEncoding: string, + preferredMode: string, @INotificationService private readonly notificationService: INotificationService, @IModeService modeService: IModeService, @IModelService modelService: IModelService, @@ -94,6 +111,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.resource = resource; this.preferredEncoding = preferredEncoding; + this.preferredMode = preferredMode; this.inOrphanMode = false; this.dirty = false; this.versionId = 0; @@ -189,18 +207,40 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private onFilesAssociationChange(): void { - if (!this.textEditorModel) { + if (!this.isResolved()) { return; } const firstLineText = this.getFirstLineText(this.textEditorModel); - const languageSelection = this.getOrCreateMode(this.modeService, undefined, firstLineText); + const languageSelection = this.getOrCreateMode(this.resource, this.modeService, this.preferredMode, firstLineText); this.modelService.setMode(this.textEditorModel, languageSelection); } - getVersionId(): number { - return this.versionId; + setMode(mode: string): void { + super.setMode(mode); + + this.preferredMode = mode; + } + + async backup(target = this.resource): Promise { + if (this.isResolved()) { + + // Only fill in model metadata if resource matches + let meta: IBackupMetaData | undefined = undefined; + if (isEqual(target, this.resource) && this.lastResolvedFileStat) { + meta = { + mtime: this.lastResolvedFileStat.mtime, + size: this.lastResolvedFileStat.size, + etag: this.lastResolvedFileStat.etag, + orphaned: this.inOrphanMode + }; + } + + return this.backupFileService.backupResource(target, this.createSnapshot(), this.versionId, meta); + } + + return Promise.resolve(); } async revert(soft?: boolean): Promise { @@ -235,7 +275,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - load(options?: ILoadOptions): Promise { + async load(options?: ILoadOptions): Promise { this.logService.trace('load() - enter', this.resource); // It is very important to not reload the model when the model is dirty. @@ -244,44 +284,57 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (this.dirty || this.saveSequentializer.hasPendingSave()) { this.logService.trace('load() - exit - without loading because model is dirty or being saved', this.resource); - return Promise.resolve(this); + return this; } // Only for new models we support to load from backup - if (!this.textEditorModel && !this.createTextEditorModelPromise) { - return this.loadFromBackup(options); + if (!this.isResolved()) { + const backup = await this.backupFileService.loadBackupResource(this.resource); + + if (this.isResolved()) { + return this; // Make sure meanwhile someone else did not suceed in loading + } + + if (backup) { + try { + return await this.loadFromBackup(backup, options); + } catch (error) { + // ignore error and continue to load as file below + } + } } // Otherwise load from file resource return this.loadFromFile(options); } - private async loadFromBackup(options?: ILoadOptions): Promise { - const backup = await this.backupFileService.loadBackupResource(this.resource); + private async loadFromBackup(backup: URI, options?: ILoadOptions): Promise { - // Make sure meanwhile someone else did not suceed or start loading - if (this.createTextEditorModelPromise || this.textEditorModel) { - return this.createTextEditorModelPromise || this; + // Resolve actual backup contents + const resolvedBackup = await this.backupFileService.resolveBackupContent(backup); + + if (this.isResolved()) { + return this; // Make sure meanwhile someone else did not suceed in loading } - // If we have a backup, continue loading with it - if (!!backup) { - const content: IRawTextContent = { - resource: this.resource, - name: basename(this.resource), - mtime: Date.now(), - size: 0, - etag: etag(Date.now(), 0), - value: createTextBufferFactory(''), /* will be filled later from backup */ - encoding: this.fileService.encoding.getWriteEncoding(this.resource, this.preferredEncoding).encoding, - isReadonly: false - }; + // Load with backup + this.loadFromContent({ + resource: this.resource, + name: basename(this.resource), + mtime: resolvedBackup.meta ? resolvedBackup.meta.mtime : Date.now(), + size: resolvedBackup.meta ? resolvedBackup.meta.size : 0, + etag: resolvedBackup.meta ? resolvedBackup.meta.etag : ETAG_DISABLED, // etag disabled if unknown! + value: resolvedBackup.value, + encoding: this.textFileService.encoding.getPreferredWriteEncoding(this.resource, this.preferredEncoding).encoding, + isReadonly: false + }, options, true /* from backup */); - return this.loadWithContent(content, options, backup); + // Restore orphaned flag based on state + if (resolvedBackup.meta && resolvedBackup.meta.orphaned) { + this.setOrphaned(true); } - // Otherwise load from file - return this.loadFromFile(options); + return this; } private async loadFromFile(options?: ILoadOptions): Promise { @@ -291,9 +344,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Decide on etag let etag: string | undefined; if (forceReadFromDisk) { - etag = undefined; // reset ETag if we enforce to read from disk - } else if (this.lastResolvedDiskStat) { - etag = this.lastResolvedDiskStat.etag; // otherwise respect etag to support caching + etag = ETAG_DISABLED; // disable ETag if we enforce to read from disk + } else if (this.lastResolvedFileStat) { + etag = this.lastResolvedFileStat.etag; // otherwise respect etag to support caching } // Ensure to track the versionId before doing a long running operation @@ -306,17 +359,16 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Resolve Content try { - const content = await this.textFileService.resolve(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding }); + const content = await this.textFileService.readStream(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding }); // Clear orphaned state when loading was successful this.setOrphaned(false); - // Guard against the model having changed in the meantime - if (currentVersionId === this.versionId) { - return this.loadWithContent(content, options); + if (currentVersionId !== this.versionId) { + return this; // Make sure meanwhile someone else did not suceed loading } - return this; + return this.loadFromContent(content, options); } catch (error) { const result = error.fileOperationResult; @@ -346,38 +398,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - private loadWithContent(content: IRawTextContent, options?: ILoadOptions, backup?: URI): Promise { - return this.doLoadWithContent(content, backup).then(model => { - - // Telemetry: We log the fileGet telemetry event after the model has been loaded to ensure a good mimetype - const settingsType = this.getTypeIfSettings(); - if (settingsType) { - /* __GDPR__ - "settingsRead" : { - "settingsType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('settingsRead', { settingsType }); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data - } else { - /* __GDPR__ - "fileGet" : { - "${include}": [ - "${FileTelemetryData}" - ] - } - */ - this.telemetryService.publicLog('fileGet', this.getTelemetryData(options && options.reason ? options.reason : LoadReason.OTHER)); - } - - return model; - }); - } - - private doLoadWithContent(content: IRawTextContent, backup?: URI): Promise { + private loadFromContent(content: ITextFileStreamContent, options?: ILoadOptions, fromBackup?: boolean): TextFileEditorModel { this.logService.trace('load() - resolved content', this.resource); // Update our resolved disk stat model - this.updateLastResolvedDiskStat({ + this.updateLastResolvedFileStat({ resource: this.resource, name: content.name, mtime: content.mtime, @@ -400,21 +425,61 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Update Existing Model - if (this.textEditorModel) { + if (this.isResolved()) { this.doUpdateTextModel(content.value); - - return Promise.resolve(this); - } - - // Join an existing request to create the editor model to avoid race conditions - else if (this.createTextEditorModelPromise) { - this.logService.trace('load() - join existing text editor model promise', this.resource); - - return this.createTextEditorModelPromise; } // Create New Model - return this.doCreateTextModel(content.resource, content.value, backup); + else { + this.doCreateTextModel(content.resource, content.value, !!fromBackup); + } + + // Telemetry: We log the fileGet telemetry event after the model has been loaded to ensure a good mimetype + const settingsType = this.getTypeIfSettings(); + if (settingsType) { + /* __GDPR__ + "settingsRead" : { + "settingsType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('settingsRead', { settingsType }); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data + } else { + /* __GDPR__ + "fileGet" : { + "${include}": [ + "${FileTelemetryData}" + ] + } + */ + this.telemetryService.publicLog('fileGet', this.getTelemetryData(options && options.reason ? options.reason : LoadReason.OTHER)); + } + + return this; + } + + private doCreateTextModel(resource: URI, value: ITextBufferFactory, fromBackup: boolean): void { + this.logService.trace('load() - created text editor model', this.resource); + + // Create model + this.createTextEditorModel(value, resource, this.preferredMode); + + // We restored a backup so we have to set the model as being dirty + // We also want to trigger auto save if it is enabled to simulate the exact same behaviour + // you would get if manually making the model dirty (fixes https://github.com/Microsoft/vscode/issues/16977) + if (fromBackup) { + this.makeDirty(); + if (this.autoSaveAfterMilliesEnabled) { + this.doAutoSave(this.versionId); + } + } + + // Ensure we are not tracking a stale state + else { + this.setDirty(false); + } + + // Model Listeners + this.installModelListeners(); } private doUpdateTextModel(value: ITextBufferFactory): void { @@ -426,7 +491,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Update model value in a block that ignores model content change events this.blockModelContentChange = true; try { - this.updateTextEditorModel(value); + this.updateTextEditorModel(value, this.preferredMode); } finally { this.blockModelContentChange = false; } @@ -435,44 +500,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.updateSavedVersionId(); } - private doCreateTextModel(resource: URI, value: ITextBufferFactory, backup: URI | undefined): Promise { - this.logService.trace('load() - created text editor model', this.resource); - - this.createTextEditorModelPromise = this.doLoadBackup(backup).then(backupContent => { - this.createTextEditorModelPromise = null; - - // Create model - const hasBackupContent = !!backupContent; - this.createTextEditorModel(backupContent ? backupContent : value, resource); - - // We restored a backup so we have to set the model as being dirty - // We also want to trigger auto save if it is enabled to simulate the exact same behaviour - // you would get if manually making the model dirty (fixes https://github.com/Microsoft/vscode/issues/16977) - if (hasBackupContent) { - this.makeDirty(); - if (this.autoSaveAfterMilliesEnabled) { - this.doAutoSave(this.versionId); - } - } - - // Ensure we are not tracking a stale state - else { - this.setDirty(false); - } - - // Model Listeners - this.installModelListeners(); - - return this; - }, error => { - this.createTextEditorModelPromise = null; - - return Promise.reject(error); - }); - - return this.createTextEditorModelPromise; - } - private installModelListeners(): void { // See https://github.com/Microsoft/vscode/issues/30189 @@ -480,27 +507,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // where `value` was captured in the content change listener closure scope. // Content Change - if (this.textEditorModel) { + if (this.isResolved()) { this._register(this.textEditorModel.onDidChangeContent(() => this.onModelContentChanged())); } } - private async doLoadBackup(backup: URI | undefined): Promise { - if (!backup) { - return null; - } - - try { - return withUndefinedAsNull(await this.backupFileService.resolveBackupContent(backup)); - } catch (error) { - return null; // ignore errors - } - } - - protected getOrCreateMode(modeService: IModeService, preferredModeIds: string | undefined, firstLineText?: string): ILanguageSelection { - return modeService.createByFilepathOrFirstLine(this.resource.fsPath, firstLineText); - } - private onModelContentChanged(): void { this.logService.trace(`onModelContentChanged() - enter`, this.resource); @@ -517,7 +528,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // In this case we clear the dirty flag and emit a SAVED event to indicate this state. // Note: we currently only do this check when auto-save is turned off because there you see // a dirty indicator that you want to get rid of when undoing to the saved version. - if (!this.autoSaveAfterMilliesEnabled && this.textEditorModel && this.textEditorModel.getAlternativeVersionId() === this.bufferSavedVersionId) { + if (!this.autoSaveAfterMilliesEnabled && this.isResolved() && this.textEditorModel.getAlternativeVersionId() === this.bufferSavedVersionId) { this.logService.trace('onModelContentChanged() - model content changed back to last saved version', this.resource); // Clear flags @@ -648,7 +659,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Push all edit operations to the undo stack so that the user has a chance to // Ctrl+Z back to the saved version. We only do this when auto-save is turned off - if (!this.autoSaveAfterMilliesEnabled && this.textEditorModel) { + if (!this.autoSaveAfterMilliesEnabled && this.isResolved()) { this.textEditorModel.pushStackElement(); } @@ -678,7 +689,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // saving contents to disk that are stale (see https://github.com/Microsoft/vscode/issues/50942). // To fix this issue, we will not store the contents to disk when we got disposed. if (this.disposed) { - return undefined; + return; + } + + // We require a resolved model from this point on, since we are about to write data to disk. + if (!this.isResolved()) { + return; } // Under certain conditions we do a short-cut of flushing contents to disk when we can assume that @@ -704,16 +720,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Save to Disk // mark the save operation as currently pending with the versionId (it might have changed from a save participant triggering) this.logService.trace(`doSave(${versionId}) - before write()`, this.resource); - const snapshot = this.createSnapshot(); - if (!snapshot) { - throw new Error('Invalid snapshot'); - } - return this.saveSequentializer.setPending(newVersionId, this.textFileService.write(this.lastResolvedDiskStat.resource, snapshot, { + return this.saveSequentializer.setPending(newVersionId, this.textFileService.write(this.lastResolvedFileStat.resource, this.createSnapshot(), { overwriteReadonly: options.overwriteReadonly, overwriteEncoding: options.overwriteEncoding, - mtime: this.lastResolvedDiskStat.mtime, + mtime: this.lastResolvedFileStat.mtime, encoding: this.getEncoding(), - etag: this.lastResolvedDiskStat.etag, + etag: this.lastResolvedFileStat.etag, writeElevated: options.writeElevated }).then(stat => { this.logService.trace(`doSave(${versionId}) - after write()`, this.resource); @@ -727,7 +739,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Updated resolved stat with updated stat - this.updateLastResolvedDiskStat(stat); + this.updateLastResolvedFileStat(stat); // Cancel any content change event promises as they are no longer valid this.contentChangeEventScheduler.cancel(); @@ -755,10 +767,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.telemetryService.publicLog('filePUT', this.getTelemetryData(options.reason)); } }, error => { - if (!error) { - error = new Error('Unknown Save Error'); // TODO@remote we should never get null as error (https://github.com/Microsoft/vscode/issues/55051) - } - this.logService.error(`doSave(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource); // Flag as error state in the model @@ -820,10 +828,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private getTelemetryData(reason: number | undefined): object { const ext = extname(this.resource); const fileName = basename(this.resource); + const path = this.resource.scheme === Schemas.file ? this.resource.fsPath : this.resource.path; const telemetryData = { - mimeType: guessMimeTypes(this.resource.fsPath).join(', '), + mimeType: guessMimeTypes(path).join(', '), ext, - path: hash(this.resource.fsPath), + path: hash(path), reason }; @@ -844,19 +853,22 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private doTouch(versionId: number): Promise { - const snapshot = this.createSnapshot(); - if (!snapshot) { - throw new Error('invalid snapshot'); + if (!this.isResolved()) { + return Promise.resolve(); } - return this.saveSequentializer.setPending(versionId, this.textFileService.write(this.lastResolvedDiskStat.resource, snapshot, { - mtime: this.lastResolvedDiskStat.mtime, + return this.saveSequentializer.setPending(versionId, this.textFileService.write(this.lastResolvedFileStat.resource, this.createSnapshot(), { + mtime: this.lastResolvedFileStat.mtime, encoding: this.getEncoding(), - etag: this.lastResolvedDiskStat.etag + etag: this.lastResolvedFileStat.etag }).then(stat => { // Updated resolved stat with updated stat since touching it might have changed mtime - this.updateLastResolvedDiskStat(stat); + this.updateLastResolvedFileStat(stat); + + // Emit File Saved Event + this._onDidStateChange.fire(StateChange.SAVED); + }, error => onUnexpectedError(error) /* just log any error but do not notify the user since the file was not dirty */)); } @@ -890,23 +902,23 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // in order to find out if the model changed back to a saved version (e.g. // when undoing long enough to reach to a version that is saved and then to // clear the dirty flag) - if (this.textEditorModel) { + if (this.isResolved()) { this.bufferSavedVersionId = this.textEditorModel.getAlternativeVersionId(); } } - private updateLastResolvedDiskStat(newVersionOnDiskStat: IFileStatWithMetadata): void { + private updateLastResolvedFileStat(newFileStat: IFileStatWithMetadata): void { // First resolve - just take - if (!this.lastResolvedDiskStat) { - this.lastResolvedDiskStat = newVersionOnDiskStat; + if (!this.lastResolvedFileStat) { + this.lastResolvedFileStat = newFileStat; } // Subsequent resolve - make sure that we only assign it if the mtime is equal or has advanced. // This prevents race conditions from loading and saving. If a save comes in late after a revert // was called, the mtime could be out of sync. - else if (this.lastResolvedDiskStat.mtime <= newVersionOnDiskStat.mtime) { - this.lastResolvedDiskStat = newVersionOnDiskStat; + else if (this.lastResolvedFileStat.mtime <= newFileStat.mtime) { + this.lastResolvedFileStat = newFileStat; } } @@ -929,10 +941,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.lastSaveAttemptTime; } - getETag(): string | null { - return this.lastResolvedDiskStat ? this.lastResolvedDiskStat.etag || null : null; - } - hasState(state: ModelState): boolean { switch (state) { case ModelState.CONFLICT: @@ -1014,12 +1022,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return true; } - isResolved(): boolean { - return !isUndefinedOrNull(this.lastResolvedDiskStat); + isResolved(): this is IResolvedTextFileEditorModel { + return !!this.textEditorModel; } isReadonly(): boolean { - return !!(this.lastResolvedDiskStat && this.lastResolvedDiskStat.isReadonly); + return !!(this.lastResolvedFileStat && this.lastResolvedFileStat.isReadonly); } isDisposed(): boolean { @@ -1031,7 +1039,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } getStat(): IFileStatWithMetadata { - return this.lastResolvedDiskStat; + return this.lastResolvedFileStat; } dispose(): void { @@ -1040,8 +1048,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.inOrphanMode = false; this.inErrorMode = false; - this.createTextEditorModelPromise = null; - this.cancelPendingAutoSave(); super.dispose(); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 4c05367a02c..af67898ef69 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -153,7 +153,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Model does not exist else { - const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined); + const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined, options ? options.mode : undefined); modelPromise = model.load(options); // Install state change listener @@ -191,20 +191,25 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE this.mapResourceToPendingModelLoaders.set(resource, modelPromise); try { - const model = await modelPromise; + const resolvedModel = await modelPromise; // Make known to manager (if not already known) - this.add(resource, model); + this.add(resource, resolvedModel); // Model can be dirty if a backup was restored, so we make sure to have this event delivered - if (model.isDirty()) { - this._onModelDirty.fire(new TextFileModelChangeEvent(model, StateChange.DIRTY)); + if (resolvedModel.isDirty()) { + this._onModelDirty.fire(new TextFileModelChangeEvent(resolvedModel, StateChange.DIRTY)); } // Remove from pending loads this.mapResourceToPendingModelLoaders.delete(resource); - return model; + // Apply mode if provided + if (options && options.mode) { + resolvedModel.setMode(options.mode); + } + + return resolvedModel; } catch (error) { // Free resources of this invalid model diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index a31288664c2..97f68a63f06 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -11,11 +11,11 @@ import { Event, Emitter } from 'vs/base/common/event'; import * as platform from 'vs/base/common/platform'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent } from 'vs/workbench/services/textfile/common/textfiles'; +import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor'; import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IFileService, IResolveContentOptions, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, ITextSnapshot, IWriteTextFileOptions, IFileStatWithMetadata, toBufferOrReadable, ICreateFileOptions } from 'vs/platform/files/common/files'; +import { IFileService, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -37,11 +37,15 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { coalesce } from 'vs/base/common/arrays'; import { trim } from 'vs/base/common/strings'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ITextSnapshot } from 'vs/editor/common/model'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; /** * The workbench file service implementation implements the raw file service spec and adds additional methods on top. */ -export class TextFileService extends Disposable implements ITextFileService { +export abstract class TextFileService extends Disposable implements ITextFileService { _serviceBrand: ServiceIdentifier; @@ -57,6 +61,8 @@ export class TextFileService extends Disposable implements ITextFileService { private _models: TextFileEditorModelManager; get models(): ITextFileEditorModelManager { return this._models; } + abstract get encoding(): IResourceEncodings; + private currentFilesAssociationConfig: { [key: string]: string; }; private configuredAutoSaveDelay?: number; private configuredAutoSaveOnFocusChange: boolean; @@ -81,7 +87,8 @@ export class TextFileService extends Disposable implements ITextFileService { @IContextKeyService contextKeyService: IContextKeyService, @IDialogService private readonly dialogService: IDialogService, @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly editorService: IEditorService, + @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService ) { super(); @@ -232,59 +239,44 @@ export class TextFileService extends Disposable implements ITextFileService { private async doBackupAll(dirtyFileModels: ITextFileEditorModel[], untitledResources: URI[]): Promise { // Handle file resources first - await Promise.all(dirtyFileModels.map(async model => { - const snapshot = model.createSnapshot(); - if (snapshot) { - await this.backupFileService.backupResource(model.getResource(), snapshot, model.getVersionId()); - } - })); + await Promise.all(dirtyFileModels.map(model => model.backup())); // Handle untitled resources - const untitledModelPromises = untitledResources + await Promise.all(untitledResources .filter(untitled => this.untitledEditorService.exists(untitled)) - .map(untitled => this.untitledEditorService.loadOrCreate({ resource: untitled })); - - const untitledModels = await Promise.all(untitledModelPromises); - - await Promise.all(untitledModels.map(async model => { - const snapshot = model.createSnapshot(); - if (snapshot) { - await this.backupFileService.backupResource(model.getResource(), snapshot, model.getVersionId()); - } - })); + .map(async untitled => (await this.untitledEditorService.loadOrCreate({ resource: untitled })).backup())); } - private confirmBeforeShutdown(): boolean | Promise { - return this.confirmSave().then(confirm => { + private async confirmBeforeShutdown(): Promise { + const confirm = await this.confirmSave(); - // Save - if (confirm === ConfirmResult.SAVE) { - return this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }).then(result => { - if (result.results.some(r => !r.success)) { - return true; // veto if some saves failed - } + // Save + if (confirm === ConfirmResult.SAVE) { + const result = await this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }); - return this.noVeto({ cleanUpBackups: true }); - }); + if (result.results.some(r => !r.success)) { + return true; // veto if some saves failed } - // Don't Save - else if (confirm === ConfirmResult.DONT_SAVE) { + return this.noVeto({ cleanUpBackups: true }); + } - // Make sure to revert untitled so that they do not restore - // see https://github.com/Microsoft/vscode/issues/29572 - this.untitledEditorService.revertAll(); + // Don't Save + else if (confirm === ConfirmResult.DONT_SAVE) { - return this.noVeto({ cleanUpBackups: true }); - } + // Make sure to revert untitled so that they do not restore + // see https://github.com/Microsoft/vscode/issues/29572 + this.untitledEditorService.revertAll(); - // Cancel - else if (confirm === ConfirmResult.CANCEL) { - return true; // veto - } + return this.noVeto({ cleanUpBackups: true }); + } - return false; - }); + // Cancel + else if (confirm === ConfirmResult.CANCEL) { + return true; // veto + } + + return false; } private noVeto(options: { cleanUpBackups: boolean }): boolean | Promise { @@ -364,24 +356,62 @@ export class TextFileService extends Disposable implements ITextFileService { //#endregion - //#region primitives (resolve, create, move, delete, update) + //#region primitives (read, create, move, delete, update) - async resolve(resource: URI, options?: IResolveContentOptions): Promise { - const streamContent = await this.fileService.resolveStreamContent(resource, options); - const value = await createTextBufferFactoryFromStream(streamContent.value); + async read(resource: URI, options?: IReadTextFileOptions): Promise { + const content = await this.fileService.readFile(resource, options); + + // in case of acceptTextOnly: true, we check the first + // chunk for possibly being binary by looking for 0-bytes + // we limit this check to the first 512 bytes + this.validateBinary(content.value, options); return { - resource: streamContent.resource, - name: streamContent.name, - mtime: streamContent.mtime, - etag: streamContent.etag, - encoding: streamContent.encoding, - isReadonly: streamContent.isReadonly, - size: streamContent.size, - value + ...content, + encoding: 'utf8', + value: content.value.toString() }; } + async readStream(resource: URI, options?: IReadTextFileOptions): Promise { + const stream = await this.fileService.readFileStream(resource, options); + + // in case of acceptTextOnly: true, we check the first + // chunk for possibly being binary by looking for 0-bytes + // we limit this check to the first 512 bytes + let checkedForBinary = false; + const throwOnBinary = (data: VSBuffer): Error | undefined => { + if (!checkedForBinary) { + checkedForBinary = true; + + this.validateBinary(data, options); + } + + return undefined; + }; + + return { + ...stream, + encoding: 'utf8', + value: await createTextBufferFactoryFromStream(stream.value, undefined, options && options.acceptTextOnly ? throwOnBinary : undefined) + }; + } + + private validateBinary(buffer: VSBuffer, options?: IReadTextFileOptions): void { + if (!options || !options.acceptTextOnly) { + return; // no validation needed + } + + // in case of acceptTextOnly: true, we check the first + // chunk for possibly being binary by looking for 0-bytes + // we limit this check to the first 512 bytes + for (let i = 0; i < buffer.byteLength && i < 512; i++) { + if (buffer.readUInt8(i) === 0) { + throw new TextFileOperationError(nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options); + } + } + } + async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { const stat = await this.doCreate(resource, value, options); @@ -459,14 +489,10 @@ export class TextFileService extends Disposable implements ITextFileService { dirtyTargetModelUris.push(targetModelResource); // Backup dirty source model to the target resource it will become later - const snapshot = sourceModel.createSnapshot(); - if (snapshot) { - await this.backupFileService.backupResource(targetModelResource, snapshot, sourceModel.getVersionId()); - } + await sourceModel.backup(targetModelResource); })); } - // Soft revert the dirty source files if any await this.revertAll(dirtySourceModels.map(dirtySourceModel => dirtySourceModel.getResource()), { soft: true }); @@ -714,14 +740,14 @@ export class TextFileService extends Disposable implements ITextFileService { private getFileModels(arg1?: URI | URI[]): ITextFileEditorModel[] { if (Array.isArray(arg1)) { const models: ITextFileEditorModel[] = []; - (arg1).forEach(resource => { + arg1.forEach(resource => { models.push(...this.getFileModels(resource)); }); return models; } - return this._models.getAll(arg1); + return this._models.getAll(arg1); } private getDirtyFileModels(resources?: URI | URI[]): ITextFileEditorModel[] { @@ -827,12 +853,14 @@ export class TextFileService extends Disposable implements ITextFileService { return false; } - // take over encoding and model value from source model + // take over encoding, mode and model value from source model targetModel.updatePreferredEncoding(sourceModel.getEncoding()); - if (targetModel.textEditorModel) { - const snapshot = sourceModel.createSnapshot(); - if (snapshot) { - this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(snapshot)); + if (sourceModel.isResolved() && targetModel.isResolved()) { + this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); + + const mode = sourceModel.textEditorModel.getLanguageIdentifier(); + if (mode.language !== PLAINTEXT_MODE_ID) { + targetModel.textEditorModel.setMode(mode); // only use if more specific than plain/text } } @@ -843,7 +871,10 @@ export class TextFileService extends Disposable implements ITextFileService { } catch (error) { // binary model: delete the file and run the operation again - if ((error).fileOperationResult === FileOperationResult.FILE_IS_BINARY || (error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) { + if ( + (error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY || + (error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE + ) { await this.fileService.del(target); return this.doSaveTextFileAs(sourceModel, resource, target, options); diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 39288a2ec69..f3db50a0396 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -6,275 +6,25 @@ import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IEncodingSupport, ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor'; -import { IResolveContentOptions, ITextSnapshot, IBaseStatWithMetadata, IWriteTextFileOptions, IFileStatWithMetadata } from 'vs/platform/files/common/files'; +import { IEncodingSupport, ConfirmResult, IRevertOptions, IModeSupport } from 'vs/workbench/common/editor'; +import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; -import { ITextBufferFactory, ITextModel } from 'vs/editor/common/model'; +import { ITextBufferFactory, ITextModel, ITextSnapshot } from 'vs/editor/common/model'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; +import { isUndefinedOrNull } from 'vs/base/common/types'; -/** - * The save error handler can be installed on the text file editor model to install code that executes when save errors occur. - */ -export interface ISaveErrorHandler { - - /** - * Called whenever a save fails. - */ - onSaveError(error: Error, model: ITextFileEditorModel): void; -} - -export interface ISaveParticipant { - - /** - * Participate in a save of a model. Allows to change the model before it is being saved to disk. - */ - participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise; -} - -/** - * States the text file editor model can be in. - */ -export const enum ModelState { - SAVED, - DIRTY, - PENDING_SAVE, - - /** - * A model is in conflict mode when changes cannot be saved because the - * underlying file has changed. Models in conflict mode are always dirty. - */ - CONFLICT, - - /** - * A model is in orphan state when the underlying file has been deleted. - */ - ORPHAN, - - /** - * Any error that happens during a save that is not causing the CONFLICT state. - * Models in error mode are always diry. - */ - ERROR -} - -export const enum StateChange { - DIRTY, - SAVING, - SAVE_ERROR, - SAVED, - REVERTED, - ENCODING, - CONTENT_CHANGE, - ORPHANED_CHANGE -} - -export class TextFileModelChangeEvent { - private _resource: URI; - private _kind: StateChange; - - constructor(model: ITextFileEditorModel, kind: StateChange) { - this._resource = model.getResource(); - this._kind = kind; - } - - get resource(): URI { - return this._resource; - } - - get kind(): StateChange { - return this._kind; - } -} - -export const TEXT_FILE_SERVICE_ID = 'textFileService'; -export const AutoSaveContext = new RawContextKey('config.files.autoSave', undefined); - -export interface ITextFileOperationResult { - results: IResult[]; -} - -export interface IResult { - source: URI; - target?: URI; - success?: boolean; -} - -export interface IAutoSaveConfiguration { - autoSaveDelay?: number; - autoSaveFocusChange: boolean; - autoSaveApplicationChange: boolean; -} - -export const enum AutoSaveMode { - OFF, - AFTER_SHORT_DELAY, - AFTER_LONG_DELAY, - ON_FOCUS_CHANGE, - ON_WINDOW_CHANGE -} - -export const enum SaveReason { - EXPLICIT = 1, - AUTO = 2, - FOCUS_CHANGE = 3, - WINDOW_CHANGE = 4 -} - -export const enum LoadReason { - EDITOR = 1, - REFERENCE = 2, - OTHER = 3 -} - -export const ITextFileService = createDecorator(TEXT_FILE_SERVICE_ID); - -export interface IRawTextContent extends IBaseStatWithMetadata { - - /** - * The line grouped content of a text file. - */ - value: ITextBufferFactory; - - /** - * The encoding of the content if known. - */ - encoding: string; -} - -export interface IModelLoadOrCreateOptions { - - /** - * Context why the model is being loaded or created. - */ - reason?: LoadReason; - - /** - * The encoding to use when resolving the model text content. - */ - encoding?: string; - - /** - * If the model was already loaded before, allows to trigger - * a reload of it to fetch the latest contents: - * - async: loadOrCreate() will return immediately and trigger - * a reload that will run in the background. - * - sync: loadOrCreate() will only return resolved when the - * model has finished reloading. - */ - reload?: { - async: boolean - }; - - /** - * Allow to load a model even if we think it is a binary file. - */ - allowBinary?: boolean; -} - -export interface ITextFileEditorModelManager { - - onModelDisposed: Event; - onModelContentChanged: Event; - onModelEncodingChanged: Event; - - onModelDirty: Event; - onModelSaveError: Event; - onModelSaved: Event; - onModelReverted: Event; - onModelOrphanedChanged: Event; - - onModelsDirty: Event; - onModelsSaveError: Event; - onModelsSaved: Event; - onModelsReverted: Event; - - get(resource: URI): ITextFileEditorModel | undefined; - - getAll(resource?: URI): ITextFileEditorModel[]; - - loadOrCreate(resource: URI, options?: IModelLoadOrCreateOptions): Promise; - - disposeModel(model: ITextFileEditorModel): void; -} - -export interface ISaveOptions { - force?: boolean; - reason?: SaveReason; - overwriteReadonly?: boolean; - overwriteEncoding?: boolean; - skipSaveParticipants?: boolean; - writeElevated?: boolean; -} - -export interface ILoadOptions { - - /** - * Go to disk bypassing any cache of the model if any. - */ - forceReadFromDisk?: boolean; - - /** - * Allow to load a model even if we think it is a binary file. - */ - allowBinary?: boolean; - - /** - * Context why the model is being loaded. - */ - reason?: LoadReason; -} - -export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport { - - onDidContentChange: Event; - onDidStateChange: Event; - - getVersionId(): number; - - getResource(): URI; - - hasState(state: ModelState): boolean; - - getETag(): string | null; - - updatePreferredEncoding(encoding: string): void; - - save(options?: ISaveOptions): Promise; - - load(options?: ILoadOptions): Promise; - - revert(soft?: boolean): Promise; - - createSnapshot(): ITextSnapshot | null; - - isDirty(): boolean; - - isResolved(): boolean; - - isDisposed(): boolean; -} - -export interface IResolvedTextFileEditorModel extends ITextFileEditorModel { - readonly textEditorModel: ITextModel; - - createSnapshot(): ITextSnapshot; -} - - -export interface IWillMoveEvent { - oldResource: URI; - newResource: URI; - - waitUntil(p: Promise): void; -} +export const ITextFileService = createDecorator('textFileService'); export interface ITextFileService extends IDisposable { _serviceBrand: ServiceIdentifier; readonly onWillMove: Event; + readonly onAutoSaveConfigurationChange: Event; + readonly onFilesAssociationChange: Event; readonly isHotExitEnabled: boolean; @@ -284,6 +34,11 @@ export interface ITextFileService extends IDisposable { */ readonly models: ITextFileEditorModelManager; + /** + * Helper to determine encoding for resources. + */ + readonly encoding: IResourceEncodings; + /** * A resource is dirty if it has unsaved changes or is an untitled file not yet saved. * @@ -348,9 +103,14 @@ export interface ITextFileService extends IDisposable { create(resource: URI, contents?: string | ITextSnapshot, options?: { overwrite?: boolean }): Promise; /** - * Resolve the contents of a file identified by the resource. + * Read the contents of a file identified by the resource. */ - resolve(resource: URI, options?: IResolveContentOptions): Promise; + read(resource: URI, options?: IReadTextFileOptions): Promise; + + /** + * Read the contents of a file identified by the resource as stream. + */ + readStream(resource: URI, options?: IReadTextFileOptions): Promise; /** * Update a file with given contents. @@ -385,3 +145,661 @@ export interface ITextFileService extends IDisposable { */ getAutoSaveConfiguration(): IAutoSaveConfiguration; } + +export interface IReadTextFileOptions extends IReadFileOptions { + + /** + * The optional acceptTextOnly parameter allows to fail this request early if the file + * contents are not textual. + */ + acceptTextOnly?: boolean; + + /** + * The optional encoding parameter allows to specify the desired encoding when resolving + * the contents of the file. + */ + encoding?: string; + + /** + * The optional guessEncoding parameter allows to guess encoding from content of the file. + */ + autoGuessEncoding?: boolean; +} + +export interface IWriteTextFileOptions extends IWriteFileOptions { + + /** + * The encoding to use when updating a file. + */ + encoding?: string; + + /** + * If set to true, will enforce the selected encoding and not perform any detection using BOMs. + */ + overwriteEncoding?: boolean; + + /** + * Whether to overwrite a file even if it is readonly. + */ + overwriteReadonly?: boolean; + + /** + * Wether to write to the file as elevated (admin) user. When setting this option a prompt will + * ask the user to authenticate as super user. + */ + writeElevated?: boolean; +} + +export const enum TextFileOperationResult { + FILE_IS_BINARY +} + +export class TextFileOperationError extends FileOperationError { + constructor(message: string, public textFileOperationResult: TextFileOperationResult, public options?: IReadTextFileOptions & IWriteTextFileOptions) { + super(message, FileOperationResult.FILE_OTHER_ERROR); + } + + static isTextFileOperationError(obj: unknown): obj is TextFileOperationError { + return obj instanceof Error && !isUndefinedOrNull((obj as TextFileOperationError).textFileOperationResult); + } +} + +export interface IResourceEncodings { + getPreferredWriteEncoding(resource: URI, preferredEncoding?: string): IResourceEncoding; +} + +export interface IResourceEncoding { + encoding: string; + hasBOM: boolean; +} + +/** + * The save error handler can be installed on the text file editor model to install code that executes when save errors occur. + */ +export interface ISaveErrorHandler { + + /** + * Called whenever a save fails. + */ + onSaveError(error: Error, model: ITextFileEditorModel): void; +} + +export interface ISaveParticipant { + + /** + * Participate in a save of a model. Allows to change the model before it is being saved to disk. + */ + participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise; +} + +/** + * States the text file editor model can be in. + */ +export const enum ModelState { + + /** + * A model is saved. + */ + SAVED, + + /** + * A model is dirty. + */ + DIRTY, + + /** + * A model is transitioning from dirty to saved. + */ + PENDING_SAVE, + + /** + * A model is in conflict mode when changes cannot be saved because the + * underlying file has changed. Models in conflict mode are always dirty. + */ + CONFLICT, + + /** + * A model is in orphan state when the underlying file has been deleted. + */ + ORPHAN, + + /** + * Any error that happens during a save that is not causing the CONFLICT state. + * Models in error mode are always diry. + */ + ERROR +} + +export const enum StateChange { + DIRTY, + SAVING, + SAVE_ERROR, + SAVED, + REVERTED, + ENCODING, + CONTENT_CHANGE, + ORPHANED_CHANGE +} + +export class TextFileModelChangeEvent { + private _resource: URI; + + constructor(model: ITextFileEditorModel, private _kind: StateChange) { + this._resource = model.getResource(); + } + + get resource(): URI { + return this._resource; + } + + get kind(): StateChange { + return this._kind; + } +} + +export const AutoSaveContext = new RawContextKey('config.files.autoSave', undefined); + +export interface ITextFileOperationResult { + results: IResult[]; +} + +export interface IResult { + source: URI; + target?: URI; + success?: boolean; +} + +export interface IAutoSaveConfiguration { + autoSaveDelay?: number; + autoSaveFocusChange: boolean; + autoSaveApplicationChange: boolean; +} + +export const enum AutoSaveMode { + OFF, + AFTER_SHORT_DELAY, + AFTER_LONG_DELAY, + ON_FOCUS_CHANGE, + ON_WINDOW_CHANGE +} + +export const enum SaveReason { + EXPLICIT = 1, + AUTO = 2, + FOCUS_CHANGE = 3, + WINDOW_CHANGE = 4 +} + +export const enum LoadReason { + EDITOR = 1, + REFERENCE = 2, + OTHER = 3 +} + +interface IBaseTextFileContent extends IBaseStatWithMetadata { + + /** + * The encoding of the content if known. + */ + encoding: string; +} + +export interface ITextFileContent extends IBaseTextFileContent { + + /** + * The content of a text file. + */ + value: string; +} + +export interface ITextFileStreamContent extends IBaseTextFileContent { + + /** + * The line grouped content of a text file. + */ + value: ITextBufferFactory; +} + +export interface IModelLoadOrCreateOptions { + + /** + * Context why the model is being loaded or created. + */ + reason?: LoadReason; + + /** + * The language mode to use for the model text content. + */ + mode?: string; + + /** + * The encoding to use when resolving the model text content. + */ + encoding?: string; + + /** + * If the model was already loaded before, allows to trigger + * a reload of it to fetch the latest contents: + * - async: loadOrCreate() will return immediately and trigger + * a reload that will run in the background. + * - sync: loadOrCreate() will only return resolved when the + * model has finished reloading. + */ + reload?: { + async: boolean + }; + + /** + * Allow to load a model even if we think it is a binary file. + */ + allowBinary?: boolean; +} + +export interface ITextFileEditorModelManager { + + readonly onModelDisposed: Event; + readonly onModelContentChanged: Event; + readonly onModelEncodingChanged: Event; + + readonly onModelDirty: Event; + readonly onModelSaveError: Event; + readonly onModelSaved: Event; + readonly onModelReverted: Event; + readonly onModelOrphanedChanged: Event; + + readonly onModelsDirty: Event; + readonly onModelsSaveError: Event; + readonly onModelsSaved: Event; + readonly onModelsReverted: Event; + + get(resource: URI): ITextFileEditorModel | undefined; + + getAll(resource?: URI): ITextFileEditorModel[]; + + loadOrCreate(resource: URI, options?: IModelLoadOrCreateOptions): Promise; + + disposeModel(model: ITextFileEditorModel): void; +} + +export interface ISaveOptions { + force?: boolean; + reason?: SaveReason; + overwriteReadonly?: boolean; + overwriteEncoding?: boolean; + skipSaveParticipants?: boolean; + writeElevated?: boolean; +} + +export interface ILoadOptions { + + /** + * Go to disk bypassing any cache of the model if any. + */ + forceReadFromDisk?: boolean; + + /** + * Allow to load a model even if we think it is a binary file. + */ + allowBinary?: boolean; + + /** + * Context why the model is being loaded. + */ + reason?: LoadReason; +} + +export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport { + + readonly onDidContentChange: Event; + readonly onDidStateChange: Event; + + getResource(): URI; + + hasState(state: ModelState): boolean; + + updatePreferredEncoding(encoding: string): void; + + save(options?: ISaveOptions): Promise; + + load(options?: ILoadOptions): Promise; + + revert(soft?: boolean): Promise; + + backup(target?: URI): Promise; + + isDirty(): boolean; + + isResolved(): this is IResolvedTextFileEditorModel; + + isDisposed(): boolean; +} + +export interface IResolvedTextFileEditorModel extends ITextFileEditorModel { + + readonly textEditorModel: ITextModel; + + createSnapshot(): ITextSnapshot; +} + +export interface IWillMoveEvent { + oldResource: URI; + newResource: URI; + + waitUntil(p: Promise): void; +} + +/** + * Helper method to convert a snapshot into its full string form. + */ +export function snapshotToString(snapshot: ITextSnapshot): string { + const chunks: string[] = []; + + let chunk: string | null; + while (typeof (chunk = snapshot.read()) === 'string') { + chunks.push(chunk); + } + + return chunks.join(''); +} + +export function stringToSnapshot(value: string): ITextSnapshot { + let done = false; + + return { + read(): string | null { + if (!done) { + done = true; + + return value; + } + + return null; + } + }; +} + +export class TextSnapshotReadable implements VSBufferReadable { + private preambleHandled: boolean; + + constructor(private snapshot: ITextSnapshot, private preamble?: string) { } + + read(): VSBuffer | null { + let value = this.snapshot.read(); + + // Handle preamble if provided + if (!this.preambleHandled) { + this.preambleHandled = true; + + if (typeof this.preamble === 'string') { + if (typeof value === 'string') { + value = this.preamble + value; + } else { + value = this.preamble; + } + } + } + + if (typeof value === 'string') { + return VSBuffer.fromString(value); + } + + return null; + } +} + +export function toBufferOrReadable(value: string): VSBuffer; +export function toBufferOrReadable(value: ITextSnapshot): VSBufferReadable; +export function toBufferOrReadable(value: string | ITextSnapshot): VSBuffer | VSBufferReadable; +export function toBufferOrReadable(value: string | ITextSnapshot | undefined): VSBuffer | VSBufferReadable | undefined; +export function toBufferOrReadable(value: string | ITextSnapshot | undefined): VSBuffer | VSBufferReadable | undefined { + if (typeof value === 'undefined') { + return undefined; + } + + if (typeof value === 'string') { + return VSBuffer.fromString(value); + } + + return new TextSnapshotReadable(value); +} + +export const SUPPORTED_ENCODINGS: { [encoding: string]: { labelLong: string; labelShort: string; order: number; encodeOnly?: boolean; alias?: string } } = { + utf8: { + labelLong: 'UTF-8', + labelShort: 'UTF-8', + order: 1, + alias: 'utf8bom' + }, + utf8bom: { + labelLong: 'UTF-8 with BOM', + labelShort: 'UTF-8 with BOM', + encodeOnly: true, + order: 2, + alias: 'utf8' + }, + utf16le: { + labelLong: 'UTF-16 LE', + labelShort: 'UTF-16 LE', + order: 3 + }, + utf16be: { + labelLong: 'UTF-16 BE', + labelShort: 'UTF-16 BE', + order: 4 + }, + windows1252: { + labelLong: 'Western (Windows 1252)', + labelShort: 'Windows 1252', + order: 5 + }, + iso88591: { + labelLong: 'Western (ISO 8859-1)', + labelShort: 'ISO 8859-1', + order: 6 + }, + iso88593: { + labelLong: 'Western (ISO 8859-3)', + labelShort: 'ISO 8859-3', + order: 7 + }, + iso885915: { + labelLong: 'Western (ISO 8859-15)', + labelShort: 'ISO 8859-15', + order: 8 + }, + macroman: { + labelLong: 'Western (Mac Roman)', + labelShort: 'Mac Roman', + order: 9 + }, + cp437: { + labelLong: 'DOS (CP 437)', + labelShort: 'CP437', + order: 10 + }, + windows1256: { + labelLong: 'Arabic (Windows 1256)', + labelShort: 'Windows 1256', + order: 11 + }, + iso88596: { + labelLong: 'Arabic (ISO 8859-6)', + labelShort: 'ISO 8859-6', + order: 12 + }, + windows1257: { + labelLong: 'Baltic (Windows 1257)', + labelShort: 'Windows 1257', + order: 13 + }, + iso88594: { + labelLong: 'Baltic (ISO 8859-4)', + labelShort: 'ISO 8859-4', + order: 14 + }, + iso885914: { + labelLong: 'Celtic (ISO 8859-14)', + labelShort: 'ISO 8859-14', + order: 15 + }, + windows1250: { + labelLong: 'Central European (Windows 1250)', + labelShort: 'Windows 1250', + order: 16 + }, + iso88592: { + labelLong: 'Central European (ISO 8859-2)', + labelShort: 'ISO 8859-2', + order: 17 + }, + cp852: { + labelLong: 'Central European (CP 852)', + labelShort: 'CP 852', + order: 18 + }, + windows1251: { + labelLong: 'Cyrillic (Windows 1251)', + labelShort: 'Windows 1251', + order: 19 + }, + cp866: { + labelLong: 'Cyrillic (CP 866)', + labelShort: 'CP 866', + order: 20 + }, + iso88595: { + labelLong: 'Cyrillic (ISO 8859-5)', + labelShort: 'ISO 8859-5', + order: 21 + }, + koi8r: { + labelLong: 'Cyrillic (KOI8-R)', + labelShort: 'KOI8-R', + order: 22 + }, + koi8u: { + labelLong: 'Cyrillic (KOI8-U)', + labelShort: 'KOI8-U', + order: 23 + }, + iso885913: { + labelLong: 'Estonian (ISO 8859-13)', + labelShort: 'ISO 8859-13', + order: 24 + }, + windows1253: { + labelLong: 'Greek (Windows 1253)', + labelShort: 'Windows 1253', + order: 25 + }, + iso88597: { + labelLong: 'Greek (ISO 8859-7)', + labelShort: 'ISO 8859-7', + order: 26 + }, + windows1255: { + labelLong: 'Hebrew (Windows 1255)', + labelShort: 'Windows 1255', + order: 27 + }, + iso88598: { + labelLong: 'Hebrew (ISO 8859-8)', + labelShort: 'ISO 8859-8', + order: 28 + }, + iso885910: { + labelLong: 'Nordic (ISO 8859-10)', + labelShort: 'ISO 8859-10', + order: 29 + }, + iso885916: { + labelLong: 'Romanian (ISO 8859-16)', + labelShort: 'ISO 8859-16', + order: 30 + }, + windows1254: { + labelLong: 'Turkish (Windows 1254)', + labelShort: 'Windows 1254', + order: 31 + }, + iso88599: { + labelLong: 'Turkish (ISO 8859-9)', + labelShort: 'ISO 8859-9', + order: 32 + }, + windows1258: { + labelLong: 'Vietnamese (Windows 1258)', + labelShort: 'Windows 1258', + order: 33 + }, + gbk: { + labelLong: 'Simplified Chinese (GBK)', + labelShort: 'GBK', + order: 34 + }, + gb18030: { + labelLong: 'Simplified Chinese (GB18030)', + labelShort: 'GB18030', + order: 35 + }, + cp950: { + labelLong: 'Traditional Chinese (Big5)', + labelShort: 'Big5', + order: 36 + }, + big5hkscs: { + labelLong: 'Traditional Chinese (Big5-HKSCS)', + labelShort: 'Big5-HKSCS', + order: 37 + }, + shiftjis: { + labelLong: 'Japanese (Shift JIS)', + labelShort: 'Shift JIS', + order: 38 + }, + eucjp: { + labelLong: 'Japanese (EUC-JP)', + labelShort: 'EUC-JP', + order: 39 + }, + euckr: { + labelLong: 'Korean (EUC-KR)', + labelShort: 'EUC-KR', + order: 40 + }, + windows874: { + labelLong: 'Thai (Windows 874)', + labelShort: 'Windows 874', + order: 41 + }, + iso885911: { + labelLong: 'Latin/Thai (ISO 8859-11)', + labelShort: 'ISO 8859-11', + order: 42 + }, + koi8ru: { + labelLong: 'Cyrillic (KOI8-RU)', + labelShort: 'KOI8-RU', + order: 43 + }, + koi8t: { + labelLong: 'Tajik (KOI8-T)', + labelShort: 'KOI8-T', + order: 44 + }, + gb2312: { + labelLong: 'Simplified Chinese (GB 2312)', + labelShort: 'GB 2312', + order: 45 + }, + cp865: { + labelLong: 'Nordic DOS (CP 865)', + labelShort: 'CP 865', + order: 46 + }, + cp850: { + labelLong: 'Western European DOS (CP 850)', + labelShort: 'CP 850', + order: 47 + } +}; \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/node/textFileService.ts b/src/vs/workbench/services/textfile/node/textFileService.ts index 87c164018a1..2e9fe43b8a2 100644 --- a/src/vs/workbench/services/textfile/node/textFileService.ts +++ b/src/vs/workbench/services/textfile/node/textFileService.ts @@ -6,30 +6,32 @@ import { tmpdir } from 'os'; import { localize } from 'vs/nls'; import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ITextFileStreamContent, ITextFileContent, IResourceEncodings, IResourceEncoding, IReadTextFileOptions, IWriteTextFileOptions, stringToSnapshot, TextFileOperationResult, TextFileOperationError } from 'vs/workbench/services/textfile/common/textfiles'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { URI } from 'vs/base/common/uri'; -import { ITextSnapshot, IWriteTextFileOptions, IFileStatWithMetadata, IResourceEncoding, IResolveContentOptions, stringToSnapshot, ICreateFileOptions, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; +import { IFileStatWithMetadata, ICreateFileOptions, FileOperationError, FileOperationResult, IFileStreamContent, IFileService } from 'vs/platform/files/common/files'; import { Schemas } from 'vs/base/common/network'; -import { exists, stat, chmod, rimraf } from 'vs/base/node/pfs'; +import { exists, stat, chmod, rimraf, MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/base/node/pfs'; import { join, dirname } from 'vs/base/common/path'; import { isMacintosh, isLinux } from 'vs/base/common/platform'; import product from 'vs/platform/product/node/product'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, IDetectedEncodingResult, detectEncodingByBOM, encodeStream, UTF8_BOM, UTF16be_BOM, UTF16le_BOM } from 'vs/base/node/encoding'; +import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, encodeStream, UTF8_BOM, UTF16be_BOM, UTF16le_BOM, toDecodeStream, IDecodeStreamResult, detectEncodingByBOMFromBuffer } from 'vs/base/node/encoding'; import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { joinPath, extname, isEqualOrParent } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { VSBufferReadable, VSBuffer } from 'vs/base/common/buffer'; +import { VSBufferReadable, VSBuffer, VSBufferReadableStream } from 'vs/base/common/buffer'; import { Readable } from 'stream'; import { isUndefinedOrNull } from 'vs/base/common/types'; +import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; +import { ITextSnapshot } from 'vs/editor/common/model'; export class NodeTextFileService extends TextFileService { private _encoding: EncodingOracle; - protected get encoding(): EncodingOracle { + get encoding(): EncodingOracle { if (!this._encoding) { this._encoding = this._register(this.instantiationService.createInstance(EncodingOracle)); } @@ -37,6 +39,129 @@ export class NodeTextFileService extends TextFileService { return this._encoding; } + async read(resource: URI, options?: IReadTextFileOptions): Promise { + const [bufferStream, decoder] = await this.doRead(resource, options); + + return { + ...bufferStream, + encoding: decoder.detected.encoding || UTF8, + value: await this.nodeReadableToString(decoder.stream) + }; + } + + async readStream(resource: URI, options?: IReadTextFileOptions): Promise { + const [bufferStream, decoder] = await this.doRead(resource, options); + + return { + ...bufferStream, + encoding: decoder.detected.encoding || UTF8, + value: await createTextBufferFactoryFromStream(decoder.stream) + }; + } + + private async doRead(resource: URI, options?: IReadTextFileOptions): Promise<[IFileStreamContent, IDecodeStreamResult]> { + + // ensure limits + options = this.ensureLimits(options); + + // read stream raw + const bufferStream = await this.fileService.readFileStream(resource, options); + + // read through encoding library + const decoder = await toDecodeStream(this.streamToNodeReadable(bufferStream.value), { + guessEncoding: (options && options.autoGuessEncoding) || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding'), + overwriteEncoding: detectedEncoding => this.encoding.getReadEncoding(resource, options, detectedEncoding) + }); + + // validate binary + if (options && options.acceptTextOnly && decoder.detected.seemsBinary) { + throw new TextFileOperationError(localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options); + } + + return [bufferStream, decoder]; + } + + private ensureLimits(options?: IReadTextFileOptions): IReadTextFileOptions { + let ensuredOptions: IReadTextFileOptions; + if (!options) { + ensuredOptions = Object.create(null); + } else { + ensuredOptions = options; + } + + let ensuredLimits: { size?: number; memory?: number; }; + if (!ensuredOptions.limits) { + ensuredLimits = Object.create(null); + ensuredOptions.limits = ensuredLimits; + } else { + ensuredLimits = ensuredOptions.limits; + } + + if (typeof ensuredLimits.size !== 'number') { + ensuredLimits.size = MAX_FILE_SIZE; + } + + if (typeof ensuredLimits.memory !== 'number') { + ensuredLimits.memory = Math.max(typeof this.environmentService.args['max-memory'] === 'string' ? parseInt(this.environmentService.args['max-memory']) * 1024 * 1024 || 0 : 0, MAX_HEAP_SIZE); + } + + return ensuredOptions; + } + + private streamToNodeReadable(stream: VSBufferReadableStream): Readable { + return new class extends Readable { + private listening = false; + + _read(size?: number): void { + if (!this.listening) { + this.listening = true; + + // Data + stream.on('data', data => { + try { + if (!this.push(data.buffer)) { + stream.pause(); // pause the stream if we should not push anymore + } + } catch (error) { + this.emit(error); + } + }); + + // End + stream.on('end', () => { + try { + this.push(null); // signal EOS + } catch (error) { + this.emit(error); + } + }); + + // Error + stream.on('error', error => this.emit(error)); + } + + // ensure the stream is flowing + stream.resume(); + } + + _destroy(error: Error | null, callback: (error: Error | null) => void): void { + stream.destroy(); + + callback(null); + } + }; + } + + private nodeReadableToString(stream: NodeJS.ReadableStream): Promise { + return new Promise((resolve, reject) => { + let result = ''; + + stream.on('data', chunk => result += chunk); + stream.on('error', reject); + stream.on('end', () => resolve(result)); + }); + } + protected async doCreate(resource: URI, value?: string, options?: ICreateFileOptions): Promise { // check for encoding @@ -108,22 +233,15 @@ export class NodeTextFileService extends TextFileService { } private getEncodedReadable(value: string | ITextSnapshot, encoding: string, addBOM: boolean): VSBufferReadable { - const readable = this.toNodeReadable(value); + const readable = this.snapshotToNodeReadable(typeof value === 'string' ? stringToSnapshot(value) : value); const encoder = encodeStream(encoding, { addBOM }); const encodedReadable = readable.pipe(encoder); - return this.toBufferReadable(encodedReadable, encoding, addBOM); + return this.nodeStreamToReadable(encodedReadable, encoding, addBOM); } - private toNodeReadable(value: string | ITextSnapshot): Readable { - let snapshot: ITextSnapshot; - if (typeof value === 'string') { - snapshot = stringToSnapshot(value); - } else { - snapshot = value; - } - + private snapshotToNodeReadable(snapshot: ITextSnapshot): Readable { return new Readable({ read: function () { try { @@ -148,7 +266,7 @@ export class NodeTextFileService extends TextFileService { }); } - private toBufferReadable(stream: NodeJS.ReadWriteStream, encoding: string, addBOM: boolean): VSBufferReadable { + private nodeStreamToReadable(stream: NodeJS.ReadWriteStream, encoding: string, addBOM: boolean): VSBufferReadable { let bytesRead = 0; let done = false; @@ -246,13 +364,14 @@ export interface IEncodingOverride { encoding: string; } -export class EncodingOracle extends Disposable { +export class EncodingOracle extends Disposable implements IResourceEncodings { protected encodingOverrides: IEncodingOverride[]; constructor( @ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService, @IEnvironmentService private environmentService: IEnvironmentService, - @IWorkspaceContextService private contextService: IWorkspaceContextService + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IFileService private fileService: IFileService ) { super(); @@ -285,7 +404,7 @@ export class EncodingOracle extends Disposable { } async getWriteEncoding(resource: URI, options?: IWriteTextFileOptions): Promise<{ encoding: string, addBOM: boolean }> { - const { encoding, hasBOM } = this.doGetWriteEncoding(resource, options ? options.encoding : undefined); + const { encoding, hasBOM } = this.getPreferredWriteEncoding(resource, options ? options.encoding : undefined); // Some encodings come with a BOM automatically if (hasBOM) { @@ -295,14 +414,21 @@ export class EncodingOracle extends Disposable { // Ensure that we preserve an existing BOM if found for UTF8 // unless we are instructed to overwrite the encoding const overwriteEncoding = options && options.overwriteEncoding; - if (!overwriteEncoding && encoding === UTF8 && resource.scheme === Schemas.file && await detectEncodingByBOM(resource.fsPath) === UTF8) { - return { encoding, addBOM: true }; + if (!overwriteEncoding && encoding === UTF8) { + try { + const buffer = (await this.fileService.readFile(resource, { length: UTF8_BOM.length })).value; + if (detectEncodingByBOMFromBuffer(buffer, buffer.byteLength) === UTF8) { + return { encoding, addBOM: true }; + } + } catch (error) { + // ignore - file might not exist + } } return { encoding, addBOM: false }; } - private doGetWriteEncoding(resource: URI, preferredEncoding?: string): IResourceEncoding { + getPreferredWriteEncoding(resource: URI, preferredEncoding?: string): IResourceEncoding { const resourceEncoding = this.getEncodingForResource(resource, preferredEncoding); return { @@ -311,12 +437,12 @@ export class EncodingOracle extends Disposable { }; } - getReadEncoding(resource: URI, options: IResolveContentOptions | undefined, detected: IDetectedEncodingResult): string { + getReadEncoding(resource: URI, options: IReadTextFileOptions | undefined, detectedEncoding: string | null): string { let preferredEncoding: string | undefined; // Encoding passed in as option if (options && options.encoding) { - if (detected.encoding === UTF8 && options.encoding === UTF8) { + if (detectedEncoding === UTF8 && options.encoding === UTF8) { preferredEncoding = UTF8_with_bom; // indicate the file has BOM if we are to resolve with UTF 8 } else { preferredEncoding = options.encoding; // give passed in encoding highest priority @@ -324,11 +450,11 @@ export class EncodingOracle extends Disposable { } // Encoding detected - else if (detected.encoding) { - if (detected.encoding === UTF8) { + else if (detectedEncoding) { + if (detectedEncoding === UTF8) { preferredEncoding = UTF8_with_bom; // if we detected UTF-8, it can only be because of a BOM } else { - preferredEncoding = detected.encoding; + preferredEncoding = detectedEncoding; } } diff --git a/src/vs/workbench/services/textfile/node/textResourcePropertiesService.ts b/src/vs/workbench/services/textfile/node/textResourcePropertiesService.ts index e9a71315bea..ce097d6fdf5 100644 --- a/src/vs/workbench/services/textfile/node/textResourcePropertiesService.ts +++ b/src/vs/workbench/services/textfile/node/textResourcePropertiesService.ts @@ -17,7 +17,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio export class TextResourcePropertiesService implements ITextResourcePropertiesService { - _serviceBrand: ServiceIdentifier; + _serviceBrand: ServiceIdentifier; private remoteEnvironment: IRemoteAgentEnvironment | null = null; diff --git a/src/vs/workbench/services/files2/test/node/fixtures/service/binary.txt b/src/vs/workbench/services/textfile/test/fixtures/binary.txt similarity index 100% rename from src/vs/workbench/services/files2/test/node/fixtures/service/binary.txt rename to src/vs/workbench/services/textfile/test/fixtures/binary.txt diff --git a/src/vs/workbench/services/textfile/test/fixtures/lorem_big5.txt b/src/vs/workbench/services/textfile/test/fixtures/lorem_big5.txt new file mode 100644 index 00000000000..91b79941f16 --- /dev/null +++ b/src/vs/workbench/services/textfile/test/fixtures/lorem_big5.txt @@ -0,0 +1,283 @@ +¤¤¤åabc Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. + +Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. + +Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. + +Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. + +Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. + +Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. + +Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. + +Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. + +Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. + +Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. + +Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. + +Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. + +Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. + +Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. + +Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. + +Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. + +Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. + +Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. + +Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. + +Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. + +Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. + +Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. + +Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. + +Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. + +Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. + +Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. + +Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. + +In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. + +Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. + +Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. + +Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. + +Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. + +Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. + +Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. + +Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. + +Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. + +Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. + +Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. + +Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. + +Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. + +Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. + +Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. + +Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. + +Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. + +Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. + +Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. + +Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. + +Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. + +Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. + +Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. + +Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. + +Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. + +Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. + +Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. + +Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. + +Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. + +Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. + +Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. + +Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. + +Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. + +Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. + +Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. + +Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. + +Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. + +Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. + +Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. + +Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. + +Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. + +Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. + +Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. + +Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. + +Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. + +Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. + +Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. + +Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. + +Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. + +Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. + +Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. + +Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. + +Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. + +Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. + +Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. + +Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. + +Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. + +Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. + +Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. + +Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. + +Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. + +Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. + +Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. + +In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. + +Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. + +Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. + +Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. + +Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. + +Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. + +In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. + +Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. + +Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. + +Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. + +Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. + +Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. + +Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. + +Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. + +Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. + +Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. + +Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. + +Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. + +Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. + +Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. + +Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. + +Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. + +Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. + +Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. + +Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. + +Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. + +Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. + +Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. + +Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. + +Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. + +Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. + +Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. + +Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. + +Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. + +Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. + +Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. + +Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. + +Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. + +Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. + +Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. + +In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. + +Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. + +Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. + +Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. + +¤¤¤åabc Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file diff --git a/src/vs/workbench/services/files2/test/node/fixtures/service/lorem.txt b/src/vs/workbench/services/textfile/test/fixtures/lorem_cp1252.txt similarity index 98% rename from src/vs/workbench/services/files2/test/node/fixtures/service/lorem.txt rename to src/vs/workbench/services/textfile/test/fixtures/lorem_cp1252.txt index 9d348ac0901..f56b7cf1fcb 100644 --- a/src/vs/workbench/services/files2/test/node/fixtures/service/lorem.txt +++ b/src/vs/workbench/services/textfile/test/fixtures/lorem_cp1252.txt @@ -1,4 +1,4 @@ -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. +öäüß Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. @@ -280,4 +280,4 @@ Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesq Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. -Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file +öäüß Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/fixtures/lorem_cp866.txt b/src/vs/workbench/services/textfile/test/fixtures/lorem_cp866.txt new file mode 100644 index 00000000000..b11625814d9 --- /dev/null +++ b/src/vs/workbench/services/textfile/test/fixtures/lorem_cp866.txt @@ -0,0 +1,283 @@ +€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯àáâãäåæçèéêëìíîï Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. + +Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. + +Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. + +Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. + +Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. + +Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. + +Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. + +Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. + +Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. + +Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. + +Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. + +Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. + +Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. + +Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. + +Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. + +Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. + +Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. + +Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. + +Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. + +Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. + +Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. + +Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. + +Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. + +Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. + +Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. + +Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. + +Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. + +In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. + +Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. + +Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. + +Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. + +Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. + +Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. + +Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. + +Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. + +Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. + +Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. + +Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. + +Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. + +Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. + +Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. + +Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. + +Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. + +Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. + +Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. + +Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. + +Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. + +Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. + +Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. + +Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. + +Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. + +Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. + +Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. + +Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. + +Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. + +Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. + +Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. + +Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. + +Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. + +Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. + +Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. + +Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. + +Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. + +Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. + +Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. + +Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. + +Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. + +Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. + +Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. + +Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. + +Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. + +Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. + +Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. + +Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. + +Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. + +Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. + +Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. + +Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. + +Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. + +Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. + +Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. + +Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. + +Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. + +Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. + +Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. + +Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. + +Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. + +Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. + +Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. + +Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. + +In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. + +Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. + +Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. + +Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. + +Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. + +Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. + +In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. + +Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. + +Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. + +Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. + +Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. + +Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. + +Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. + +Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. + +Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. + +Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. + +Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. + +Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. + +Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. + +Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. + +Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. + +Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. + +Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. + +Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. + +Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. + +Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. + +Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. + +Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. + +Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. + +Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. + +Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. + +Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. + +Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. + +Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. + +Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. + +Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. + +Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. + +Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. + +Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. + +Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. + +In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. + +Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. + +Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. + +Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. + +€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯àáâãäåæçèéêëìíîï Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/fixtures/lorem_gbk.txt b/src/vs/workbench/services/textfile/test/fixtures/lorem_gbk.txt new file mode 100644 index 00000000000..2e1de224145 --- /dev/null +++ b/src/vs/workbench/services/textfile/test/fixtures/lorem_gbk.txt @@ -0,0 +1,283 @@ +Öйúabc Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. + +Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. + +Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. + +Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. + +Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. + +Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. + +Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. + +Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. + +Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. + +Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. + +Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. + +Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. + +Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. + +Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. + +Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. + +Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. + +Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. + +Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. + +Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. + +Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. + +Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. + +Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. + +Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. + +Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. + +Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. + +Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. + +Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. + +In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. + +Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. + +Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. + +Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. + +Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. + +Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. + +Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. + +Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. + +Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. + +Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. + +Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. + +Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. + +Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. + +Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. + +Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. + +Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. + +Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. + +Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. + +Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. + +Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. + +Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. + +Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. + +Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. + +Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. + +Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. + +Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. + +Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. + +Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. + +Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. + +Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. + +Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. + +Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. + +Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. + +Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. + +Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. + +Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. + +Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. + +Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. + +Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. + +Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. + +Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. + +Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. + +Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. + +Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. + +Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. + +Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. + +Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. + +Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. + +Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. + +Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. + +Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. + +Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. + +Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. + +Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. + +Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. + +Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. + +Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. + +Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. + +Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. + +Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. + +Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. + +Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. + +Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. + +In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. + +Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. + +Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. + +Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. + +Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. + +Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. + +In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. + +Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. + +Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. + +Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. + +Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. + +Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. + +Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. + +Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. + +Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. + +Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. + +Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. + +Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. + +Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. + +Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. + +Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. + +Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. + +Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. + +Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. + +Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. + +Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. + +Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. + +Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. + +Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. + +Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. + +Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. + +Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. + +Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. + +Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. + +Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. + +Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. + +Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. + +Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. + +Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. + +Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. + +In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. + +Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. + +Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. + +Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. + +Öйúabc Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/fixtures/lorem_shiftjis.txt b/src/vs/workbench/services/textfile/test/fixtures/lorem_shiftjis.txt new file mode 100644 index 00000000000..3e106d9df47 --- /dev/null +++ b/src/vs/workbench/services/textfile/test/fixtures/lorem_shiftjis.txt @@ -0,0 +1,283 @@ +’†•¶abc Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. + +Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. + +Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. + +Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. + +Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. + +Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. + +Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. + +Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. + +Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. + +Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. + +Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. + +Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. + +Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. + +Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. + +Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. + +Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. + +Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. + +Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. + +Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. + +Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. + +Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. + +Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. + +Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. + +Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. + +Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. + +Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. + +Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. + +In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. + +Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. + +Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. + +Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. + +Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. + +Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. + +Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. + +Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. + +Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. + +Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. + +Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. + +Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. + +Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. + +Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. + +Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. + +Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. + +Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. + +Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. + +Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. + +Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. + +Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. + +Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. + +Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. + +Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. + +Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. + +Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. + +Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. + +Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. + +Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. + +Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. + +Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. + +Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. + +Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. + +Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. + +Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. + +Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. + +Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. + +Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. + +Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. + +Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. + +Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. + +Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. + +Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. + +Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. + +Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. + +Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. + +Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. + +Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. + +Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. + +Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. + +Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. + +Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. + +Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. + +Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. + +Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. + +Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. + +Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. + +Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. + +Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. + +Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. + +Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. + +Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. + +Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. + +In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. + +Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. + +Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. + +Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. + +Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. + +Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. + +In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. + +Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. + +Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. + +Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. + +Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. + +Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. + +Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. + +Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. + +Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. + +Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. + +Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. + +Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. + +Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. + +Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. + +Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. + +Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. + +Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. + +Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. + +Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. + +Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. + +Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. + +Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. + +Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. + +Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. + +Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. + +Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. + +Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. + +Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. + +Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. + +Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. + +Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. + +Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. + +Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. + +Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. + +In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. + +Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. + +Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. + +Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. + +’†•¶abc Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/fixtures/lorem_utf16be.txt b/src/vs/workbench/services/textfile/test/fixtures/lorem_utf16be.txt new file mode 100644 index 00000000000..468eee80007 Binary files /dev/null and b/src/vs/workbench/services/textfile/test/fixtures/lorem_utf16be.txt differ diff --git a/src/vs/workbench/services/textfile/test/fixtures/lorem_utf16le.txt b/src/vs/workbench/services/textfile/test/fixtures/lorem_utf16le.txt new file mode 100644 index 00000000000..b9a7dff430d Binary files /dev/null and b/src/vs/workbench/services/textfile/test/fixtures/lorem_utf16le.txt differ diff --git a/src/vs/workbench/services/textfile/test/fixtures/lorem_utf8bom.txt b/src/vs/workbench/services/textfile/test/fixtures/lorem_utf8bom.txt new file mode 100644 index 00000000000..edd30bae3c9 --- /dev/null +++ b/src/vs/workbench/services/textfile/test/fixtures/lorem_utf8bom.txt @@ -0,0 +1,283 @@ +öäüß Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. + +Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. + +Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. + +Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. + +Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. + +Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. + +Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. + +Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. + +Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. + +Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. + +Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. + +Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. + +Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. + +Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. + +Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. + +Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. + +Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. + +Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. + +Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. + +Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. + +Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. + +Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. + +Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. + +Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. + +Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. + +Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. + +Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. + +In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. + +Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. + +Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. + +Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. + +Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. + +Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. + +Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. + +Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. + +Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. + +Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. + +Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. + +Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. + +Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. + +Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. + +Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. + +Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. + +Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. + +Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. + +Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. + +Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. + +Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. + +Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. + +Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. + +Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. + +Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. + +Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. + +Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. + +Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. + +Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. + +Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. + +Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. + +Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. + +Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. + +Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. + +Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. + +Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. + +Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. + +Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. + +Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. + +Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. + +Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. + +Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. + +Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. + +Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. + +Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. + +Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. + +Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. + +Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. + +Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. + +Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. + +Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. + +Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. + +Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. + +Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. + +Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. + +Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. + +Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. + +Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. + +Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. + +Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. + +Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. + +Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. + +Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. + +Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. + +Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. + +Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. + +In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. + +Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. + +Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. + +Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. + +Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. + +Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. + +In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. + +Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. + +Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. + +Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. + +Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. + +Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. + +Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. + +Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. + +Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. + +Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. + +Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. + +Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. + +Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. + +Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. + +Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. + +Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. + +Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. + +Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. + +Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. + +Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. + +Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. + +Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. + +Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. + +Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. + +Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. + +Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. + +Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. + +Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. + +Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. + +Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. + +Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. + +Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. + +Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. + +Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. + +In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. + +Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. + +Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. + +Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. + +Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. + +öäüß Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/fixtures/some.utf16le b/src/vs/workbench/services/textfile/test/fixtures/some.utf16le new file mode 100644 index 00000000000..41c12add670 Binary files /dev/null and b/src/vs/workbench/services/textfile/test/fixtures/some.utf16le differ diff --git a/src/vs/workbench/services/textfile/test/fixtures/some_small_cp1252.txt b/src/vs/workbench/services/textfile/test/fixtures/some_small_cp1252.txt new file mode 100644 index 00000000000..0ad555462f2 --- /dev/null +++ b/src/vs/workbench/services/textfile/test/fixtures/some_small_cp1252.txt @@ -0,0 +1 @@ +Private = "Persönlicheß Information" \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/fixtures/utf16_be_nobom.txt b/src/vs/workbench/services/textfile/test/fixtures/utf16_be_nobom.txt new file mode 100644 index 00000000000..63c29412093 Binary files /dev/null and b/src/vs/workbench/services/textfile/test/fixtures/utf16_be_nobom.txt differ diff --git a/src/vs/workbench/services/textfile/test/fixtures/utf16_le_nobom.txt b/src/vs/workbench/services/textfile/test/fixtures/utf16_le_nobom.txt new file mode 100644 index 00000000000..7b94ff215b0 Binary files /dev/null and b/src/vs/workbench/services/textfile/test/fixtures/utf16_le_nobom.txt differ diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index 8daf875068a..e22cbe034db 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -7,13 +7,14 @@ import * as assert from 'assert'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EncodingMode } from 'vs/workbench/common/editor'; import { TextFileEditorModel, SaveSequentializer } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, ModelState, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ModelState, StateChange, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { workbenchInstantiationService, TestTextFileService, createFileInput, TestFileService } from 'vs/workbench/test/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { FileOperationResult, FileOperationError, IFileService, snapshotToString } from 'vs/platform/files/common/files'; +import { FileOperationResult, FileOperationError, IFileService } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout } from 'vs/base/common/async'; +import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; class ServiceAccessor { constructor(@ITextFileService public textFileService: TestTextFileService, @IModelService public modelService: IModelService, @IFileService public fileService: TestFileService) { @@ -44,25 +45,53 @@ suite('Files - TextFileEditorModel', () => { accessor.fileService.setContent(content); }); - test('Save', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + test('save', async function () { + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); await model.load(); model.textEditorModel!.setValue('bar'); assert.ok(getLastModifiedTime(model) <= Date.now()); + let savedEvent = false; + model.onDidStateChange(e => { + if (e === StateChange.SAVED) { + savedEvent = true; + } + }); + await model.save(); assert.ok(model.getLastSaveAttemptTime() <= Date.now()); assert.ok(!model.isDirty()); + assert.ok(savedEvent); + + model.dispose(); + assert.ok(!accessor.modelService.getModel(model.getResource())); + }); + + test('save - touching also emits saved event', async function () { + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + + await model.load(); + + let savedEvent = false; + model.onDidStateChange(e => { + if (e === StateChange.SAVED) { + savedEvent = true; + } + }); + + await model.save({ force: true }); + + assert.ok(savedEvent); model.dispose(); assert.ok(!accessor.modelService.getModel(model.getResource())); }); test('setEncoding - encode', function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); model.setEncoding('utf8', EncodingMode.Encode); // no-op assert.equal(getLastModifiedTime(model), -1); @@ -75,7 +104,7 @@ suite('Files - TextFileEditorModel', () => { }); test('setEncoding - decode', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); model.setEncoding('utf16', EncodingMode.Decode); @@ -84,8 +113,24 @@ suite('Files - TextFileEditorModel', () => { model.dispose(); }); + test('create with mode', async function () { + const mode = 'text-file-model-test'; + ModesRegistry.registerLanguage({ + id: mode, + }); + + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', mode); + + await model.load(); + + assert.equal(model.textEditorModel!.getModeId(), mode); + + model.dispose(); + assert.ok(!accessor.modelService.getModel(model.getResource())); + }); + test('disposes when underlying model is destroyed', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); await model.load(); @@ -94,7 +139,7 @@ suite('Files - TextFileEditorModel', () => { }); test('Load does not trigger save', async function () { - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined); assert.ok(model.hasState(ModelState.SAVED)); model.onDidStateChange(e => { @@ -108,7 +153,7 @@ suite('Files - TextFileEditorModel', () => { }); test('Load returns dirty model as long as model is dirty', async function () { - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); await model.load(); model.textEditorModel!.setValue('foo'); @@ -123,7 +168,7 @@ suite('Files - TextFileEditorModel', () => { test('Revert', async function () { let eventCounter = 0; - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); model.onDidStateChange(e => { if (e === StateChange.REVERTED) { @@ -145,7 +190,7 @@ suite('Files - TextFileEditorModel', () => { test('Revert (soft)', async function () { let eventCounter = 0; - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); model.onDidStateChange(e => { if (e === StateChange.REVERTED) { @@ -165,7 +210,7 @@ suite('Files - TextFileEditorModel', () => { }); test('Load and undo turns model dirty', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); await model.load(); accessor.fileService.setContent('Hello Change'); @@ -175,7 +220,7 @@ suite('Files - TextFileEditorModel', () => { }); test('File not modified error is handled gracefully', async function () { - let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); await model.load(); @@ -190,7 +235,7 @@ suite('Files - TextFileEditorModel', () => { }); test('Load error is handled gracefully if model already exists', async function () { - let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); await model.load(); accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_FOUND)); @@ -236,7 +281,7 @@ suite('Files - TextFileEditorModel', () => { test('Save Participant', async function () { let eventCounter = 0; - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); model.onDidStateChange(e => { if (e === StateChange.SAVED) { @@ -266,7 +311,7 @@ suite('Files - TextFileEditorModel', () => { test('Save Participant, async participant', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); TextFileEditorModel.setSaveParticipant({ participate: (model) => { @@ -284,7 +329,7 @@ suite('Files - TextFileEditorModel', () => { }); test('Save Participant, bad participant', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); TextFileEditorModel.setSaveParticipant({ participate: (model) => { diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index 15be3b8533f..1b5f490f490 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -13,6 +13,7 @@ import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/file import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout } from 'vs/base/common/async'; import { toResource } from 'vs/base/test/common/utils'; +import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; export class TestTextFileEditorModelManager extends TextFileEditorModelManager { @@ -42,9 +43,9 @@ suite('Files - TextFileEditorModelManager', () => { test('add, remove, clear, get, getAll', function () { const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8'); - const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8'); - const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8'); + const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined); + const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined); + const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined); manager.add(URI.file('/test.html'), model1); manager.add(URI.file('/some/other.html'), model2); @@ -117,9 +118,9 @@ suite('Files - TextFileEditorModelManager', () => { test('removed from cache when model disposed', function () { const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8'); - const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8'); - const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8'); + const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined); + const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined); + const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined); manager.add(URI.file('/test.html'), model1); manager.add(URI.file('/some/other.html'), model2); @@ -290,4 +291,24 @@ suite('Files - TextFileEditorModelManager', () => { assert.ok(model.isDisposed()); manager.dispose(); }); + + test('mode', async function () { + const mode = 'text-file-model-manager-test'; + ModesRegistry.registerLanguage({ + id: mode, + }); + + const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); + + const resource = toResource.call(this, '/path/index_something.txt'); + + let model = await manager.loadOrCreate(resource, { mode }); + assert.equal(model.textEditorModel!.getModeId(), mode); + + model = await manager.loadOrCreate(resource, { mode: 'text' }); + assert.equal(model.textEditorModel!.getModeId(), PLAINTEXT_MODE_ID); + + manager.disposeModel((model as TextFileEditorModel)); + manager.dispose(); + }); }); \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts index 65b968a90c3..12d985b1825 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts @@ -5,11 +5,11 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService, TestFileService, TestEnvironmentService, TestTextResourceConfigurationService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, snapshotToString, TextFileOperationResult, TextFileOperationError } from 'vs/workbench/services/textfile/common/textfiles'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { IFileService, ITextSnapshot, snapshotToString } from 'vs/platform/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -18,20 +18,21 @@ import { Schemas } from 'vs/base/common/network'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { rimraf, RimRafMode, copy, readFile, exists } from 'vs/base/node/pfs'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { FileService2 } from 'vs/workbench/services/files2/common/fileService2'; +import { FileService } from 'vs/workbench/services/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { tmpdir } from 'os'; -import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider'; +import { DiskFileSystemProvider } from 'vs/workbench/services/files/node/diskFileSystemProvider'; import { generateUuid } from 'vs/base/common/uuid'; -import { join } from 'vs/base/common/path'; +import { join, basename } from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { detectEncodingByBOM, UTF16be, UTF16le, UTF8_with_bom, UTF8 } from 'vs/base/node/encoding'; +import { UTF16be, UTF16le, UTF8_with_bom, UTF8 } from 'vs/base/node/encoding'; import { NodeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/node/textFileService'; -import { LegacyFileService } from 'vs/workbench/services/files/node/fileService'; -import { DefaultEndOfLine } from 'vs/editor/common/model'; +import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { isWindows } from 'vs/base/common/platform'; +import { readFileSync, statSync } from 'fs'; +import { detectEncodingByBOM } from 'vs/base/test/node/encoding/encoding.test'; class ServiceAccessor { constructor( @@ -49,7 +50,7 @@ class ServiceAccessor { class TestNodeTextFileService extends NodeTextFileService { private _testEncoding: TestEncodingOracle; - protected get encoding(): TestEncodingOracle { + get encoding(): TestEncodingOracle { if (!this._testEncoding) { this._testEncoding = this._register(this.instantiationService.createInstance(TestEncodingOracle)); } @@ -84,19 +85,12 @@ suite('Files - TextFileService i/o', () => { accessor = instantiationService.createInstance(ServiceAccessor); const logService = new NullLogService(); - const fileService = new FileService2(logService); + const fileService = new FileService(logService); const fileProvider = new DiskFileSystemProvider(logService); disposables.push(fileService.registerProvider(Schemas.file, fileProvider)); disposables.push(fileProvider); - fileService.setLegacyService(new LegacyFileService( - fileService, - accessor.contextService, - TestEnvironmentService, - new TestTextResourceConfigurationService() - )); - const collection = new ServiceCollection(); collection.set(IFileService, fileService); @@ -246,7 +240,7 @@ suite('Files - TextFileService i/o', () => { const detectedEncoding = await detectEncodingByBOM(resource.fsPath); assert.equal(detectedEncoding, encoding); - const resolved = await service.resolve(resource); + const resolved = await service.readStream(resource); assert.equal(resolved.encoding, encoding); assert.equal(snapshotToString(resolved.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(false)), expectedContent); @@ -273,18 +267,18 @@ suite('Files - TextFileService i/o', () => { }); async function testEncodingKeepsData(resource: URI, encoding: string, expected: string) { - let resolved = await service.resolve(resource, { encoding }); + let resolved = await service.readStream(resource, { encoding }); const content = snapshotToString(resolved.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(false)); assert.equal(content, expected); await service.write(resource, content, { encoding }); - resolved = await service.resolve(resource, { encoding }); + resolved = await service.readStream(resource, { encoding }); assert.equal(snapshotToString(resolved.value.create(DefaultEndOfLine.CRLF).createSnapshot(false)), content); await service.write(resource, TextModel.createFromString(content).createSnapshot(), { encoding }); - resolved = await service.resolve(resource, { encoding }); + resolved = await service.readStream(resource, { encoding }); assert.equal(snapshotToString(resolved.value.create(DefaultEndOfLine.CRLF).createSnapshot(false)), content); } @@ -295,8 +289,8 @@ suite('Files - TextFileService i/o', () => { await service.write(resource, content); - const resolved = await service.resolve(resource); - assert.equal(snapshotToString(resolved.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(false)), content); + const resolved = await service.readStream(resource); + assert.equal(resolved.value.getFirstLineText(999999), content); }); test('write - no encoding - content as snapshot', async () => { @@ -306,14 +300,14 @@ suite('Files - TextFileService i/o', () => { await service.write(resource, TextModel.createFromString(content).createSnapshot()); - const resolved = await service.resolve(resource); - assert.equal(snapshotToString(resolved.value.create(isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(false)), content); + const resolved = await service.readStream(resource); + assert.equal(resolved.value.getFirstLineText(999999), content); }); test('write - encoding preserved (UTF 16 LE) - content as string', async () => { const resource = URI.file(join(testDir, 'some_utf16le.css')); - const resolved = await service.resolve(resource); + const resolved = await service.readStream(resource); assert.equal(resolved.encoding, UTF16le); await testEncoding(URI.file(join(testDir, 'some_utf16le.css')), UTF16le, 'Hello\nWorld', 'Hello\nWorld'); @@ -322,7 +316,7 @@ suite('Files - TextFileService i/o', () => { test('write - encoding preserved (UTF 16 LE) - content as snapshot', async () => { const resource = URI.file(join(testDir, 'some_utf16le.css')); - const resolved = await service.resolve(resource); + const resolved = await service.readStream(resource); assert.equal(resolved.encoding, UTF16le); await testEncoding(URI.file(join(testDir, 'some_utf16le.css')), UTF16le, TextModel.createFromString('Hello\nWorld').createSnapshot(), 'Hello\nWorld'); @@ -412,4 +406,208 @@ suite('Files - TextFileService i/o', () => { let detectedEncoding = await detectEncodingByBOM(resource.fsPath); assert.equal(detectedEncoding, UTF8); }); + + test('readStream - small text', async () => { + const resource = URI.file(join(testDir, 'small.txt')); + + await testReadStream(resource); + }); + + test('readStream - large text', async () => { + const resource = URI.file(join(testDir, 'lorem.txt')); + + await testReadStream(resource); + }); + + async function testReadStream(resource: URI): Promise { + const result = await service.readStream(resource); + + assert.equal(result.name, basename(resource.fsPath)); + assert.equal(result.size, statSync(resource.fsPath).size); + assert.equal(snapshotToString(result.value.create(DefaultEndOfLine.LF).createSnapshot(false)), snapshotToString(TextModel.createFromString(readFileSync(resource.fsPath).toString()).createSnapshot(false))); + } + + test('read - small text', async () => { + const resource = URI.file(join(testDir, 'small.txt')); + + await testRead(resource); + }); + + test('read - large text', async () => { + const resource = URI.file(join(testDir, 'lorem.txt')); + + await testRead(resource); + }); + + async function testRead(resource: URI): Promise { + const result = await service.read(resource); + + assert.equal(result.name, basename(resource.fsPath)); + assert.equal(result.size, statSync(resource.fsPath).size); + assert.equal(result.value, readFileSync(resource.fsPath).toString()); + } + + test('readStream - encoding picked up (CP1252)', async () => { + const resource = URI.file(join(testDir, 'some_small_cp1252.txt')); + const encoding = 'windows1252'; + + const result = await service.readStream(resource, { encoding }); + assert.equal(result.encoding, encoding); + assert.equal(result.value.getFirstLineText(999999), 'Private = "Persönlicheß Information"'); + }); + + test('read - encoding picked up (CP1252)', async () => { + const resource = URI.file(join(testDir, 'some_small_cp1252.txt')); + const encoding = 'windows1252'; + + const result = await service.read(resource, { encoding }); + assert.equal(result.encoding, encoding); + assert.equal(result.value, 'Private = "Persönlicheß Information"'); + }); + + test('read - encoding picked up (binary)', async () => { + const resource = URI.file(join(testDir, 'some_small_cp1252.txt')); + const encoding = 'binary'; + + const result = await service.read(resource, { encoding }); + assert.equal(result.encoding, encoding); + assert.equal(result.value, 'Private = "Persönlicheß Information"'); + }); + + test('read - encoding picked up (base64)', async () => { + const resource = URI.file(join(testDir, 'some_small_cp1252.txt')); + const encoding = 'base64'; + + const result = await service.read(resource, { encoding }); + assert.equal(result.encoding, encoding); + assert.equal(result.value, btoa('Private = "Persönlicheß Information"')); + }); + + test('readStream - user overrides BOM', async () => { + const resource = URI.file(join(testDir, 'some_utf16le.css')); + + const result = await service.readStream(resource, { encoding: 'windows1252' }); + assert.equal(result.encoding, 'windows1252'); + }); + + test('readStream - BOM removed', async () => { + const resource = URI.file(join(testDir, 'some_utf8_bom.txt')); + + const result = await service.readStream(resource); + assert.equal(result.value.getFirstLineText(999999), 'This is some UTF 8 with BOM file.'); + }); + + test('readStream - invalid encoding', async () => { + const resource = URI.file(join(testDir, 'index.html')); + + const result = await service.readStream(resource, { encoding: 'superduper' }); + assert.equal(result.encoding, 'utf8'); + }); + + test('readStream - encoding override', async () => { + const resource = URI.file(join(testDir, 'some.utf16le')); + + const result = await service.readStream(resource, { encoding: 'windows1252' }); + assert.equal(result.encoding, 'utf16le'); + assert.equal(result.value.getFirstLineText(999999), 'This is some UTF 16 with BOM file.'); + }); + + test('readStream - large Big5', async () => { + await testLargeEncoding('big5', '中文abc'); + }); + + test('readStream - large CP1252', async () => { + await testLargeEncoding('cp1252', 'öäüß'); + }); + + test('readStream - large Cyrillic', async () => { + await testLargeEncoding('cp866', 'ÐБВГДЕЖЗИЙКЛМÐОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыьÑÑŽÑ'); + }); + + test('readStream - large GBK', async () => { + await testLargeEncoding('gbk', '中国abc'); + }); + + test('readStream - large ShiftJS', async () => { + await testLargeEncoding('shiftjis', '中文abc'); + }); + + test('readStream - large UTF8 BOM', async () => { + await testLargeEncoding('utf8bom', 'öäüß'); + }); + + test('readStream - large UTF16 LE', async () => { + await testLargeEncoding('utf16le', 'öäüß'); + }); + + test('readStream - large UTF16 BE', async () => { + await testLargeEncoding('utf16be', 'öäüß'); + }); + + async function testLargeEncoding(encoding: string, needle: string): Promise { + const resource = URI.file(join(testDir, `lorem_${encoding}.txt`)); + + const result = await service.readStream(resource, { encoding }); + assert.equal(result.encoding, encoding); + + const contents = snapshotToString(result.value.create(DefaultEndOfLine.LF).createSnapshot(false)); + + assert.equal(contents.indexOf(needle), 0); + assert.ok(contents.indexOf(needle, 10) > 0); + } + + test('readStream - UTF16 LE (no BOM)', async () => { + const resource = URI.file(join(testDir, 'utf16_le_nobom.txt')); + + const result = await service.readStream(resource); + assert.equal(result.encoding, 'utf16le'); + }); + + test('readStream - UTF16 BE (no BOM)', async () => { + const resource = URI.file(join(testDir, 'utf16_be_nobom.txt')); + + const result = await service.readStream(resource); + assert.equal(result.encoding, 'utf16be'); + }); + + test('readStream - autoguessEncoding', async () => { + const resource = URI.file(join(testDir, 'some_cp1252.txt')); + + const result = await service.readStream(resource, { autoGuessEncoding: true }); + assert.equal(result.encoding, 'windows1252'); + }); + + test('readStream - FILE_IS_BINARY', async () => { + const resource = URI.file(join(testDir, 'binary.txt')); + + let error: TextFileOperationError | undefined = undefined; + try { + await service.readStream(resource, { acceptTextOnly: true }); + } catch (err) { + error = err; + } + + assert.ok(error); + assert.equal(error!.textFileOperationResult, TextFileOperationResult.FILE_IS_BINARY); + + const result = await service.readStream(URI.file(join(testDir, 'small.txt')), { acceptTextOnly: true }); + assert.equal(result.name, 'small.txt'); + }); + + test('read - FILE_IS_BINARY', async () => { + const resource = URI.file(join(testDir, 'binary.txt')); + + let error: TextFileOperationError | undefined = undefined; + try { + await service.read(resource, { acceptTextOnly: true }); + } catch (err) { + error = err; + } + + assert.ok(error); + assert.equal(error!.textFileOperationResult, TextFileOperationResult.FILE_IS_BINARY); + + const result = await service.read(URI.file(join(testDir, 'small.txt')), { acceptTextOnly: true }); + assert.equal(result.name, 'small.txt'); + }); }); diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 72049db9f11..ce5dd5dd772 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -65,8 +65,8 @@ suite('Files - TextFileService', () => { accessor.untitledEditorService.revertAll(); }); - test('confirm onWillShutdown - no veto', function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8'); + test('confirm onWillShutdown - no veto', async function () { + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.getResource(), model); const event = new BeforeShutdownEventImpl(); @@ -76,14 +76,12 @@ suite('Files - TextFileService', () => { if (typeof veto === 'boolean') { assert.ok(!veto); } else { - veto.then(veto => { - assert.ok(!veto); - }); + assert.ok(!(await veto)); } }); test('confirm onWillShutdown - veto if user cancels', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8'); + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.getResource(), model); const service = accessor.textFileService; @@ -99,7 +97,7 @@ suite('Files - TextFileService', () => { }); test('confirm onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8'); + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.getResource(), model); const service = accessor.textFileService; @@ -125,7 +123,7 @@ suite('Files - TextFileService', () => { }); test('confirm onWillShutdown - save (hot.exit: off)', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8'); + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.getResource(), model); const service = accessor.textFileService; @@ -144,7 +142,7 @@ suite('Files - TextFileService', () => { }); test('isDirty/getDirty - files and untitled', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8'); + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.getResource(), model); const service = accessor.textFileService; @@ -171,7 +169,7 @@ suite('Files - TextFileService', () => { }); test('save - file', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8'); + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.getResource(), model); const service = accessor.textFileService; @@ -187,11 +185,11 @@ suite('Files - TextFileService', () => { test('save - UNC path', async function () { const untitledUncUri = URI.from({ scheme: 'untitled', authority: 'server', path: '/share/path/file.txt' }); - model = instantiationService.createInstance(TextFileEditorModel, untitledUncUri, 'utf8'); + model = instantiationService.createInstance(TextFileEditorModel, untitledUncUri, 'utf8', undefined); (accessor.textFileService.models).add(model.getResource(), model); const mockedFileUri = untitledUncUri.with({ scheme: Schemas.file }); - const mockedEditorInput = instantiationService.createInstance(TextFileEditorModel, mockedFileUri, 'utf8'); + const mockedEditorInput = instantiationService.createInstance(TextFileEditorModel, mockedFileUri, 'utf8', undefined); const loadOrCreateStub = sinon.stub(accessor.textFileService.models, 'loadOrCreate', () => Promise.resolve(mockedEditorInput)); sinon.stub(accessor.untitledEditorService, 'exists', () => true); @@ -211,7 +209,7 @@ suite('Files - TextFileService', () => { }); test('saveAll - file', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8'); + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.getResource(), model); const service = accessor.textFileService; @@ -228,7 +226,7 @@ suite('Files - TextFileService', () => { }); test('saveAs - file', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8'); + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.getResource(), model); const service = accessor.textFileService; @@ -244,7 +242,7 @@ suite('Files - TextFileService', () => { }); test('revert - file', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8'); + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.getResource(), model); const service = accessor.textFileService; @@ -260,7 +258,7 @@ suite('Files - TextFileService', () => { }); test('delete - dirty file', async function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8'); + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.getResource(), model); const service = accessor.textFileService; @@ -274,8 +272,8 @@ suite('Files - TextFileService', () => { }); test('move - dirty file', async function () { - let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8'); - let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_target.txt'), 'utf8'); + let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); + let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_target.txt'), 'utf8', undefined); (accessor.textFileService.models).add(sourceModel.getResource(), sourceModel); (accessor.textFileService.models).add(targetModel.getResource(), targetModel); @@ -395,7 +393,7 @@ suite('Files - TextFileService', () => { }); async function hotExitTest(this: any, setting: string, shutdownReason: ShutdownReason, multipleWindows: boolean, workspace: true, shouldVeto: boolean): Promise { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8'); + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.models).add(model.getResource(), model); const service = accessor.textFileService; diff --git a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts index c05d512145c..b924c55963d 100644 --- a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts @@ -15,11 +15,10 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { Event } from 'vs/base/common/event'; -import { snapshotToString } from 'vs/platform/files/common/files'; import { timeout } from 'vs/base/common/async'; class ServiceAccessor { @@ -54,7 +53,7 @@ suite('Workbench - TextModelResolverService', () => { accessor.untitledEditorService.revertAll(); }); - test('resolve resource', function () { + test('resolve resource', async () => { const dispose = accessor.textModelResolverService.registerTextModelContentProvider('test', { provideTextContent: function (resource: URI): Promise { if (resource.scheme === 'test') { @@ -68,67 +67,60 @@ suite('Workbench - TextModelResolverService', () => { }); let resource = URI.from({ scheme: 'test', authority: null!, path: 'thePath' }); - let input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, 'The Name', 'The Description', resource); + let input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, 'The Name', 'The Description', resource, undefined); - return input.resolve().then(async model => { - assert.ok(model); - assert.equal(snapshotToString((model as ResourceEditorModel).createSnapshot()!), 'Hello Test'); - - let disposed = false; - let disposedPromise = new Promise(resolve => { - Event.once(model.onDispose)(() => { - disposed = true; - resolve(); - }); - }); - input.dispose(); - await disposedPromise; - assert.equal(disposed, true); - - dispose.dispose(); - }); - }); - - test('resolve file', function () { - model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8'); - (accessor.textFileService.models).add(model.getResource(), model); - - return model.load().then(() => { - return accessor.textModelResolverService.createModelReference(model.getResource()).then(ref => { - const model = ref.object; - const editorModel = model.textEditorModel; - - assert.ok(editorModel); - assert.equal(editorModel.getValue(), 'Hello Html'); - - let disposed = false; - Event.once(model.onDispose)(() => { - disposed = true; - }); - - ref.dispose(); - return timeout(0).then(() => { // due to the reference resolving the model first which is async - assert.equal(disposed, true); - }); + const model = await input.resolve(); + assert.ok(model); + assert.equal(snapshotToString(((model as ResourceEditorModel).createSnapshot()!)), 'Hello Test'); + let disposed = false; + let disposedPromise = new Promise(resolve => { + Event.once(model.onDispose)(() => { + disposed = true; + resolve(); }); }); + input.dispose(); + + await disposedPromise; + assert.equal(disposed, true); + dispose.dispose(); }); - test('resolve untitled', function () { + test('resolve file', async function () { + const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined); + (accessor.textFileService.models).add(textModel.getResource(), textModel); + + await textModel.load(); + + const ref = await accessor.textModelResolverService.createModelReference(textModel.getResource()); + + const model = ref.object; + const editorModel = model.textEditorModel; + + assert.ok(editorModel); + assert.equal(editorModel.getValue(), 'Hello Html'); + + let disposed = false; + Event.once(model.onDispose)(() => { + disposed = true; + }); + + ref.dispose(); + await timeout(0); // due to the reference resolving the model first which is async + assert.equal(disposed, true); + }); + + test('resolve untitled', async () => { const service = accessor.untitledEditorService; const input = service.createOrGet(); - return input.resolve().then(() => { - return accessor.textModelResolverService.createModelReference(input.getResource()).then(ref => { - const model = ref.object; - const editorModel = model.textEditorModel; - - assert.ok(editorModel); - ref.dispose(); - - input.dispose(); - }); - }); + await input.resolve(); + const ref = await accessor.textModelResolverService.createModelReference(input.getResource()); + const model = ref.object; + const editorModel = model.textEditorModel; + assert.ok(editorModel); + ref.dispose(); + input.dispose(); }); test('even loading documents should be refcounted', async () => { @@ -136,12 +128,12 @@ suite('Workbench - TextModelResolverService', () => { let waitForIt = new Promise(c => resolveModel = c); const disposable = accessor.textModelResolverService.registerTextModelContentProvider('test', { - provideTextContent: (resource: URI): Promise => { - return waitForIt.then(_ => { - let modelContent = 'Hello Test'; - let languageSelection = accessor.modeService.create('json'); - return accessor.modelService.createModel(modelContent, languageSelection, resource); - }); + provideTextContent: async (resource: URI): Promise => { + await waitForIt; + + let modelContent = 'Hello Test'; + let languageSelection = accessor.modeService.create('json'); + return accessor.modelService.createModel(modelContent, languageSelection, resource); } }); diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 480afa0a225..2bb21797ae9 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -6,19 +6,19 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; import * as errors from 'vs/base/common/errors'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; -import { ColorThemeData } from './colorThemeData'; +import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ColorThemeStore } from 'vs/workbench/services/themes/browser/colorThemeStore'; +import { ColorThemeStore } from 'vs/workbench/services/themes/common/colorThemeStore'; import { FileIconThemeStore } from 'vs/workbench/services/themes/common/fileIconThemeStore'; import { FileIconThemeData } from 'vs/workbench/services/themes/common/fileIconThemeData'; import { removeClasses, addClasses } from 'vs/base/browser/dom'; @@ -30,6 +30,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { textmateColorsSchemaId, registerColorThemeSchemas, textmateColorSettingsSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; // implementation @@ -62,10 +63,6 @@ function validateThemeId(theme: string): string { return theme; } -export interface IColorCustomizations { - [colorIdOrThemeSettingsId: string]: string | IColorCustomizations; -} - export class WorkbenchThemeService implements IWorkbenchThemeService { _serviceBrand: any; @@ -482,7 +479,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.doSetFileIconTheme(newIconTheme); // remember theme data for a quick restore - if (newIconTheme.isLoaded) { + if (newIconTheme.isLoaded && newIconTheme.location && !getRemoteAuthority(newIconTheme.location)) { this.storageService.store(PERSISTED_ICON_THEME_STORAGE_KEY, newIconTheme.toStorageData(), StorageScope.GLOBAL); } diff --git a/src/vs/workbench/services/themes/browser/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts similarity index 96% rename from src/vs/workbench/services/themes/browser/colorThemeData.ts rename to src/vs/workbench/services/themes/common/colorThemeData.ts index 27806ab7cf1..acc4ba8788d 100644 --- a/src/vs/workbench/services/themes/browser/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -6,8 +6,8 @@ import { basename } from 'vs/base/common/path'; import * as Json from 'vs/base/common/json'; import { Color } from 'vs/base/common/color'; -import { ExtensionData, ITokenColorCustomizations, ITokenColorizationRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { convertSettings } from 'vs/workbench/services/themes/browser/themeCompatibility'; +import { ExtensionData, ITokenColorCustomizations, ITokenColorizationRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { convertSettings } from 'vs/workbench/services/themes/common/themeCompatibility'; import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; @@ -15,7 +15,6 @@ import * as resources from 'vs/base/common/resources'; import { Extensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { ThemeType } from 'vs/platform/theme/common/themeService'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IColorCustomizations } from 'vs/workbench/services/themes/browser/workbenchThemeService'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; @@ -297,7 +296,7 @@ function toCSSSelector(extensionId: string, path: string) { function _loadColorTheme(fileService: IFileService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise { if (resources.extname(themeLocation) === '.json') { - return fileService.resolveContent(themeLocation, { encoding: 'utf8' }).then(content => { + return fileService.readFile(themeLocation).then(content => { let errors: Json.ParseError[] = []; let contentValue = Json.parse(content.value.toString(), errors); if (errors.length > 0) { @@ -345,7 +344,7 @@ function _loadColorTheme(fileService: IFileService, themeLocation: URI, resultRu } function _loadSyntaxTokens(fileService: IFileService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise { - return fileService.resolveContent(themeLocation, { encoding: 'utf8' }).then(content => { + return fileService.readFile(themeLocation).then(content => { try { let contentValue = parsePList(content.value.toString()); let settings: ITokenColorizationRule[] = contentValue.settings; diff --git a/src/vs/workbench/services/themes/browser/colorThemeStore.ts b/src/vs/workbench/services/themes/common/colorThemeStore.ts similarity index 98% rename from src/vs/workbench/services/themes/browser/colorThemeStore.ts rename to src/vs/workbench/services/themes/common/colorThemeStore.ts index dcf92ce0e62..a803474e606 100644 --- a/src/vs/workbench/services/themes/browser/colorThemeStore.ts +++ b/src/vs/workbench/services/themes/common/colorThemeStore.ts @@ -9,7 +9,7 @@ import * as types from 'vs/base/common/types'; import * as resources from 'vs/base/common/resources'; import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ExtensionData, IThemeExtensionPoint, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { ColorThemeData } from 'vs/workbench/services/themes/browser/colorThemeData'; +import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/workbench/services/themes/common/fileIconThemeData.ts b/src/vs/workbench/services/themes/common/fileIconThemeData.ts index 0ee73d56bb9..7d84be48b03 100644 --- a/src/vs/workbench/services/themes/common/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/common/fileIconThemeData.ts @@ -185,7 +185,7 @@ interface IconThemeDocument extends IconsAssociation { } function _loadIconThemeDocument(fileService: IFileService, location: URI): Promise { - return fileService.resolveContent(location, { encoding: 'utf8' }).then((content) => { + return fileService.readFile(location).then((content) => { let errors: Json.ParseError[] = []; let contentValue = Json.parse(content.value.toString(), errors); if (errors.length > 0 || !contentValue) { diff --git a/src/vs/workbench/services/themes/browser/themeCompatibility.ts b/src/vs/workbench/services/themes/common/themeCompatibility.ts similarity index 100% rename from src/vs/workbench/services/themes/browser/themeCompatibility.ts rename to src/vs/workbench/services/themes/common/themeCompatibility.ts diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index dd6d7ae2179..831b8ad3391 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -64,6 +64,10 @@ export interface IWorkbenchThemeService extends IThemeService { onDidFileIconThemeChange: Event; } +export interface IColorCustomizations { + [colorIdOrThemeSettingsId: string]: string | IColorCustomizations; +} + export interface ITokenColorCustomizations { comments?: string | ITokenColorizationSetting; strings?: string | ITokenColorizationSetting; diff --git a/src/vs/workbench/services/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledEditorService.ts index 6b343944f7c..35b571ec52b 100644 --- a/src/vs/workbench/services/untitled/common/untitledEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledEditorService.ts @@ -21,7 +21,7 @@ export const IUntitledEditorService = createDecorator('u export interface IModelLoadOrCreateOptions { resource?: URI; - modeId?: string; + mode?: string; initialValue?: string; encoding?: string; useResourcePath?: boolean; @@ -29,7 +29,7 @@ export interface IModelLoadOrCreateOptions { export interface IUntitledEditorService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; /** * Events for when untitled editors content changes (e.g. any keystroke). @@ -78,7 +78,7 @@ export interface IUntitledEditorService { * It is valid to pass in a file resource. In that case the path will be used as identifier. * The use case is to be able to create a new file with a specific path with VSCode. */ - createOrGet(resource?: URI, modeId?: string, initialValue?: string, encoding?: string): UntitledEditorInput; + createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string): UntitledEditorInput; /** * Creates a new untitled model with the optional resource URI or returns an existing one @@ -184,10 +184,10 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor } loadOrCreate(options: IModelLoadOrCreateOptions = Object.create(null)): Promise { - return this.createOrGet(options.resource, options.modeId, options.initialValue, options.encoding, options.useResourcePath).resolve(); + return this.createOrGet(options.resource, options.mode, options.initialValue, options.encoding, options.useResourcePath).resolve(); } - createOrGet(resource?: URI, modeId?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledEditorInput { + createOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledEditorInput { if (resource) { // Massage resource if it comes with known file based resource @@ -207,44 +207,47 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor } // Create new otherwise - return this.doCreate(resource, hasAssociatedFilePath, modeId, initialValue, encoding); + return this.doCreate(resource, hasAssociatedFilePath, mode, initialValue, encoding); } - private doCreate(resource?: URI, hasAssociatedFilePath?: boolean, modeId?: string, initialValue?: string, encoding?: string): UntitledEditorInput { - if (!resource) { + private doCreate(resource?: URI, hasAssociatedFilePath?: boolean, mode?: string, initialValue?: string, encoding?: string): UntitledEditorInput { + let untitledResource: URI; + if (resource) { + untitledResource = resource; + } else { // Create new taking a resource URI that is not already taken let counter = this.mapResourceToInput.size + 1; do { - resource = URI.from({ scheme: Schemas.untitled, path: `Untitled-${counter}` }); + untitledResource = URI.from({ scheme: Schemas.untitled, path: `Untitled-${counter}` }); counter++; - } while (this.mapResourceToInput.has(resource)); + } while (this.mapResourceToInput.has(untitledResource)); } // Look up default language from settings if any - if (!modeId && !hasAssociatedFilePath) { + if (!mode && !hasAssociatedFilePath) { const configuration = this.configurationService.getValue(); if (configuration.files && configuration.files.defaultLanguage) { - modeId = configuration.files.defaultLanguage; + mode = configuration.files.defaultLanguage; } } - const input = this.instantiationService.createInstance(UntitledEditorInput, resource, hasAssociatedFilePath, modeId, initialValue, encoding); + const input = this.instantiationService.createInstance(UntitledEditorInput, untitledResource, hasAssociatedFilePath, mode, initialValue, encoding); const contentListener = input.onDidModelChangeContent(() => { - this._onDidChangeContent.fire(resource!); + this._onDidChangeContent.fire(untitledResource); }); const dirtyListener = input.onDidChangeDirty(() => { - this._onDidChangeDirty.fire(resource!); + this._onDidChangeDirty.fire(untitledResource); }); const encodingListener = input.onDidModelChangeEncoding(() => { - this._onDidChangeEncoding.fire(resource!); + this._onDidChangeEncoding.fire(untitledResource); }); const disposeListener = input.onDispose(() => { - this._onDidDisposeModel.fire(resource!); + this._onDidDisposeModel.fire(untitledResource); }); // Remove from cache on dispose @@ -259,7 +262,7 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor }); // Add to cache - this.mapResourceToInput.set(resource, input); + this.mapResourceToInput.set(untitledResource, input); return input; } diff --git a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts index 40d86026557..b09b8d1d520 100644 --- a/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/electron-browser/workspaceEditingService.ts @@ -244,7 +244,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } async createAndEnterWorkspace(folders: IWorkspaceFolderCreationData[], path?: URI): Promise { - if (path && !this.isValidTargetWorkspacePath(path)) { + if (path && !await this.isValidTargetWorkspacePath(path)) { return Promise.reject(null); } const remoteAuthority = this.environmentService.configuration.remoteAuthority; @@ -258,7 +258,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } async saveAndEnterWorkspace(path: URI): Promise { - if (!this.isValidTargetWorkspacePath(path)) { + if (!await this.isValidTargetWorkspacePath(path)) { return Promise.reject(null); } const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); @@ -298,8 +298,8 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } // Read the contents of the workspace file, update it to new location and save it. - const raw = await this.fileService.resolveContent(configPathURI); - const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(raw.value, configPathURI, targetConfigPathURI); + const raw = await this.fileService.readFile(configPathURI); + const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(raw.value.toString(), configPathURI, targetConfigPathURI); await this.textFileService.create(targetConfigPathURI, newRawWorkspaceContents, { overwrite: true }); } diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index 4f7400dbc91..c1ab20f2573 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -86,7 +86,7 @@ class MyResourceInput extends ResourceEditorInput { } suite('Workbench base editor', () => { - test('BaseEditor API', function () { + test('BaseEditor API', async () => { let e = new MyEditor(NullTelemetryService); let input = new MyOtherInput(); let options = new EditorOptions(); @@ -94,25 +94,24 @@ suite('Workbench base editor', () => { assert(!e.isVisible()); assert(!e.input); assert(!e.options); - return e.setInput(input, options, CancellationToken.None).then(() => { - assert.strictEqual(input, e.input); - assert.strictEqual(options, e.options); - const group = new TestEditorGroup(1); - e.setVisible(true, group); - assert(e.isVisible()); - assert.equal(e.group, group); - input.onDispose(() => { - assert(false); - }); - e.dispose(); - e.clearInput(); - e.setVisible(false, group); - assert(!e.isVisible()); - assert(!e.input); - assert(!e.options); - assert(!e.getControl()); + await e.setInput(input, options, CancellationToken.None); + assert.strictEqual(input, e.input); + assert.strictEqual(options, e.options); + const group = new TestEditorGroup(1); + e.setVisible(true, group); + assert(e.isVisible()); + assert.equal(e.group, group); + input.onDispose(() => { + assert(false); }); + e.dispose(); + e.clearInput(); + e.setVisible(false, group); + assert(!e.isVisible()); + assert(!e.input); + assert(!e.options); + assert(!e.getControl()); }); test('EditorDescriptor', () => { @@ -154,10 +153,10 @@ suite('Workbench base editor', () => { let inst = new TestInstantiationService(); - const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake')))!.instantiate(inst); + const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); assert.strictEqual(editor.getId(), 'myEditor'); - const otherEditor = EditorRegistry.getEditor(inst.createInstance(ResourceEditorInput, 'fake', '', URI.file('/fake')))!.instantiate(inst); + const otherEditor = EditorRegistry.getEditor(inst.createInstance(ResourceEditorInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); assert.strictEqual(otherEditor.getId(), 'myOtherEditor'); (EditorRegistry).setEditors(oldEditors); @@ -173,7 +172,7 @@ suite('Workbench base editor', () => { let inst = new TestInstantiationService(); - const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake')))!.instantiate(inst); + const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'), undefined))!.instantiate(inst); assert.strictEqual('myOtherEditor', editor.getId()); (EditorRegistry).setEditors(oldEditors); diff --git a/src/vs/workbench/test/common/editor/editorDiffModel.test.ts b/src/vs/workbench/test/common/editor/editorDiffModel.test.ts index 3023add68ca..662bd56d224 100644 --- a/src/vs/workbench/test/common/editor/editorDiffModel.test.ts +++ b/src/vs/workbench/test/common/editor/editorDiffModel.test.ts @@ -35,7 +35,7 @@ suite('Workbench editor model', () => { accessor = instantiationService.createInstance(ServiceAccessor); }); - test('TextDiffEditorModel', () => { + test('TextDiffEditorModel', async () => { const dispose = accessor.textModelResolverService.registerTextModelContentProvider('test', { provideTextContent: function (resource: URI): Promise { if (resource.scheme === 'test') { @@ -48,27 +48,26 @@ suite('Workbench editor model', () => { } }); - let input = instantiationService.createInstance(ResourceEditorInput, 'name', 'description', URI.from({ scheme: 'test', authority: null!, path: 'thePath' })); - let otherInput = instantiationService.createInstance(ResourceEditorInput, 'name2', 'description', URI.from({ scheme: 'test', authority: null!, path: 'thePath' })); + let input = instantiationService.createInstance(ResourceEditorInput, 'name', 'description', URI.from({ scheme: 'test', authority: null!, path: 'thePath' }), undefined); + let otherInput = instantiationService.createInstance(ResourceEditorInput, 'name2', 'description', URI.from({ scheme: 'test', authority: null!, path: 'thePath' }), undefined); let diffInput = new DiffEditorInput('name', 'description', input, otherInput); - return diffInput.resolve().then((model: any) => { - assert(model); - assert(model instanceof TextDiffEditorModel); + let model = await diffInput.resolve() as TextDiffEditorModel; - let diffEditorModel = model.textDiffEditorModel; - assert(diffEditorModel.original); - assert(diffEditorModel.modified); + assert(model); + assert(model instanceof TextDiffEditorModel); - return diffInput.resolve().then((model: any) => { - assert(model.isResolved()); + let diffEditorModel = model.textDiffEditorModel!; + assert(diffEditorModel.original); + assert(diffEditorModel.modified); - assert(diffEditorModel !== model.textDiffEditorModel); - diffInput.dispose(); - assert(!model.textDiffEditorModel); + model = await diffInput.resolve() as TextDiffEditorModel; + assert(model.isResolved()); - dispose.dispose(); - }); - }); + assert(diffEditorModel !== model.textDiffEditorModel); + diffInput.dispose(); + assert(!model.textDiffEditorModel); + + dispose.dispose(); }); }); diff --git a/src/vs/workbench/test/common/editor/editorGroups.test.ts b/src/vs/workbench/test/common/editor/editorGroups.test.ts index fde39c097be..18d862a91eb 100644 --- a/src/vs/workbench/test/common/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/common/editor/editorGroups.test.ts @@ -111,27 +111,17 @@ class TestFileEditorInput extends EditorInput implements IFileEditorInput { } getTypeId() { return 'testFileEditorInputForGroups'; } resolve(): Promise { return Promise.resolve(null!); } + setEncoding(encoding: string) { } + getEncoding(): string { return null!; } + setPreferredEncoding(encoding: string) { } + getResource(): URI { return this.resource; } + setForceOpenAsBinary(): void { } + setMode(mode: string) { } + setPreferredMode(mode: string) { } matches(other: TestFileEditorInput): boolean { return other && this.id === other.id && other instanceof TestFileEditorInput; } - - setEncoding(encoding: string) { - } - - getEncoding(): string { - return null!; - } - - setPreferredEncoding(encoding: string) { - } - - getResource(): URI { - return this.resource; - } - - setForceOpenAsBinary(): void { - } } function input(id = String(index++), nonSerializable?: boolean, resource?: URI): EditorInput { diff --git a/src/vs/workbench/test/common/editor/editorModel.test.ts b/src/vs/workbench/test/common/editor/editorModel.test.ts index 513d2783fa1..663f9268501 100644 --- a/src/vs/workbench/test/common/editor/editorModel.test.ts +++ b/src/vs/workbench/test/common/editor/editorModel.test.ts @@ -21,8 +21,8 @@ import { TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTe class MyEditorModel extends EditorModel { } class MyTextEditorModel extends BaseTextEditorModel { - public createTextEditorModel(value: ITextBufferFactory, resource?: URI, modeId?: string) { - return super.createTextEditorModel(value, resource, modeId); + public createTextEditorModel(value: ITextBufferFactory, resource?: URI, preferredMode?: string) { + return super.createTextEditorModel(value, resource, preferredMode); } isReadonly(): boolean { @@ -40,7 +40,7 @@ suite('Workbench editor model', () => { modeService = instantiationService.stub(IModeService, ModeServiceImpl); }); - test('EditorModel', () => { + test('EditorModel', async () => { let counter = 0; let m = new MyEditorModel(); @@ -50,25 +50,23 @@ suite('Workbench editor model', () => { counter++; }); - return m.load().then(model => { - assert(model === m); - assert.strictEqual(m.isResolved(), true); - m.dispose(); - assert.equal(counter, 1); - }); + const model = await m.load(); + assert(model === m); + assert.strictEqual(m.isResolved(), true); + m.dispose(); + assert.equal(counter, 1); }); - test('BaseTextEditorModel', () => { + test('BaseTextEditorModel', async () => { let modelService = stubModelService(instantiationService); let m = new MyTextEditorModel(modelService, modeService); - return m.load().then((model: MyTextEditorModel) => { - assert(model === m); - model.createTextEditorModel(createTextBufferFactory('foo'), null!, 'text/plain'); - assert.strictEqual(m.isResolved(), true); - }).then(() => { - m.dispose(); - }); + const model = await m.load() as MyTextEditorModel; + + assert(model === m); + model.createTextEditorModel(createTextBufferFactory('foo'), null!, 'text/plain'); + assert.strictEqual(m.isResolved(), true); + m.dispose(); }); function stubModelService(instantiationService: TestInstantiationService): IModelService { diff --git a/src/vs/workbench/test/common/editor/resourceEditorInput.test.ts b/src/vs/workbench/test/common/editor/resourceEditorInput.test.ts index c9d6aff9b56..d1e788e80f4 100644 --- a/src/vs/workbench/test/common/editor/resourceEditorInput.test.ts +++ b/src/vs/workbench/test/common/editor/resourceEditorInput.test.ts @@ -11,18 +11,17 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { snapshotToString } from 'vs/platform/files/common/files'; +import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; class ServiceAccessor { constructor( @IModelService public modelService: IModelService, @IModeService public modeService: IModeService - ) { - } + ) { } } suite('Workbench resource editor input', () => { - let instantiationService: IInstantiationService; let accessor: ServiceAccessor; @@ -31,14 +30,33 @@ suite('Workbench resource editor input', () => { accessor = instantiationService.createInstance(ServiceAccessor); }); - test('simple', () => { - let resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' }); + test('basics', async () => { + const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' }); accessor.modelService.createModel('function test() {}', accessor.modeService.create('text'), resource); - let input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, 'The Name', 'The Description', resource); - return input.resolve().then(model => { - assert.ok(model); - assert.equal(snapshotToString((model as ResourceEditorModel).createSnapshot()!), 'function test() {}'); + const input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, 'The Name', 'The Description', resource, undefined); + + const model = await input.resolve(); + + assert.ok(model); + assert.equal(snapshotToString(((model as ResourceEditorModel).createSnapshot()!)), 'function test() {}'); + }); + + test('custom mode', async () => { + ModesRegistry.registerLanguage({ + id: 'resource-input-test', }); + + const resource = URI.from({ scheme: 'inmemory', authority: null!, path: 'thePath' }); + accessor.modelService.createModel('function test() {}', accessor.modeService.create('text'), resource); + + const input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, 'The Name', 'The Description', resource, 'resource-input-test'); + + const model = await input.resolve(); + assert.ok(model); + assert.equal(model.textEditorModel.getModeId(), 'resource-input-test'); + + input.setMode('text'); + assert.equal(model.textEditorModel.getModeId(), PLAINTEXT_MODE_ID); }); }); \ No newline at end of file diff --git a/src/vs/workbench/test/common/editor/untitledEditor.test.ts b/src/vs/workbench/test/common/editor/untitledEditor.test.ts index ce48df8f334..001ad7a6a2b 100644 --- a/src/vs/workbench/test/common/editor/untitledEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledEditor.test.ts @@ -14,8 +14,9 @@ import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorMo import { IModeService } from 'vs/editor/common/services/modeService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; -import { snapshotToString } from 'vs/platform/files/common/files'; import { timeout } from 'vs/base/common/async'; +import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; +import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; export class TestUntitledEditorService extends UntitledEditorService { get(resource: URI) { return super.get(resource); } @@ -45,7 +46,7 @@ suite('Workbench untitled editors', () => { accessor.untitledEditorService.dispose(); }); - test('Untitled Editor Service', function (done) { + test('Untitled Editor Service', async (done) => { const service = accessor.untitledEditorService; assert.equal(service.getAll().length, 0); @@ -68,36 +69,35 @@ suite('Workbench untitled editors', () => { assert.equal(service.getAll().length, 1); // dirty - input2.resolve().then(model => { - assert.ok(!service.isDirty(input2.getResource())); + const model = await input2.resolve(); - const listener = service.onDidChangeDirty(resource => { - listener.dispose(); + assert.ok(!service.isDirty(input2.getResource())); - assert.equal(resource.toString(), input2.getResource().toString()); + const listener = service.onDidChangeDirty(resource => { + listener.dispose(); - assert.ok(service.isDirty(input2.getResource())); - assert.equal(service.getDirty()[0].toString(), input2.getResource().toString()); - assert.equal(service.getDirty([input2.getResource()])[0].toString(), input2.getResource().toString()); - assert.equal(service.getDirty([input1.getResource()]).length, 0); + assert.equal(resource.toString(), input2.getResource().toString()); - service.revertAll(); - assert.equal(service.getAll().length, 0); - assert.ok(!input2.isDirty()); - assert.ok(!model.isDirty()); + assert.ok(service.isDirty(input2.getResource())); + assert.equal(service.getDirty()[0].toString(), input2.getResource().toString()); + assert.equal(service.getDirty([input2.getResource()])[0].toString(), input2.getResource().toString()); + assert.equal(service.getDirty([input1.getResource()]).length, 0); - input2.dispose(); + service.revertAll(); + assert.equal(service.getAll().length, 0); + assert.ok(!input2.isDirty()); + assert.ok(!model.isDirty()); - assert.ok(!service.exists(input2.getResource())); + input2.dispose(); - done(); - }); + assert.ok(!service.exists(input2.getResource())); + done(); + }); - model.textEditorModel.setValue('foo bar'); - }, err => done(err)); + model.textEditorModel.setValue('foo bar'); }); - test('Untitled with associated resource', function () { + test('Untitled with associated resource', () => { const service = accessor.untitledEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); const untitled = service.createOrGet(file); @@ -107,53 +107,49 @@ suite('Workbench untitled editors', () => { untitled.dispose(); }); - test('Untitled no longer dirty when content gets empty', function () { + test('Untitled no longer dirty when content gets empty', async () => { const service = accessor.untitledEditorService; const input = service.createOrGet(); // dirty - return input.resolve().then(model => { - model.textEditorModel.setValue('foo bar'); - assert.ok(model.isDirty()); - - model.textEditorModel.setValue(''); - assert.ok(!model.isDirty()); - - input.dispose(); - }); + const model = await input.resolve(); + model.textEditorModel.setValue('foo bar'); + assert.ok(model.isDirty()); + model.textEditorModel.setValue(''); + assert.ok(!model.isDirty()); + input.dispose(); }); - test('Untitled via loadOrCreate', function () { + test('Untitled via loadOrCreate', async () => { const service = accessor.untitledEditorService; - service.loadOrCreate().then(model1 => { - model1.textEditorModel!.setValue('foo bar'); - assert.ok(model1.isDirty()); - model1.textEditorModel!.setValue(''); - assert.ok(!model1.isDirty()); + const model1 = await service.loadOrCreate(); - return service.loadOrCreate({ initialValue: 'Hello World' }).then(model2 => { - assert.equal(snapshotToString(model2.createSnapshot()!), 'Hello World'); + model1.textEditorModel!.setValue('foo bar'); + assert.ok(model1.isDirty()); - const input = service.createOrGet(); + model1.textEditorModel!.setValue(''); + assert.ok(!model1.isDirty()); - return service.loadOrCreate({ resource: input.getResource() }).then(model3 => { - assert.equal(model3.getResource().toString(), input.getResource().toString()); + const model2 = await service.loadOrCreate({ initialValue: 'Hello World' }); + assert.equal(snapshotToString(model2.createSnapshot()!), 'Hello World'); - const file = URI.file(join('C:\\', '/foo/file44.txt')); - return service.loadOrCreate({ resource: file }).then(model4 => { - assert.ok(service.hasAssociatedFilePath(model4.getResource())); - assert.ok(model4.isDirty()); + const input = service.createOrGet(); - model1.dispose(); - model2.dispose(); - model3.dispose(); - model4.dispose(); - input.dispose(); - }); - }); - }); - }); + const model3 = await service.loadOrCreate({ resource: input.getResource() }); + + assert.equal(model3.getResource().toString(), input.getResource().toString()); + + const file = URI.file(join('C:\\', '/foo/file44.txt')); + const model4 = await service.loadOrCreate({ resource: file }); + assert.ok(service.hasAssociatedFilePath(model4.getResource())); + assert.ok(model4.isDirty()); + + model1.dispose(); + model2.dispose(); + model3.dispose(); + model4.dispose(); + input.dispose(); }); test('Untitled suggest name', function () { @@ -163,24 +159,31 @@ suite('Workbench untitled editors', () => { assert.ok(service.suggestFileName(input.getResource())); }); - test('Untitled with associated path remains dirty when content gets empty', function () { + test('Untitled with associated path remains dirty when content gets empty', async () => { const service = accessor.untitledEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); const input = service.createOrGet(file); // dirty - return input.resolve().then(model => { - model.textEditorModel.setValue('foo bar'); - assert.ok(model.isDirty()); - - model.textEditorModel.setValue(''); - assert.ok(model.isDirty()); - - input.dispose(); - }); + const model = await input.resolve(); + model.textEditorModel.setValue('foo bar'); + assert.ok(model.isDirty()); + model.textEditorModel.setValue(''); + assert.ok(model.isDirty()); + input.dispose(); }); - test('Untitled created with files.defaultLanguage setting', function () { + test('Untitled with initial content is dirty', async () => { + const service = accessor.untitledEditorService; + const input = service.createOrGet(undefined, undefined, 'Hello World'); + + // dirty + const model = await input.resolve(); + assert.ok(model.isDirty()); + input.dispose(); + }); + + test('Untitled created with files.defaultLanguage setting', () => { const defaultLanguage = 'javascript'; const config = accessor.testConfigurationService; config.setUserConfiguration('files', { 'defaultLanguage': defaultLanguage }); @@ -188,30 +191,52 @@ suite('Workbench untitled editors', () => { const service = accessor.untitledEditorService; const input = service.createOrGet(); - assert.equal(input.getModeId(), defaultLanguage); + assert.equal(input.getMode(), defaultLanguage); config.setUserConfiguration('files', { 'defaultLanguage': undefined }); input.dispose(); }); - test('Untitled created with modeId overrides files.defaultLanguage setting', function () { - const modeId = 'typescript'; + test('Untitled created with mode overrides files.defaultLanguage setting', () => { + const mode = 'typescript'; const defaultLanguage = 'javascript'; const config = accessor.testConfigurationService; config.setUserConfiguration('files', { 'defaultLanguage': defaultLanguage }); const service = accessor.untitledEditorService; - const input = service.createOrGet(null!, modeId); + const input = service.createOrGet(null!, mode); - assert.equal(input.getModeId(), modeId); + assert.equal(input.getMode(), mode); config.setUserConfiguration('files', { 'defaultLanguage': undefined }); input.dispose(); }); - test('encoding change event', function () { + test('Untitled can change mode afterwards', async () => { + const mode = 'untitled-input-test'; + + ModesRegistry.registerLanguage({ + id: mode, + }); + + const service = accessor.untitledEditorService; + const input = service.createOrGet(null!, mode); + + assert.equal(input.getMode(), mode); + + const model = await input.resolve(); + assert.equal(model.getMode(), mode); + + input.setMode('text'); + + assert.equal(input.getMode(), PLAINTEXT_MODE_ID); + + input.dispose(); + }); + + test('encoding change event', async () => { const service = accessor.untitledEditorService; const input = service.createOrGet(); @@ -223,16 +248,13 @@ suite('Workbench untitled editors', () => { }); // dirty - return input.resolve().then(model => { - model.setEncoding('utf16'); - - assert.equal(counter, 1); - - input.dispose(); - }); + const model = await input.resolve(); + model.setEncoding('utf16'); + assert.equal(counter, 1); + input.dispose(); }); - test('onDidChangeContent event', () => { + test('onDidChangeContent event', async () => { const service = accessor.untitledEditorService; const input = service.createOrGet(); @@ -245,39 +267,32 @@ suite('Workbench untitled editors', () => { assert.equal(r.toString(), input.getResource().toString()); }); - return input.resolve().then(model => { - model.textEditorModel.setValue('foo'); - assert.equal(counter, 0, 'Dirty model should not trigger event immediately'); + const model = await input.resolve(); + model.textEditorModel.setValue('foo'); + assert.equal(counter, 0, 'Dirty model should not trigger event immediately'); - return timeout(3).then(() => { - assert.equal(counter, 1, 'Dirty model should trigger event'); + await timeout(3); + assert.equal(counter, 1, 'Dirty model should trigger event'); + model.textEditorModel.setValue('bar'); - model.textEditorModel.setValue('bar'); - return timeout(3).then(() => { - assert.equal(counter, 2, 'Content change when dirty should trigger event'); + await timeout(3); + assert.equal(counter, 2, 'Content change when dirty should trigger event'); + model.textEditorModel.setValue(''); - model.textEditorModel.setValue(''); - return timeout(3).then(() => { - assert.equal(counter, 3, 'Manual revert should trigger event'); + await timeout(3); + assert.equal(counter, 3, 'Manual revert should trigger event'); + model.textEditorModel.setValue('foo'); - model.textEditorModel.setValue('foo'); - return timeout(3).then(() => { - assert.equal(counter, 4, 'Dirty model should trigger event'); + await timeout(3); + assert.equal(counter, 4, 'Dirty model should trigger event'); + model.revert(); - model.revert(); - return timeout(3).then(() => { - assert.equal(counter, 5, 'Revert should trigger event'); - - input.dispose(); - }); - }); - }); - }); - }); - }); + await timeout(3); + assert.equal(counter, 5, 'Revert should trigger event'); + input.dispose(); }); - test('onDidDisposeModel event', () => { + test('onDidDisposeModel event', async () => { const service = accessor.untitledEditorService; const input = service.createOrGet(); @@ -288,10 +303,9 @@ suite('Workbench untitled editors', () => { assert.equal(r.toString(), input.getResource().toString()); }); - return input.resolve().then(model => { - assert.equal(counter, 0); - input.dispose(); - assert.equal(counter, 1); - }); + await input.resolve(); + assert.equal(counter, 0); + input.dispose(); + assert.equal(counter, 1); }); }); \ No newline at end of file diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts index 69b3d465ea3..678c6aab20b 100644 --- a/src/vs/workbench/test/common/notifications.test.ts +++ b/src/vs/workbench/test/common/notifications.test.ts @@ -106,19 +106,27 @@ suite('Notifications', () => { assert.equal(item6.actions.primary!.length, 1); // Links - let item7 = NotificationViewItem.create({ severity: Severity.Info, message: 'Unable to [Link 1](http://link1.com) open [Link 2](https://link2.com) and [Invalid Link3](ftp://link3.com)' })!; + let item7 = NotificationViewItem.create({ severity: Severity.Info, message: 'Unable to [Link 1](http://link1.com) open [Link 2](command:open.me "Open This") and [Link 3](command:without.title) and [Invalid Link4](ftp://link4.com)' })!; const links = item7.message.links; - assert.equal(links.length, 2); + assert.equal(links.length, 3); assert.equal(links[0].name, 'Link 1'); assert.equal(links[0].href, 'http://link1.com'); + assert.equal(links[0].title, 'http://link1.com'); assert.equal(links[0].length, '[Link 1](http://link1.com)'.length); assert.equal(links[0].offset, 'Unable to '.length); assert.equal(links[1].name, 'Link 2'); - assert.equal(links[1].href, 'https://link2.com'); - assert.equal(links[1].length, '[Link 2](https://link2.com)'.length); + assert.equal(links[1].href, 'command:open.me'); + assert.equal(links[1].title, 'Open This'); + assert.equal(links[1].length, '[Link 2](command:open.me "Open This")'.length); assert.equal(links[1].offset, 'Unable to [Link 1](http://link1.com) open '.length); + + assert.equal(links[2].name, 'Link 3'); + assert.equal(links[2].href, 'command:without.title'); + assert.equal(links[2].title, 'Click to execute command \'without.title\''); + assert.equal(links[2].length, '[Link 3](command:without.title)'.length); + assert.equal(links[2].offset, 'Unable to [Link 1](http://link1.com) open [Link 2](command:open.me "Open This") and '.length); }); test('Model', () => { diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts index 881df02a1f5..502d3fae8e9 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts @@ -13,9 +13,8 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, SaveReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, SaveReason, IResolvedTextFileEditorModel, snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { snapshotToString } from 'vs/platform/files/common/files'; class ServiceAccessor { constructor(@ITextFileService public textFileService: TestTextFileService, @IModelService public modelService: IModelService) { @@ -38,7 +37,7 @@ suite('MainThreadSaveParticipant', function () { }); test('insert final new line', async function () { - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/final_new_line.txt'), 'utf8') as IResolvedTextFileEditorModel; + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel; await model.load(); const configService = new TestConfigurationService(); @@ -71,7 +70,7 @@ suite('MainThreadSaveParticipant', function () { }); test('trim final new lines', async function () { - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8') as IResolvedTextFileEditorModel; + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel; await model.load(); const configService = new TestConfigurationService(); @@ -106,7 +105,7 @@ suite('MainThreadSaveParticipant', function () { }); test('trim final new lines bug#39750', async function () { - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8') as IResolvedTextFileEditorModel; + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel; await model.load(); const configService = new TestConfigurationService(); @@ -133,7 +132,7 @@ suite('MainThreadSaveParticipant', function () { }); test('trim final new lines bug#46075', async function () { - const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8') as IResolvedTextFileEditorModel; + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel; await model.load(); const configService = new TestConfigurationService(); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 6204c5c1a56..b917b7ac10a 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -15,7 +15,7 @@ import { ConfirmResult, IEditorInputWithOptions, CloseDirection, IEditorIdentifi import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService, Parts, Position as PartPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; @@ -25,12 +25,11 @@ import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/serv import { IWorkspaceContextService, IWorkspace as IWorkbenchWorkspace, WorkbenchState, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, Workspace } from 'vs/platform/workspace/common/workspace'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; -import { FileOperationEvent, IFileService, IResolveContentOptions, FileOperationError, IFileStat, IResolveFileResult, FileChangesEvent, IResolveFileOptions, IContent, IStreamContent, ICreateFileOptions, ITextSnapshot, IResourceEncodings, IResourceEncoding, IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, IFileStatWithMetadata, IResolveMetadataFileOptions, IWriteFileOptions } from 'vs/platform/files/common/files'; +import { FileOperationEvent, IFileService, FileOperationError, IFileStat, IResolveFileResult, FileChangesEvent, IResolveFileOptions, ICreateFileOptions, IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, IFileStatWithMetadata, IResolveMetadataFileOptions, IWriteFileOptions, IReadFileOptions, IFileContent, IFileStreamContent } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { IRawTextContent, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileStreamContent, ITextFileService, IResourceEncoding, IReadTextFileOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { parseArgs } from 'vs/platform/environment/node/argv'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; @@ -38,7 +37,7 @@ import { IInstantiationService, ServicesAccessor, ServiceIdentifier } from 'vs/p import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IWindowsService, IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, MenuBarVisibility, IURIToOpen, IOpenSettings, IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; -import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; +import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; @@ -49,7 +48,7 @@ import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/pos import { IMenuService, MenuId, IMenu, ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; -import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, IModelDecorationOptions, ITextModel } from 'vs/editor/common/model'; +import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, IModelDecorationOptions, ITextModel, ITextSnapshot } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -83,9 +82,10 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedPr import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; +import { BrowserTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { - return instantiationService.createInstance(FileEditorInput, resource, undefined); + return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); } export const TestEnvironmentService = new WorkbenchEnvironmentService(parseArgs(process.argv) as IWindowConfiguration, process.execPath); @@ -176,7 +176,7 @@ export class TestContextService implements IWorkspaceContextService { } } -export class TestTextFileService extends TextFileService { +export class TestTextFileService extends BrowserTextFileService { public cleanupBackupsBeforeShutdownCalled: boolean; private promptPath: URI; @@ -200,7 +200,8 @@ export class TestTextFileService extends TextFileService { @IContextKeyService contextKeyService: IContextKeyService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, - @IEditorService editorService: IEditorService + @IEditorService editorService: IEditorService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService ) { super( contextService, @@ -219,7 +220,8 @@ export class TestTextFileService extends TextFileService { contextKeyService, dialogService, fileDialogService, - editorService + editorService, + textResourceConfigurationService ); } @@ -235,7 +237,7 @@ export class TestTextFileService extends TextFileService { this.resolveTextContentError = error; } - public resolve(resource: URI, options?: IResolveContentOptions): Promise { + public readStream(resource: URI, options?: IReadTextFileOptions): Promise { if (this.resolveTextContentError) { const error = this.resolveTextContentError; this.resolveTextContentError = null; @@ -243,15 +245,15 @@ export class TestTextFileService extends TextFileService { return Promise.reject(error); } - return this.fileService.resolveContent(resource, options).then((content): IRawTextContent => { + return this.fileService.readFileStream(resource, options).then(async (content): Promise => { return { resource: content.resource, name: content.name, mtime: content.mtime, etag: content.etag, - encoding: content.encoding, - value: createTextBufferFactory(content.value), - size: content.value.length + encoding: 'utf8', + value: await createTextBufferFactoryFromStream(content.value), + size: 10 }; }); } @@ -890,8 +892,6 @@ export class TestFileService implements IFileService { public _serviceBrand: any; - public encoding: IResourceEncodings; - private readonly _onFileChanges: Emitter; private readonly _onAfterOperation: Emitter; @@ -899,6 +899,7 @@ export class TestFileService implements IFileService { readonly onError: Event = Event.None; private content = 'Hello Html'; + private lastReadFileUri: URI; constructor() { this._onFileChanges = new Emitter(); @@ -913,6 +914,10 @@ export class TestFileService implements IFileService { return this.content; } + public getLastReadFileUri(): URI { + return this.lastReadFileUri; + } + public get onFileChanges(): Event { return this._onFileChanges.event; } @@ -951,10 +956,12 @@ export class TestFileService implements IFileService { return Promise.resolve(true); } - resolveContent(resource: URI, _options?: IResolveContentOptions): Promise { + readFile(resource: URI, options?: IReadFileOptions | undefined): Promise { + this.lastReadFileUri = resource; + return Promise.resolve({ resource: resource, - value: this.content, + value: VSBuffer.fromString(this.content), etag: 'index.txt', encoding: 'utf8', mtime: Date.now(), @@ -963,7 +970,9 @@ export class TestFileService implements IFileService { }); } - resolveStreamContent(resource: URI, _options?: IResolveContentOptions): Promise { + readFileStream(resource: URI, options?: IReadFileOptions | undefined): Promise { + this.lastReadFileUri = resource; + return Promise.resolve({ resource: resource, value: { @@ -974,7 +983,10 @@ export class TestFileService implements IFileService { if (event === 'end') { callback(); } - } + }, + resume: () => { }, + pause: () => { }, + destroy: () => { } }, etag: 'index.txt', encoding: 'utf8', @@ -1014,8 +1026,12 @@ export class TestFileService implements IFileService { onDidChangeFileSystemProviderRegistrations = Event.None; - registerProvider(_scheme: string, _provider: IFileSystemProvider) { - return { dispose() { } }; + private providers = new Map(); + + registerProvider(scheme: string, provider: IFileSystemProvider) { + this.providers.set(scheme, provider); + + return toDisposable(() => this.providers.delete(scheme)); } activateProvider(_scheme: string): Promise { @@ -1023,7 +1039,7 @@ export class TestFileService implements IFileService { } canHandleResource(resource: URI): boolean { - return resource.scheme === 'file'; + return resource.scheme === 'file' || this.providers.has(resource.scheme); } hasCapability(resource: URI, capability: FileSystemProviderCapabilities): boolean { return false; } @@ -1077,7 +1093,7 @@ export class TestBackupFileService implements IBackupFileService { throw new Error('not implemented'); } - public backupResource(_resource: URI, _content: ITextSnapshot): Promise { + public backupResource(_resource: URI, _content: ITextSnapshot, versionId?: number, meta?: T): Promise { return Promise.resolve(); } @@ -1092,7 +1108,7 @@ export class TestBackupFileService implements IBackupFileService { return textBuffer.getValueInRange(range, EndOfLinePreference.TextDefined); } - public resolveBackupContent(_backup: URI): Promise { + public resolveBackupContent(_backup: URI): Promise> { throw new Error('not implemented'); } diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 1232c5b68bf..de7f0954eba 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -116,7 +116,6 @@ import 'vs/workbench/services/dialogs/electron-browser/dialogService'; import 'vs/workbench/services/backup/node/backupFileService'; import 'vs/workbench/services/editor/browser/editorService'; import 'vs/workbench/services/history/browser/history'; -import 'vs/workbench/services/files/node/remoteFileService'; import 'vs/workbench/services/activity/browser/activityService'; import 'vs/workbench/browser/parts/views/views'; import 'vs/workbench/services/keybinding/electron-browser/keybindingService'; @@ -183,7 +182,7 @@ import 'vs/workbench/browser/parts/statusbar/statusbarPart'; //#region --- workbench contributions // Workspace File Watching -import 'vs/workbench/services/files2/common/workspaceWatcher'; +import 'vs/workbench/services/files/common/workspaceWatcher'; // Telemetry import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; diff --git a/src/vs/workbench/workbench.nodeless.main.css b/src/vs/workbench/workbench.web.main.css similarity index 100% rename from src/vs/workbench/workbench.nodeless.main.css rename to src/vs/workbench/workbench.web.main.css diff --git a/src/vs/workbench/workbench.nodeless.main.nls.js b/src/vs/workbench/workbench.web.main.nls.js similarity index 100% rename from src/vs/workbench/workbench.nodeless.main.nls.js rename to src/vs/workbench/workbench.web.main.nls.js diff --git a/src/vs/workbench/workbench.nodeless.main.ts b/src/vs/workbench/workbench.web.main.ts similarity index 96% rename from src/vs/workbench/workbench.nodeless.main.ts rename to src/vs/workbench/workbench.web.main.ts index dadae15c171..3d23a2ecde7 100644 --- a/src/vs/workbench/workbench.nodeless.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -12,7 +12,7 @@ import 'vs/editor/editor.all'; // import 'vs/workbench/electron-browser/main.contribution'; import 'vs/workbench/browser/workbench.contribution'; -import 'vs/workbench/browser/nodeless.main'; +import 'vs/workbench/browser/web.main'; //#endregion @@ -91,12 +91,10 @@ import { ContextViewService } from 'vs/platform/contextview/browser/contextViewS // import { RelayURLService } from 'vs/platform/url/electron-browser/urlService'; import { IHeapService, NullHeapService } from 'vs/workbench/services/heap/common/heap'; import { IBroadcastService, NullBroadcastService } from 'vs/workbench/services/broadcast/common/broadcast'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import 'vs/workbench/browser/nodeless.simpleservices'; +import 'vs/workbench/browser/web.simpleservices'; import 'vs/platform/dialogs/browser/dialogService'; @@ -115,13 +113,12 @@ import 'vs/workbench/services/preferences/browser/preferencesService'; import 'vs/workbench/services/output/common/outputChannelModelService'; import 'vs/workbench/services/configuration/common/jsonEditingService'; import 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; -// import 'vs/workbench/services/textfile/node/textFileService'; +import 'vs/workbench/services/textfile/browser/textFileService'; import 'vs/workbench/services/dialogs/browser/fileDialogService'; // import 'vs/workbench/services/dialogs/electron-browser/dialogService'; // import 'vs/workbench/services/backup/node/backupFileService'; import 'vs/workbench/services/editor/browser/editorService'; import 'vs/workbench/services/history/browser/history'; -// import 'vs/workbench/services/files/node/remoteFileService'; import 'vs/workbench/services/activity/browser/activityService'; import 'vs/workbench/browser/parts/views/views'; // import 'vs/workbench/services/keybinding/electron-browser/keybindingService'; @@ -171,7 +168,6 @@ registerSingleton(IContextViewService, ContextViewService, true); registerSingleton(IHeapService, NullHeapService); registerSingleton(IBroadcastService, NullBroadcastService); registerSingleton(IContextMenuService, ContextMenuService); -registerSingleton(ITextFileService, TextFileService); registerSingleton(IConfigurationResolverService, ConfigurationResolverService, true); //#endregion @@ -192,7 +188,7 @@ import 'vs/workbench/browser/parts/statusbar/statusbarPart'; //#region --- workbench contributions // Workspace File Watching -import 'vs/workbench/services/files2/common/workspaceWatcher'; +import 'vs/workbench/services/files/common/workspaceWatcher'; // Telemetry import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index d705254e193..4215a7fe247 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -15,10 +15,9 @@ export function setup() { return; } - const extensionName = 'vscode-smoketest-check'; await app.workbench.extensions.openExtensionsViewlet(); - await app.workbench.extensions.installExtension(extensionName); + await app.workbench.extensions.installExtension('michelkaporin.vscode-smoketest-check', 'vscode-smoketest-check'); await app.workbench.extensions.waitForExtensionsViewlet(); await app.workbench.quickopen.runCommand('Smoke Test Check'); diff --git a/test/smoke/src/areas/extensions/extensions.ts b/test/smoke/src/areas/extensions/extensions.ts index bf9748c466e..899783e4f64 100644 --- a/test/smoke/src/areas/extensions/extensions.ts +++ b/test/smoke/src/areas/extensions/extensions.ts @@ -28,14 +28,14 @@ export class Extensions extends Viewlet { await this.code.waitForElement(SEARCH_BOX); } - async searchForExtension(name: string): Promise { + async searchForExtension(id: string): Promise { await this.code.waitAndClick(SEARCH_BOX); await this.code.waitForActiveElement(SEARCH_BOX); - await this.code.waitForTypeInEditor(SEARCH_BOX, `name:"${name}"`); + await this.code.waitForTypeInEditor(SEARCH_BOX, `@id:${id}`); } - async installExtension(name: string): Promise { - await this.searchForExtension(name); + async installExtension(id: string, name: string): Promise { + await this.searchForExtension(id); const ariaLabel = `${name}. Press enter for extension details.`; await this.code.waitAndClick(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[aria-label="${ariaLabel}"] .extension li[class='action-item'] .extension-action.install`); await this.code.waitForElement(`.extension-editor .monaco-action-bar .action-item:not(.disabled) .extension-action.uninstall`); diff --git a/test/smoke/src/areas/workbench/localization.test.ts b/test/smoke/src/areas/workbench/localization.test.ts index dace3cc683e..c0844c2c6bf 100644 --- a/test/smoke/src/areas/workbench/localization.test.ts +++ b/test/smoke/src/areas/workbench/localization.test.ts @@ -14,9 +14,8 @@ export function setup() { return; } - const extensionName = 'German Language Pack for Visual Studio Code'; await app.workbench.extensions.openExtensionsViewlet(); - await app.workbench.extensions.installExtension(extensionName); + await app.workbench.extensions.installExtension('ms-ceintl.vscode-language-pack-de', 'German Language Pack for Visual Studio Code'); await app.restart({ extraArgs: ['--locale=DE'] }); }); diff --git a/yarn.lock b/yarn.lock index 86a0865607c..f43c0cca03c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -757,7 +757,7 @@ async-settle@^1.0.0: dependencies: async-done "^1.2.2" -async@1.x, async@^1.4.0: +async@1.x, async@^1.4.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= @@ -828,7 +828,7 @@ azure-storage@^2.10.2: xml2js "0.2.8" xmlbuilder "^9.0.7" -babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: +babel-code-frame@^6.16.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= @@ -1635,6 +1635,11 @@ colormin@^1.0.5: css-color-names "0.0.4" has "^1.0.1" +colors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" + integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= + colors@^1.1.2: version "1.2.1" resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794" @@ -1850,6 +1855,11 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +corser@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87" + integrity sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c= + coveralls@^2.11.11: version "2.13.3" resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-2.13.3.tgz#9ad7c2ae527417f361e8b626483f48ee92dd2bc7" @@ -2095,6 +2105,13 @@ debug@3.1.0, debug@^3.1.0: dependencies: ms "2.0.0" +debug@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + debug@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" @@ -2431,6 +2448,16 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +ecstatic@^3.0.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/ecstatic/-/ecstatic-3.3.1.tgz#b15b5b036c2233defc78d7bacbd8765226c95577" + integrity sha512-/rrctvxZ78HMI/tPIsqdvFKHHscxR3IJuKrZI2ZoUgkt2SiufyLFBmcco+aqQBIu6P1qBsUNG3drAAGLx80vTQ== + dependencies: + he "^1.1.1" + mime "^1.6.0" + minimist "^1.1.0" + url-join "^2.0.5" + editions@^1.1.1, editions@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" @@ -2889,6 +2916,11 @@ event-stream@~3.3.4: stream-combiner "^0.2.2" through "^2.3.8" +eventemitter3@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" + integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== + events@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -3306,6 +3338,13 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2: inherits "^2.0.1" readable-stream "^2.0.4" +follow-redirects@^1.0.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76" + integrity sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ== + dependencies: + debug "^3.2.6" + for-in@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.5.tgz#007374e2b6d5c67420a1479bdb75a04872b738c4" @@ -4213,6 +4252,11 @@ hawk@~6.0.2: hoek "4.x.x" sntp "2.x.x" +he@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -4279,6 +4323,29 @@ http-proxy-agent@2.1.0, http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" +http-proxy@^1.8.1: + version "1.17.0" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" + integrity sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g== + dependencies: + eventemitter3 "^3.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +http-server@^0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/http-server/-/http-server-0.11.1.tgz#2302a56a6ffef7f9abea0147d838a5e9b6b6a79b" + integrity sha512-6JeGDGoujJLmhjiRGlt8yK8Z9Kl0vnl/dQoQZlc4oeqaUoAKQg94NILLfrY3oWzSyFaQCVNTcKE5PZ3cH8VP9w== + dependencies: + colors "1.0.3" + corser "~2.0.0" + ecstatic "^3.0.0" + http-proxy "^1.8.1" + opener "~1.4.0" + optimist "0.6.x" + portfinder "^1.0.13" + union "~0.4.3" + http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -4863,6 +4930,11 @@ is-windows@^1.0.1, is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + is@^3.1.0, is@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/is/-/is-3.2.1.tgz#d0ac2ad55eb7b0bec926a5266f6c662aaa83dca5" @@ -4990,7 +5062,7 @@ js-yaml@3.6.1: argparse "^1.0.7" esprima "^2.6.0" -js-yaml@3.x, js-yaml@^3.5.1, js-yaml@^3.7.0: +js-yaml@3.x, js-yaml@^3.5.1: version "3.10.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" integrity sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA== @@ -5006,6 +5078,14 @@ js-yaml@^3.12.0: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^3.13.0: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" @@ -5688,7 +5768,7 @@ mime@1.4.1, mime@^1.3.4: resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== -mime@^1.4.1: +mime@^1.4.1, mime@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== @@ -6324,6 +6404,18 @@ oniguruma@^7.0.0: dependencies: nan "^2.10.0" +opener@~1.4.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8" + integrity sha1-XG2ixdflgx6P+jlklQ+NZnSskLg= + +opn@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" + integrity sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA== + dependencies: + is-wsl "^1.1.0" + optimist@0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.3.5.tgz#03654b52417030312d109f39b159825b60309304" @@ -6331,7 +6423,7 @@ optimist@0.3.5: dependencies: wordwrap "~0.0.2" -optimist@^0.6.1: +optimist@0.6.x, optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" integrity sha1-2j6nRob6IaGaERwybpDrFaAZZoY= @@ -6756,6 +6848,15 @@ pluralize@^1.2.1: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" integrity sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU= +portfinder@^1.0.13: + version "1.0.20" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.20.tgz#bea68632e54b2e13ab7b0c4775e9b41bf270e44a" + integrity sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw== + dependencies: + async "^1.5.2" + debug "^2.2.0" + mkdirp "0.5.x" + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -7198,6 +7299,11 @@ qs@6.5.1, qs@~6.5.1: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A== +qs@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-2.3.3.tgz#e9e85adbe75da0bbe4c8e0476a086290f863b404" + integrity sha1-6eha2+ddoLvkyOBHaghikPhjtAQ= + qs@~6.3.0: version "6.3.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" @@ -7667,6 +7773,11 @@ require-uncached@^1.0.2: caller-path "^0.1.0" resolve-from "^1.0.0" +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -8851,25 +8962,26 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== -tslint@^5.11.0: - version "5.11.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.11.0.tgz#98f30c02eae3cde7006201e4c33cb08b48581eed" - integrity sha1-mPMMAurjzecAYgHkwzywi0hYHu0= +tslint@^5.16.0: + version "5.16.0" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.16.0.tgz#ae61f9c5a98d295b9a4f4553b1b1e831c1984d67" + integrity sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA== dependencies: - babel-code-frame "^6.22.0" + "@babel/code-frame" "^7.0.0" builtin-modules "^1.1.1" chalk "^2.3.0" commander "^2.12.1" diff "^3.2.0" glob "^7.1.1" - js-yaml "^3.7.0" + js-yaml "^3.13.0" minimatch "^3.0.4" + mkdirp "^0.5.1" resolve "^1.3.2" semver "^5.3.0" tslib "^1.8.0" - tsutils "^2.27.2" + tsutils "^2.29.0" -tsutils@^2.27.2: +tsutils@^2.29.0: version "2.29.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== @@ -8954,10 +9066,10 @@ typescript-tslint-plugin@^0.0.7: minimatch "^3.0.4" vscode-languageserver "^5.1.0" -typescript@3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.1.tgz#b6691be11a881ffa9a05765a205cb7383f3b63c6" - integrity sha512-3NSMb2VzDQm8oBTLH6Nj55VVtUEpe/rgkIzMir0qVoLyjDZlnMBva0U6vDiV3IH+sl/Yu6oP5QwsAQtHPmDd2Q== +typescript@3.4.5: + version "3.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99" + integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw== typescript@^2.6.2: version "2.6.2" @@ -9067,6 +9179,13 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^0.4.3" +union@~0.4.3: + version "0.4.6" + resolved "https://registry.yarnpkg.com/union/-/union-0.4.6.tgz#198fbdaeba254e788b0efcb630bc11f24a2959e0" + integrity sha1-GY+9rrolTniLDvy2MLwR8kopWeA= + dependencies: + qs "~2.3.3" + uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" @@ -9146,6 +9265,11 @@ url-join@^1.1.0: resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78" integrity sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg= +url-join@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728" + integrity sha1-WvIvGMBSoACkjXuCxenC4v7tpyg= + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -9541,10 +9665,10 @@ vscode-windows-registry@1.0.1: dependencies: nan "^2.12.1" -vscode-xterm@3.13.0-beta3: - version "3.13.0-beta3" - resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.13.0-beta3.tgz#ab642ed77df07c2adfca7b15ae39c18328db3fc6" - integrity sha512-XvgD/P6CCV0+79UYM7CEL6Ziv2RiDopI3Wa1hIm3Dm8InWxkl3QwykINlempMNub+r0gwVwLKSQYr+Q/jg1IAw== +vscode-xterm@3.14.0-beta2: + version "3.14.0-beta2" + resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.14.0-beta2.tgz#5c9e0b9ef72de80f8ba4c9a89cec78cac73bbc1b" + integrity sha512-J/aXupjdOQmHE19tsO8Jrtdv5zMHhluVHBkLdEo2SIZtkkjkhviuwR6vtLotRJX8rcgwcC3z8LbVx0Phi+pvKg== vso-node-api@6.1.2-preview: version "6.1.2-preview"