diff --git a/build/.nativeignore b/build/.nativeignore new file mode 100644 index 0000000000..a04b4dbc23 --- /dev/null +++ b/build/.nativeignore @@ -0,0 +1,126 @@ +# 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 + +chart.js/node_modules/** + +emmet/node_modules/** + +pty.js/build/** +!pty.js/build/Release/** + +jquery-ui/external/** +jquery-ui/demos/** + +core-js/**/** + +slickgrid/node_modules/** +slickgrid/examples/** + +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 f5d5ef626b..5d6ec8c2cf 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 dc55bce808..639456ad4c 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 342d72e496..f727e30d25 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 9588ebcb36..9d98e9fa47 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 86868f6a2a..c8bedfbffc 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/sync-mooncake.yml b/build/azure-pipelines/sync-mooncake.yml index c422839de1..f3e8bc0785 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 a49349150d..8a6a6abb65 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/gulpfile.vscode.js b/build/gulpfile.vscode.js index c988384009..4754f6eee0 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -335,33 +335,7 @@ 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'])) - // {{SQL CARBON EDIT}} - .pipe(util.cleanNodeModule('chart.js', ['node_modules/**'], undefined)) - .pipe(util.cleanNodeModule('emmet', ['node_modules/**'], undefined)) - .pipe(util.cleanNodeModule('pty.js', ['build/**'], ['build/Release/**'])) - .pipe(util.cleanNodeModule('jquery-ui', ['external/**', 'demos/**'], undefined)) - .pipe(util.cleanNodeModule('core-js', ['**/**'], undefined)) - .pipe(util.cleanNodeModule('slickgrid', ['node_modules/**', 'examples/**'], undefined)) - .pipe(util.cleanNodeModule('nsfw', ['binding.gyp', 'build/**', 'src/**', 'openpa/**', 'includes/**'], ['**/*.node', '**/*.a'])) - .pipe(util.cleanNodeModule('vscode-nsfw', ['binding.gyp', 'build/**', 'src/**', 'openpa/**', 'includes/**'], ['build/Release/*.node', '**/*.a'])) - // {{SQL CARBON EDIT}} - End - .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')); // {{SQL CARBON EDIT}} @@ -380,7 +354,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op ], { base: '.', dot: true }); let all = es.merge( - packageJsonStream, + packageJsonStream, productJsonStream, license, api, diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 250be3f063..b16f7cad4a 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -306,9 +306,7 @@ function packageExtensionsStream(optsIn) { ..._.flatten(extensionsProductionDependencies.map((d) => path.relative(root, d.path)).map((d) => [`${d}/**`, `!${d}/**/{test,tests}/**`])), ]; const localExtensionDependencies = () => gulp.src(extensionDepsSrc, { base: '.', dot: true }) - .pipe(filter(['**', '!**/package-lock.json'])) - .pipe(util2.cleanNodeModule('account-provider-azure', ['node_modules/date-utils/doc/**', 'node_modules/adal_node/node_modules/**'], undefined)) - .pipe(util2.cleanNodeModule('typescript', ['**/**'], undefined)); + .pipe(filter(['**', '!**/package-lock.json'])); // Original code commented out here // const localExtensionDependencies = () => gulp.src('extensions/node_modules/**', { base: '.' }); // const marketplaceExtensions = () => es.merge( diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 25730a7bf3..5262045a8f 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -370,8 +370,6 @@ export function packageExtensionsStream(optsIn?: IPackageExtensionsOptions): Nod const localExtensionDependencies = () => gulp.src(extensionDepsSrc, { base: '.', dot: true }) .pipe(filter(['**', '!**/package-lock.json'])) - .pipe(util2.cleanNodeModule('account-provider-azure', ['node_modules/date-utils/doc/**', 'node_modules/adal_node/node_modules/**'], undefined)) - .pipe(util2.cleanNodeModule('typescript', ['**/**'], undefined)); // Original code commented out here // const localExtensionDependencies = () => gulp.src('extensions/node_modules/**', { base: '.' }); diff --git a/build/lib/util.js b/build/lib/util.js index 40563ca8e5..175b6934b2 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 95c8d996ef..594086a5c8 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/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 6decfcc826..e8cd0730ce 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -60,6 +60,18 @@ interface PreviewStyleLoadErrorMessage extends WebviewMessage { }; } +export class PreviewDocumentVersion { + public constructor( + public readonly resource: vscode.Uri, + public readonly version: number, + ) { } + + public equals(other: PreviewDocumentVersion): boolean { + return this.resource.fsPath === other.resource.fsPath + && this.version === other.version; + } +} + export class MarkdownPreview extends Disposable { public static viewType = 'markdown.preview'; @@ -71,7 +83,7 @@ export class MarkdownPreview extends Disposable { private throttleTimer: any; private line: number | undefined = undefined; private firstUpdate = true; - private currentVersion?: { resource: vscode.Uri, version: number }; + private currentVersion?: PreviewDocumentVersion; private forceUpdate = false; private isScrolling = false; private _disposed: boolean = false; @@ -389,7 +401,8 @@ export class MarkdownPreview extends Disposable { return; } - if (!this.forceUpdate && this.currentVersion && this.currentVersion.resource.fsPath === resource.fsPath && this.currentVersion.version === document.version) { + const pendingVersion = new PreviewDocumentVersion(resource, document.version); + if (!this.forceUpdate && this.currentVersion && this.currentVersion.equals(pendingVersion)) { if (this.line) { this.updateForView(resource, this.line); } @@ -397,10 +410,14 @@ export class MarkdownPreview extends Disposable { } this.forceUpdate = false; - this.currentVersion = { resource, version: document.version }; + this.currentVersion = pendingVersion; if (this._resource === resource) { const content = await this._contentProvider.provideTextDocumentContent(document, this._previewConfigurations, this.line, this.state); - this.setContent(content); + // Another call to `doUpdate` may have happened. + // Make sure we are still updating for the correct document + if (this.currentVersion && this.currentVersion.equals(pendingVersion)) { + this.setContent(content); + } } } diff --git a/extensions/package.json b/extensions/package.json index 8d41ef689c..da7f9f2b32 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,9 +3,9 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.4.5" + "typescript": "3.5.0-dev.20190517" }, "scripts": { "postinstall": "node ./postinstall" } -} \ No newline at end of file +} diff --git a/extensions/powershell/syntaxes/powershell.tmLanguage.json b/extensions/powershell/syntaxes/powershell.tmLanguage.json index 801ff729b9..b997667c5a 100644 --- a/extensions/powershell/syntaxes/powershell.tmLanguage.json +++ b/extensions/powershell/syntaxes/powershell.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/PowerShell/EditorSyntax/commit/12b7d7257eb493e45a9af0af9094ec0c2a996712", + "version": "https://github.com/PowerShell/EditorSyntax/commit/44eac8702f3cbe55a4ec70c1fdb163d42b4162fc", "name": "PowerShell", "scopeName": "source.powershell", "patterns": [ @@ -252,7 +252,7 @@ ] }, "attribute": { - "begin": "(\\[)\\s*\\b(?i)(cmdletbinding|alias|outputtype|parameter|validatenotnull|validatenotnullorempty|validatecount|validateset|allownull|allowemptycollection|allowemptystring|validatescript|validaterange|validatepattern|validatelength)\\b", + "begin": "(\\[)\\s*\\b(?i)(cmdletbinding|alias|outputtype|parameter|validatenotnull|validatenotnullorempty|validatecount|validateset|allownull|allowemptycollection|allowemptystring|validatescript|validaterange|validatepattern|validatelength|supportswildcards)\\b", "beginCaptures": { "1": { "name": "punctuation.section.bracket.begin.powershell" @@ -283,33 +283,6 @@ } }, "patterns": [ - { - "include": "#variable" - }, - { - "include": "#variableNoProperty" - }, - { - "include": "#hashtable" - }, - { - "include": "#scriptblock" - }, - { - "include": "#doubleQuotedStringEscapes" - }, - { - "include": "#doubleQuotedString" - }, - { - "include": "#type" - }, - { - "include": "#numericConstant" - }, - { - "include": "#doubleQuotedString" - }, { "include": "$self" }, @@ -323,17 +296,6 @@ "name": "keyword.operator.assignment.powershell" } } - }, - { - "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": { @@ -644,6 +622,9 @@ }, { "captures": { + "0": { + "name": "support.variable.automatic.powershell" + }, "1": { "name": "keyword.other.variable.definition.powershell" }, @@ -655,7 +636,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": { @@ -833,7 +814,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": { @@ -865,7 +846,7 @@ "name": "entity.name.function.invocation.powershell" } }, - "match": "(?i:(\\$|@)(global|local|private|script|using|workflow):((?:\\p{L}|\\d|_)+))" + "match": "(?i:(\\$)(global|local|private|script|using|workflow):((?:\\p{L}|\\d|_)+))" }, { "captures": { @@ -981,9 +962,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 8c691ac46d..184fe2ce3c 100644 --- a/extensions/powershell/test/colorize-results/test_ps1.json +++ b/extensions/powershell/test/colorize-results/test_ps1.json @@ -694,24 +694,24 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": null, - "light_plus": null, - "dark_vs": null, - "light_vs": null, - "hc_black": "keyword: #569CD6" + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "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": null, - "light_plus": null, - "dark_vs": null, - "light_vs": null, - "hc_black": "default: #FFFFFF" + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.variable: #9CDCFE" } }, { @@ -1068,24 +1068,24 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell keyword.other.variable.definition.powershell", + "t": "source.powershell meta.scriptblock.powershell interpolated.simple.source.powershell support.variable.automatic.powershell punctuation.definition.variable.powershell", "r": { - "dark_plus": null, - "light_plus": null, - "dark_vs": null, - "light_vs": null, - "hc_black": "keyword: #569CD6" + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "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": null, - "light_plus": null, - "dark_vs": null, - "light_vs": null, - "hc_black": "default: #FFFFFF" + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.variable: #9CDCFE" } }, { @@ -1266,24 +1266,24 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell keyword.other.variable.definition.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": null, - "light_plus": null, - "dark_vs": null, - "light_vs": null, - "hc_black": "keyword: #569CD6" + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "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": null, - "light_plus": null, - "dark_vs": null, - "light_vs": null, - "hc_black": "default: #FFFFFF" + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.variable: #9CDCFE" } }, { @@ -1431,24 +1431,24 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell keyword.other.variable.definition.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": null, - "light_plus": null, - "dark_vs": null, - "light_vs": null, - "hc_black": "keyword: #569CD6" + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "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": null, - "light_plus": null, - "dark_vs": null, - "light_vs": null, - "hc_black": "default: #FFFFFF" + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.variable: #9CDCFE" } }, { @@ -1508,24 +1508,24 @@ }, { "c": "$", - "t": "source.powershell meta.scriptblock.powershell meta.scriptblock.powershell meta.scriptblock.powershell interpolated.simple.source.powershell keyword.other.variable.definition.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": null, - "light_plus": null, - "dark_vs": null, - "light_vs": null, - "hc_black": "keyword: #569CD6" + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "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": null, - "light_plus": null, - "dark_vs": null, - "light_vs": null, - "hc_black": "default: #FFFFFF" + "dark_plus": "support.variable: #9CDCFE", + "light_plus": "support.variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.variable: #9CDCFE" } }, { diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index 763513a590..517429bb6d 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 9139021748..3a5401b477 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 149346bbb9..38c68f6e8f 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -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@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 e2f0685f5d..01beebaff8 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "tslint": "node node_modules/tslint/bin/tslint -c tslint-gci.json -p src/tsconfig.json", "strict-null-check": "tsc -p src/tsconfig.strictNullChecks.json", "strict-null-check-watch": "tsc -p src/tsconfig.strictNullChecks.json --watch", - "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": { "@angular/animations": "~4.1.3", @@ -75,7 +76,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", "zone.js": "^0.8.4" @@ -131,6 +132,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", @@ -143,6 +145,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", @@ -155,7 +158,7 @@ "source-map": "^0.4.4", "temp-write": "^3.4.0", "ts-loader": "^4.4.2", - "tslint": "^5.11.0", + "tslint": "^5.16.0", "tslint-microsoft-contrib": "^6.0.0", "typemoq": "^0.3.2", "typescript": "3.4.5", diff --git a/scripts/code-web.js b/scripts/code-web.js new file mode 100644 index 0000000000..c4f3699f2a --- /dev/null +++ b/scripts/code-web.js @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. 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/src/buildfile.js b/src/buildfile.js index 507ef3f818..09b62e7f38 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/sql/base/browser/ui/checkbox/media/checkbox.css b/src/sql/base/browser/ui/checkbox/media/checkbox.css index 5485dc8982..5efbb133c5 100644 --- a/src/sql/base/browser/ui/checkbox/media/checkbox.css +++ b/src/sql/base/browser/ui/checkbox/media/checkbox.css @@ -14,7 +14,7 @@ background: url('check.svg') center center no-repeat; } -.vs-dark.monaco-shell .custom-checkbox.sql-checkbox.checked { +.vs-dark .custom-checkbox.sql-checkbox.checked { background: url('check_inverse.svg') center center no-repeat; } diff --git a/src/sql/base/browser/ui/taskbar/actionbar.ts b/src/sql/base/browser/ui/taskbar/actionbar.ts index 9d4bafc707..0b48545e73 100644 --- a/src/sql/base/browser/ui/taskbar/actionbar.ts +++ b/src/sql/base/browser/ui/taskbar/actionbar.ts @@ -7,8 +7,8 @@ import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { - IActionBarOptions, ActionsOrientation, IActionItem, - IActionOptions, ActionItem, BaseActionItem + IActionBarOptions, ActionsOrientation, IActionViewItem, + IActionOptions, ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import * as lifecycle from 'vs/base/common/lifecycle'; import * as DOM from 'vs/base/browser/dom'; @@ -31,7 +31,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { private _context: any; // Items - private _items: IActionItem[]; + private _items: IActionViewItem[]; private _focusedItem?: number; private _focusTracker: DOM.IFocusTracker; @@ -214,14 +214,14 @@ export class ActionBar extends ActionRunner implements IActionRunner { actionItemElement.className = 'action-item'; actionItemElement.setAttribute('role', 'presentation'); - let item: IActionItem | undefined = undefined; + let item: IActionViewItem | undefined = 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; @@ -248,7 +248,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { public clear(): void { // Do not dispose action items if they were provided from outside - this._items = this._options.actionItemProvider ? [] : lifecycle.dispose(this._items); + this._items = this._options.actionViewItemProvider ? [] : lifecycle.dispose(this._items); DOM.clearNode(this._actionsList); } @@ -274,7 +274,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { } let startIndex = this._focusedItem; - let item: IActionItem; + let item: IActionViewItem; do { this._focusedItem = (this._focusedItem + 1) % this._items.length; @@ -294,7 +294,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { } let startIndex = this._focusedItem; - let item: IActionItem; + let item: IActionViewItem; do { this._focusedItem = this._focusedItem - 1; @@ -343,7 +343,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { // trigger action let actionItem = this._items[this._focusedItem]; - if (actionItem instanceof BaseActionItem) { + if (actionItem instanceof BaseActionViewItem) { const context = (actionItem._context === null || actionItem._context === undefined) ? event : actionItem._context; this.run(actionItem._action, context); } diff --git a/src/sql/base/browser/ui/taskbar/taskbar.ts b/src/sql/base/browser/ui/taskbar/taskbar.ts index 9f6b8e9649..f0eb15a150 100644 --- a/src/sql/base/browser/ui/taskbar/taskbar.ts +++ b/src/sql/base/browser/ui/taskbar/taskbar.ts @@ -43,8 +43,8 @@ export class Taskbar { this.actionBar = new ActionBar(element, { orientation: options.orientation, ariaLabel: options.ariaLabel, - actionItemProvider: (action: Action) => { - return options.actionItemProvider ? options.actionItemProvider(action) : undefined; + actionViewItemProvider: (action: Action) => { + return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined; } }); } diff --git a/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts index 957e9c655b..1436116401 100644 --- a/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts +++ b/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts @@ -397,7 +397,7 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements let isUntitled: boolean = uri.scheme === Schemas.untitled; const fileInput = isUntitled ? this._untitledEditorService.createOrGet(uri, notebookModeId, options.initialContent) : - this._editorService.createInput({ resource: uri, language: notebookModeId }); + this._editorService.createInput({ resource: uri, mode: notebookModeId }); let input = this._instantiationService.createInstance(NotebookInput, path.basename(uri.fsPath), uri, fileInput); input.defaultKernel = options.defaultKernel; input.connectionProfile = new ConnectionProfile(this._capabilitiesService, options.connectionProfile); diff --git a/src/sql/workbench/browser/modal/media/modal.css b/src/sql/workbench/browser/modal/media/modal.css index 684e331f3b..8a51a2d278 100644 --- a/src/sql/workbench/browser/modal/media/modal.css +++ b/src/sql/workbench/browser/modal/media/modal.css @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-shell .modal { +.modal { background-color: rgba(204, 204, 204, 0.6); right: 0; left: 0; @@ -13,7 +13,7 @@ z-index: 500; } -.monaco-shell .modal:not(.flyout-dialog) .modal-dialog { +.modal:not(.flyout-dialog) .modal-dialog { margin: auto; width: 640px; height: 480px; @@ -32,7 +32,7 @@ height: 25px; } -.monaco-shell .modal.flyout-dialog .modal-dialog { +.modal.flyout-dialog .modal-dialog { margin: auto auto auto auto; height: 100%; width: 500px; @@ -40,14 +40,13 @@ position: absolute; overflow-y: hidden; } - -.monaco-shell .modal.flyout-dialog.wide .modal-dialog { +.modal.flyout-dialog.wide .modal-dialog { width: 1200px; max-width: 95%; min-width: 400px; } -.monaco-shell .modal.flyout-dialog .modal-content { +.modal.flyout-dialog .modal-content { height: 100%; font-size: 11px; display: flex; @@ -66,10 +65,10 @@ margin-right: 10px; } -.monaco-shell .modal.flyout-dialog .modal-body, -.monaco-shell .modal.flyout-dialog .angular-modal-body, +.modal.flyout-dialog .modal-body, +.modal.flyout-dialog .angular-modal-body, /* Style for body and footer in modal dialog. This should be applied to dialog created with angular component. */ -.monaco-shell .modal.flyout-dialog .modal-body-and-footer { +.modal.flyout-dialog .modal-body-and-footer { flex: 1 1; overflow: hidden; } @@ -105,11 +104,11 @@ padding-left: 4px; } -.vs-dark.monaco-shell .modal.flyout-dialog .input { +.vs-dark .modal.flyout-dialog .input { background-color: #3C3C3C; } -.vs-dark.monaco-shell .modal.flyout-dialog input:disabled { +.vs-dark .modal.flyout-dialog input:disabled { background-color: #E1E1E1; color: #3C3C3C; } diff --git a/src/sql/workbench/browser/parts/views/customView.ts b/src/sql/workbench/browser/parts/views/customView.ts index 895d06be67..4dc9c96dab 100644 --- a/src/sql/workbench/browser/parts/views/customView.ts +++ b/src/sql/workbench/browser/parts/views/customView.ts @@ -10,7 +10,6 @@ import { IAction, 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 { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ViewContainer, ITreeItemLabel } from 'vs/workbench/common/views'; import { FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet'; @@ -22,7 +21,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 { ResourceLabel } 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 { basename } from 'vs/base/common/path'; import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -46,6 +45,7 @@ import { ITreeItem, ITreeView } from 'sql/workbench/common/views'; import { IOEShimService } from 'sql/workbench/parts/objectExplorer/common/objectExplorerViewTreeShim'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { NodeContextKey } from 'sql/workbench/parts/dataExplorer/common/nodeContext'; +import { fillInActionBarActions, fillInContextMenuActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; class TitleMenus implements IDisposable { @@ -319,7 +319,7 @@ export class CustomTreeView extends Disposable implements ITreeView { } private createTree() { - const actionItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuItemActionItem, action) : undefined; + const actionItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined; const menus = this.instantiationService.createInstance(TreeMenus, this.id); const dataSource = this.instantiationService.createInstance(TreeDataSource, this, this.container, this.id); const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, menus, actionItemProvider); @@ -599,7 +599,7 @@ class TreeRenderer implements IRenderer { constructor( private treeViewId: string, private menus: TreeMenus, - private actionItemProvider: IActionItemProvider, + private actionItemProvider: IActionViewItemProvider, @IInstantiationService private instantiationService: IInstantiationService, @IWorkbenchThemeService private themeService: IWorkbenchThemeService, @IConfigurationService private configurationService: IConfigurationService, @@ -623,7 +623,7 @@ class TreeRenderer implements IRenderer { DOM.addClass(resourceLabel.element.element, 'custom-view-tree-node-item-resourceLabel'); const actionsContainer = DOM.append(resourceLabel.element.element, DOM.$('.actions')); const actionBar = new ActionBar(actionsContainer, { - actionItemProvider: this.actionItemProvider, + actionViewItemProvider: this.actionItemProvider, actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection()) }); @@ -769,10 +769,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 null; }, diff --git a/src/sql/workbench/common/customInputConverter.ts b/src/sql/workbench/common/customInputConverter.ts index d489cfe6ad..86fc0fdf0a 100644 --- a/src/sql/workbench/common/customInputConverter.ts +++ b/src/sql/workbench/common/customInputConverter.ts @@ -181,7 +181,7 @@ function getNotebookFileExtensions(instantiationService: IInstantiationService): function hasNotebookFileMode(input: EditorInput): boolean { if (input instanceof UntitledEditorInput) { let untitledCast: UntitledEditorInput = input; - return (untitledCast && untitledCast.getModeId() === notebookModeId); + return (untitledCast && untitledCast.getMode() === notebookModeId); } return false; } @@ -200,7 +200,7 @@ function withService(instantiationService: IInstantiationServ function hasSqlFileMode(input: EditorInput): boolean { if (input instanceof UntitledEditorInput) { let untitledCast: UntitledEditorInput = input; - return untitledCast && (untitledCast.getModeId() === undefined || untitledCast.getModeId() === sqlModeId); + return untitledCast && (untitledCast.getMode() === undefined || untitledCast.getMode() === sqlModeId); } return false; diff --git a/src/sql/workbench/electron-browser/modelComponents/webview.component.ts b/src/sql/workbench/electron-browser/modelComponents/webview.component.ts index 7b5331f789..b9b26d186c 100644 --- a/src/sql/workbench/electron-browser/modelComponents/webview.component.ts +++ b/src/sql/workbench/electron-browser/modelComponents/webview.component.ts @@ -94,7 +94,7 @@ export default class WebViewComponent extends ComponentBase implements IComponen private setHtml(): void { if (this._webview && this.html) { this._renderedHtml = this.html; - this._webview.contents = this._renderedHtml; + this._webview.html = this._renderedHtml; this._webview.layout(); } } diff --git a/src/sql/workbench/parts/dashboard/contents/webviewContent.component.ts b/src/sql/workbench/parts/dashboard/contents/webviewContent.component.ts index 58a912e853..d20d7511c4 100644 --- a/src/sql/workbench/parts/dashboard/contents/webviewContent.component.ts +++ b/src/sql/workbench/parts/dashboard/contents/webviewContent.component.ts @@ -80,7 +80,7 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo public setHtml(html: string): void { this._html = html; if (this._webview) { - this._webview.contents = html; + this._webview.html = html; this._webview.layout(); } } @@ -112,7 +112,7 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo this._onMessage.fire(e); }); if (this._html) { - this._webview.contents = this._html; + this._webview.html = this._html; } this._webview.layout(); } diff --git a/src/sql/workbench/parts/dashboard/widgets/webview/webviewWidget.component.ts b/src/sql/workbench/parts/dashboard/widgets/webview/webviewWidget.component.ts index 375add445d..87f68bbb9a 100644 --- a/src/sql/workbench/parts/dashboard/widgets/webview/webviewWidget.component.ts +++ b/src/sql/workbench/parts/dashboard/widgets/webview/webviewWidget.component.ts @@ -60,7 +60,7 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget, public setHtml(html: string): void { this._html = html; if (this._webview) { - this._webview.contents = html; + this._webview.html = html; this._webview.layout(); } } @@ -110,7 +110,7 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget, this._onMessage.fire(e); }); if (this._html) { - this._webview.contents = this._html; + this._webview.html = this._html; } this._webview.layout(); } diff --git a/src/sql/workbench/parts/editData/browser/editDataActions.ts b/src/sql/workbench/parts/editData/browser/editDataActions.ts index fe8301b719..a4a4e324ff 100644 --- a/src/sql/workbench/parts/editData/browser/editDataActions.ts +++ b/src/sql/workbench/parts/editData/browser/editDataActions.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Action, IActionItem, IActionRunner } from 'vs/base/common/actions'; +import { Action, IActionViewItem, IActionRunner } from 'vs/base/common/actions'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IQueryModelService } from 'sql/platform/query/common/queryModel'; import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; @@ -150,7 +150,7 @@ export class ChangeMaxRowsAction extends EditDataAction { * Action item that handles the dropdown (combobox) that lists the avaliable number of row selections * for an edit data session */ -export class ChangeMaxRowsActionItem implements IActionItem { +export class ChangeMaxRowsActionItem implements IActionViewItem { public actionRunner: IActionRunner; public defaultRowCount: number; diff --git a/src/sql/workbench/parts/editData/browser/editDataEditor.ts b/src/sql/workbench/parts/editData/browser/editDataEditor.ts index 7c2d706e84..59fd9ec2c8 100644 --- a/src/sql/workbench/parts/editData/browser/editDataEditor.ts +++ b/src/sql/workbench/parts/editData/browser/editDataEditor.ts @@ -19,7 +19,7 @@ import { EditDataInput } from 'sql/workbench/parts/editData/common/editDataInput import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import * as queryContext from 'sql/workbench/parts/query/common/queryContext'; import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar'; -import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { IQueryModelService } from 'sql/platform/query/common/queryModel'; import { IEditorDescriptorService } from 'sql/workbench/services/queryEditor/common/editorDescriptorService'; @@ -313,7 +313,7 @@ export class EditDataEditor extends BaseEditor { // Create QueryTaskbar this._taskbarContainer = DOM.append(parentElement, DOM.$('div')); this._taskbar = new Taskbar(this._taskbarContainer, { - actionItemProvider: (action: Action) => this._getChangeMaxRowsAction(action) + actionViewItemProvider: (action: Action) => this._getChangeMaxRowsAction(action) }); // Create Actions for the toolbar @@ -344,7 +344,7 @@ export class EditDataEditor extends BaseEditor { /** * Gets the IActionItem for the list of row number drop down */ - private _getChangeMaxRowsAction(action: Action): IActionItem { + private _getChangeMaxRowsAction(action: Action): IActionViewItem { let actionID = ChangeMaxRowsAction.ID; if (action.id === actionID) { if (!this._changeMaxRowsActionItem) { diff --git a/src/sql/workbench/parts/jobManagement/electron-browser/media/jobHistory.css b/src/sql/workbench/parts/jobManagement/electron-browser/media/jobHistory.css index f5089814b3..f924199f0c 100644 --- a/src/sql/workbench/parts/jobManagement/electron-browser/media/jobHistory.css +++ b/src/sql/workbench/parts/jobManagement/electron-browser/media/jobHistory.css @@ -119,8 +119,8 @@ input#accordion:checked ~ .accordion-content { padding-bottom: 10px; } -.vs-dark.monaco-shell .all-jobs >.back-button-icon, -.hc-black.monaco-shell .all-jobs >.back-button-icon { +.vs-dark .all-jobs >.back-button-icon, +.hc-black .all-jobs >.back-button-icon { content: url('back_inverse.svg'); } diff --git a/src/sql/workbench/parts/notebook/notebook.component.ts b/src/sql/workbench/parts/notebook/notebook.component.ts index 4557f1baee..c77cd0ef34 100644 --- a/src/sql/workbench/parts/notebook/notebook.component.ts +++ b/src/sql/workbench/parts/notebook/notebook.component.ts @@ -14,10 +14,9 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; -import { IAction, Action, IActionItem } from 'vs/base/common/actions'; +import { IAction, Action, IActionViewItem } from 'vs/base/common/actions'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { fillInActions, LabeledMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { AngularDisposable } from 'sql/base/node/lifecycle'; @@ -48,6 +47,7 @@ import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ILogService } from 'vs/platform/log/common/log'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { LabeledMenuItemActionItem, fillInActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; export const NOTEBOOK_SELECTOR: string = 'notebook-component'; @@ -405,7 +405,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe this._trustedAction.enabled = false; let taskbar = this.toolbar.nativeElement; - this._actionBar = new Taskbar(taskbar, { actionItemProvider: action => this.actionItemProvider(action as Action) }); + this._actionBar = new Taskbar(taskbar, { actionViewItemProvider: action => this.actionItemProvider(action as Action) }); this._actionBar.context = this; this._actionBar.setContent([ { action: addCodeCellButton }, @@ -419,7 +419,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe } - private actionItemProvider(action: Action): IActionItem { + private actionItemProvider(action: Action): IActionViewItem { // Check extensions to create ActionItem; otherwise, return undefined // This is similar behavior that exists in MenuItemActionItem if (action instanceof MenuItemAction) { diff --git a/src/sql/workbench/parts/objectExplorer/browser/serverTreeActionProvider.ts b/src/sql/workbench/parts/objectExplorer/browser/serverTreeActionProvider.ts index 842b257dcf..c52d8e36f1 100644 --- a/src/sql/workbench/parts/objectExplorer/browser/serverTreeActionProvider.ts +++ b/src/sql/workbench/parts/objectExplorer/browser/serverTreeActionProvider.ts @@ -8,7 +8,6 @@ import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import { IAction } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { DisconnectConnectionAction, AddServerAction, @@ -31,6 +30,7 @@ import { TreeNodeContextKey } from 'sql/workbench/parts/objectExplorer/common/tr import { IQueryManagementService } from 'sql/platform/query/common/queryManagement'; import { IScriptingService } from 'sql/platform/scripting/common/scriptingService'; import { ServerInfoContextKey } from 'sql/workbench/parts/connection/common/serverInfoContextKey'; +import { fillInActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; /** * Provides actions for the server tree elements diff --git a/src/sql/workbench/parts/query/browser/queryActions.ts b/src/sql/workbench/parts/query/browser/queryActions.ts index 5267252710..911efdef30 100644 --- a/src/sql/workbench/parts/query/browser/queryActions.ts +++ b/src/sql/workbench/parts/query/browser/queryActions.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/queryActions'; import * as nls from 'vs/nls'; -import { Action, IActionItem, IActionRunner } from 'vs/base/common/actions'; +import { Action, IActionViewItem, IActionRunner } from 'vs/base/common/actions'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -427,7 +427,7 @@ export class ListDatabasesAction extends QueryTaskbarAction { * Action item that handles the dropdown (combobox) that lists the available databases. * Based off StartDebugActionItem. */ -export class ListDatabasesActionItem implements IActionItem { +export class ListDatabasesActionItem implements IActionViewItem { public static ID = 'listDatabaseQueryActionItem'; public actionRunner: IActionRunner; diff --git a/src/sql/workbench/parts/query/browser/queryEditor.ts b/src/sql/workbench/parts/query/browser/queryEditor.ts index 75bb2836e2..137fa5a382 100644 --- a/src/sql/workbench/parts/query/browser/queryEditor.ts +++ b/src/sql/workbench/parts/query/browser/queryEditor.ts @@ -23,7 +23,7 @@ import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResour import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { ISelectionData } from 'azdata'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -463,7 +463,7 @@ export class QueryEditor extends BaseEditor { // Create QueryTaskbar this._taskbarContainer = DOM.append(parentElement, DOM.$('div')); this._taskbar = new Taskbar(this._taskbarContainer, { - actionItemProvider: (action: Action) => this._getActionItemForAction(action), + actionViewItemProvider: (action: Action) => this._getActionItemForAction(action), }); // Create Actions for the toolbar @@ -513,7 +513,7 @@ export class QueryEditor extends BaseEditor { * Gets the IActionItem for the List Databases dropdown if provided the associated Action. * Otherwise returns null. */ - private _getActionItemForAction(action: Action): IActionItem { + private _getActionItemForAction(action: Action): IActionViewItem { if (action.id === ListDatabasesAction.ID) { return this.listDatabasesActionItem; } diff --git a/src/sql/workbench/parts/query/common/queryInput.ts b/src/sql/workbench/parts/query/common/queryInput.ts index a0e8b43a8a..8cdf076ca8 100644 --- a/src/sql/workbench/parts/query/common/queryInput.ts +++ b/src/sql/workbench/parts/query/common/queryInput.ts @@ -146,8 +146,11 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec // Description is shown beside the tab name in the combobox of open editors public getDescription(): string { return this._description; } public supportsSplitEditor(): boolean { return false; } - public getModeId(): string { return QueryInput.SCHEMA; } + public getMode(): string { return QueryInput.SCHEMA; } public revert(): Promise { return this._sql.revert(); } + public setMode(mode: string) { + this._sql.setMode(mode); + } public matches(otherInput: any): boolean { if (otherInput instanceof QueryInput) { diff --git a/src/sql/workbench/parts/webview/electron-browser/webViewDialog.ts b/src/sql/workbench/parts/webview/electron-browser/webViewDialog.ts index 23b3be8950..f6e8a4c847 100644 --- a/src/sql/workbench/parts/webview/electron-browser/webViewDialog.ts +++ b/src/sql/workbench/parts/webview/electron-browser/webViewDialog.ts @@ -121,7 +121,7 @@ export class WebViewDialog extends Modal { } private updateDialogBody(): void { - this._webview.contents = this.html; + this._webview.html = this.html; } /* espace key */ diff --git a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts index 9f246c33ff..bb9ed7af72 100644 --- a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts +++ b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts @@ -303,7 +303,7 @@ export class QueryEditorService implements IQueryEditorService { newEditorInput = queryInput; } else { let uriCopy: URI = URI.from({ scheme: uri.scheme, authority: uri.authority, path: uri.path, query: uri.query, fragment: uri.fragment }); - newEditorInput = QueryEditorService.instantiationService.createInstance(FileEditorInput, uriCopy, undefined); + newEditorInput = QueryEditorService.instantiationService.createInstance(FileEditorInput, uriCopy, undefined, undefined); } return newEditorInput; diff --git a/src/typings/vscode-xterm.d.ts b/src/typings/vscode-xterm.d.ts index e425a37c43..0e5e59a9b5 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 0f99d839e8..b388c4ead7 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 557f16c86a..a4434d722a 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 b2908db913..d400bbadf3 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -329,7 +329,7 @@ function parseFormattedText(content: string): IFormatParseTree { children: [] }; - let actionItemIndex = 0; + let actionViewItemIndex = 0; let current = root; const stack: IFormatParseTree[] = []; const stream = new StringStream(content); @@ -359,8 +359,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 ca685b7bc8..ea3d32745f 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 9a2f02ddb0..47adfc1499 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 655f74850a..5729f405c2 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 }; } @@ -278,7 +276,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/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 68cbae533b..a3bd38835b 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -6,7 +6,7 @@ import 'vs/css!./dropdown'; import { Gesture, EventType as GestureEventType } from 'vs/base/browser/touch'; import { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions'; -import { BaseActionItem, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { BaseActionViewItem, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IContextViewProvider, IAnchor, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IMenuOptions } from 'vs/base/browser/ui/menu/menu'; @@ -247,7 +247,7 @@ export class DropdownMenu extends BaseDropdown { getAnchor: () => this.element, getActions: () => this.actions, getActionsContext: () => this.menuOptions ? this.menuOptions.context : null, - getActionItem: action => this.menuOptions && this.menuOptions.actionItemProvider ? this.menuOptions.actionItemProvider(action) : undefined, + getActionViewItem: action => this.menuOptions && this.menuOptions.actionViewItemProvider ? this.menuOptions.actionViewItemProvider(action) : undefined, getKeyBinding: action => this.menuOptions && this.menuOptions.getKeyBinding ? this.menuOptions.getKeyBinding(action) : undefined, getMenuClassName: () => this.menuClassName, onHide: () => this.onHide(), @@ -266,23 +266,23 @@ export class DropdownMenu extends BaseDropdown { } } -export class DropdownMenuActionItem extends BaseActionItem { +export class DropdownMenuActionViewItem extends BaseActionViewItem { private menuActionsOrProvider: any; private dropdownMenu: DropdownMenu; private contextMenuProvider: IContextMenuProvider; - private actionItemProvider?: IActionItemProvider; + private actionViewItemProvider?: IActionViewItemProvider; private keybindings?: (action: IAction) => ResolvedKeybinding | undefined; private clazz: string | undefined; private anchorAlignmentProvider: (() => AnchorAlignment) | undefined; - constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment); - constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment); - constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionItemProvider: IActionItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment) { + constructor(action: IAction, menuActions: IAction[], contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment); + constructor(action: IAction, actionProvider: IActionProvider, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment); + constructor(action: IAction, menuActionsOrProvider: any, contextMenuProvider: IContextMenuProvider, actionViewItemProvider: IActionViewItemProvider | undefined, actionRunner: IActionRunner, keybindings: ((action: IAction) => ResolvedKeybinding | undefined) | undefined, clazz: string | undefined, anchorAlignmentProvider?: () => AnchorAlignment) { super(null, action); this.menuActionsOrProvider = menuActionsOrProvider; this.contextMenuProvider = contextMenuProvider; - this.actionItemProvider = actionItemProvider; + this.actionViewItemProvider = actionViewItemProvider; this.actionRunner = actionRunner; this.keybindings = keybindings; this.clazz = clazz; @@ -319,7 +319,7 @@ export class DropdownMenuActionItem extends BaseActionItem { this.dropdownMenu = this._register(new DropdownMenu(container, options)); this.dropdownMenu.menuOptions = { - actionItemProvider: this.actionItemProvider, + actionViewItemProvider: this.actionViewItemProvider, actionRunner: this.actionRunner, getKeyBinding: this.keybindings, context: this._context diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 2222629934..1d72186b6b 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -161,7 +161,7 @@ export class InputBox extends Widget { let tagName = this.options.flexibleHeight ? 'textarea' : 'input'; let wrapper = dom.append(this.element, $('.wrapper')); - this.input = dom.append(wrapper, $(tagName + '.input')); + this.input = dom.append(wrapper, $(tagName + '.input')); this.input.setAttribute('autocorrect', 'off'); this.input.setAttribute('autocapitalize', 'off'); this.input.setAttribute('spellcheck', 'false'); diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 6dff868dff..c61060cafa 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -7,7 +7,7 @@ import 'vs/css!./menu'; import * as nls from 'vs/nls'; import * as strings from 'vs/base/common/strings'; import { IActionRunner, IAction, Action } from 'vs/base/common/actions'; -import { ActionBar, IActionItemProvider, ActionsOrientation, Separator, ActionItem, IActionItemOptions, BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, IActionViewItemProvider, ActionsOrientation, Separator, ActionViewItem, IActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -39,7 +39,7 @@ export const MENU_ESCAPED_MNEMONIC_REGEX: RegExp = createMenuEscapedMnemonicRegE export interface IMenuOptions { context?: any; - actionItemProvider?: IActionItemProvider; + actionViewItemProvider?: IActionViewItemProvider; actionRunner?: IActionRunner; getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined; ariaLabel?: string; @@ -70,7 +70,7 @@ interface ISubMenuData { } export class Menu extends ActionBar { - private mnemonics: Map>; + private mnemonics: Map>; private menuDisposables: IDisposable[]; private scrollableElement: DomScrollableElement; private menuElement: HTMLElement; @@ -88,7 +88,7 @@ export class Menu extends ActionBar { super(menuElement, { orientation: ActionsOrientation.VERTICAL, - actionItemProvider: action => this.doGetActionItem(action, options, parentData), + actionViewItemProvider: action => this.doGetActionViewItem(action, options, parentData), context: options.context, actionRunner: options.actionRunner, ariaLabel: options.ariaLabel, @@ -113,7 +113,7 @@ export class Menu extends ActionBar { const actions = this.mnemonics.get(key)!; if (actions.length === 1) { - if (actions[0] instanceof SubmenuActionItem) { + if (actions[0] instanceof SubmenuMenuActionViewItem) { this.focusItemByElement(actions[0].container); } @@ -138,7 +138,7 @@ export class Menu extends ActionBar { const event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Home) || event.equals(KeyCode.PageUp)) { - this.focusedItem = this.items.length - 1; + this.focusedItem = this.viewItems.length - 1; this.focusNext(); EventHelper.stop(e, true); } else if (event.equals(KeyCode.End) || event.equals(KeyCode.PageDown)) { @@ -189,7 +189,7 @@ export class Menu extends ActionBar { parent: this }; - this.mnemonics = new Map>(); + this.mnemonics = new Map>(); this.push(actions, { icon: true, label: true, isMenu: true }); @@ -223,7 +223,7 @@ export class Menu extends ActionBar { container.appendChild(this.scrollableElement.getDomNode()); this.scrollableElement.scanDomNode(); - this.items.filter(item => !(item instanceof MenuSeparatorActionItem)).forEach((item: MenuActionItem, index: number, array: any[]) => { + this.viewItems.filter(item => !(item instanceof MenuSeparatorActionViewItem)).forEach((item: BaseMenuActionViewItem, index: number, array: any[]) => { item.updatePositionInSet(index + 1, array.length); }); } @@ -241,9 +241,9 @@ export class Menu extends ActionBar { this.domNode.style.backgroundColor = bgColor; container.style.boxShadow = shadow; - if (this.items) { - this.items.forEach(item => { - if (item instanceof MenuActionItem || item instanceof MenuSeparatorActionItem) { + if (this.viewItems) { + this.viewItems.forEach(item => { + if (item instanceof BaseMenuActionViewItem || item instanceof MenuSeparatorActionViewItem) { item.style(style); } }); @@ -263,12 +263,12 @@ export class Menu extends ActionBar { } trigger(index: number): void { - if (index <= this.items.length && index >= 0) { - const item = this.items[index]; - if (item instanceof SubmenuActionItem) { + if (index <= this.viewItems.length && index >= 0) { + const item = this.viewItems[index]; + if (item instanceof SubmenuMenuActionViewItem) { super.focus(index); item.open(true); - } else if (item instanceof MenuActionItem) { + } else if (item instanceof BaseMenuActionViewItem) { super.run(item._action, item._context); } else { return; @@ -295,27 +295,27 @@ export class Menu extends ActionBar { } } - private doGetActionItem(action: IAction, options: IMenuOptions, parentData: ISubMenuData): BaseActionItem { + private doGetActionViewItem(action: IAction, options: IMenuOptions, parentData: ISubMenuData): BaseActionViewItem { if (action instanceof Separator) { - return new MenuSeparatorActionItem(options.context, action, { icon: true }); + return new MenuSeparatorActionViewItem(options.context, action, { icon: true }); } else if (action instanceof SubmenuAction) { - const menuActionItem = new SubmenuActionItem(action, action.entries, parentData, options); + const menuActionViewItem = new SubmenuMenuActionViewItem(action, action.entries, parentData, options); if (options.enableMnemonics) { - const mnemonic = menuActionItem.getMnemonic(); - if (mnemonic && menuActionItem.isEnabled()) { - let actionItems: MenuActionItem[] = []; + const mnemonic = menuActionViewItem.getMnemonic(); + if (mnemonic && menuActionViewItem.isEnabled()) { + let actionViewItems: BaseMenuActionViewItem[] = []; if (this.mnemonics.has(mnemonic)) { - actionItems = this.mnemonics.get(mnemonic)!; + actionViewItems = this.mnemonics.get(mnemonic)!; } - actionItems.push(menuActionItem); + actionViewItems.push(menuActionViewItem); - this.mnemonics.set(mnemonic, actionItems); + this.mnemonics.set(mnemonic, actionViewItems); } } - return menuActionItem; + return menuActionViewItem; } else { const menuItemOptions: IMenuItemOptions = { enableMnemonics: options.enableMnemonics }; if (options.getKeyBinding) { @@ -329,32 +329,32 @@ export class Menu extends ActionBar { } } - const menuActionItem = new MenuActionItem(options.context, action, menuItemOptions); + const menuActionViewItem = new BaseMenuActionViewItem(options.context, action, menuItemOptions); if (options.enableMnemonics) { - const mnemonic = menuActionItem.getMnemonic(); - if (mnemonic && menuActionItem.isEnabled()) { - let actionItems: MenuActionItem[] = []; + const mnemonic = menuActionViewItem.getMnemonic(); + if (mnemonic && menuActionViewItem.isEnabled()) { + let actionViewItems: BaseMenuActionViewItem[] = []; if (this.mnemonics.has(mnemonic)) { - actionItems = this.mnemonics.get(mnemonic)!; + actionViewItems = this.mnemonics.get(mnemonic)!; } - actionItems.push(menuActionItem); + actionViewItems.push(menuActionViewItem); - this.mnemonics.set(mnemonic, actionItems); + this.mnemonics.set(mnemonic, actionViewItems); } } - return menuActionItem; + return menuActionViewItem; } } } -interface IMenuItemOptions extends IActionItemOptions { +interface IMenuItemOptions extends IActionViewItemOptions { enableMnemonics?: boolean; } -class MenuActionItem extends BaseActionItem { +class BaseMenuActionViewItem extends BaseActionViewItem { public container: HTMLElement; @@ -562,7 +562,7 @@ class MenuActionItem extends BaseActionItem { } } -class SubmenuActionItem extends MenuActionItem { +class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { private mysubmenu: Menu | null; private submenuContainer: HTMLElement | undefined; private submenuIndicator: HTMLElement; @@ -778,7 +778,7 @@ class SubmenuActionItem extends MenuActionItem { } } -class MenuSeparatorActionItem extends ActionItem { +class MenuSeparatorActionViewItem extends ActionViewItem { style(style: IMenuStyles): void { this.label.style.borderBottomColor = style.separatorColor ? `${style.separatorColor}` : null; } diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index f586e880dc..ed5149a0cd 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -41,8 +41,8 @@ class SelectListRenderer implements IListRendererObject.create(null); + renderTemplate(container: HTMLElement): ISelectListTemplateData { + const data: ISelectListTemplateData = Object.create(null); data.disposables = []; data.root = container; data.text = dom.append(container, $('.option-text')); @@ -54,10 +54,10 @@ class SelectListRenderer implements IListRenderertemplateData; - const text = (element).text; - const decoratorRight = (element).decoratorRight; - const isDisabled = (element).isDisabled; + const data: ISelectListTemplateData = templateData; + const text = element.text; + const decoratorRight = element.decoratorRight; + const isDisabled = element.isDisabled; data.text.textContent = text; data.decoratorRight.innerText = (!!decoratorRight ? decoratorRight : ''); @@ -73,10 +73,10 @@ class SelectListRenderer implements IListRendererdata.root), 'option-disabled'); + dom.addClass(data.root, 'option-disabled'); } else { // Make sure we do class removal from prior template rendering - dom.removeClass((data.root), 'option-disabled'); + dom.removeClass(data.root, 'option-disabled'); } } @@ -836,9 +836,9 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate { 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 d3891181af..2f33b06bdd 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 698a220b97..7d08f03117 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/common/actions.ts b/src/vs/base/common/actions.ts index 93f55a6409..dca4a79f2c 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/console.ts b/src/vs/base/common/console.ts index 70232b47d2..5ea6aefe13 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 80736ee8a2..7483613ee8 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/mime.ts b/src/vs/base/common/mime.ts index fa281cf1ef..af16456a13 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 c8c1586c73..1b734d0364 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 3694cef195..35489f1881 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 d4888386ec..b1fa46c8e3 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -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 658ec817a9..058a2f59ee 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/pfs.ts b/src/vs/base/node/pfs.ts index 40d63048d1..bf908f49a0 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 e160fcd467..5b4d173cb8 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/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index f3edba5460..4148a623b4 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -218,7 +218,7 @@ 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.messageType = buff.readUInt8(0); this._state.id = buff.readUInt32BE(1); this._state.ack = buff.readUInt32BE(5); } else { diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts index 75e7c50847..03b2da2c2e 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/mime.test.ts b/src/vs/base/test/common/mime.test.ts index 0cc9bfce43..dc41e96bb1 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 863b125f7b..8a4150feed 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 Source EULA. 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 eafcfb3fe1..98743764b3 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/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html new file mode 100644 index 0000000000..832b6033bf --- /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 0000000000..dbaf27c590 --- /dev/null +++ b/src/vs/code/browser/workbench/workbench.js @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. 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/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 8e06bc1362..90ab9dd0a1 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -106,8 +106,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 e37abfdf5a..0000000000 --- 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 fefcbc7a74..0000000000 --- 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 Source EULA. 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); - }); - }); -})(); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 2073b305eb..eae9dc72f3 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 b4d897b858..25e058b554 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 7c682fe74e..bf981ce16a 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) } @@ -201,10 +192,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 } @@ -640,10 +627,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))}`; } @@ -709,66 +692,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 } } @@ -794,12 +766,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 @@ -812,7 +786,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/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index a3a1971787..ca3e1f3490 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 59a0955e6e..80ab30eb96 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/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index 39c9edf0e5..0f2f3327c0 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 23141923ad..ab0c4717e6 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/common/services/getIconClasses.ts b/src/vs/editor/common/services/getIconClasses.ts index 9854146d45..3a9ea2e795 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 3c6cf0fdd8..9c8539f62b 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 bd719e9729..5c778c4e82 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 f8264c769f..f736226271 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 64953ed807..367df28c44 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 2c19e2d7e4..697f8979d2 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 d5515688a5..c641c9ce13 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/colorPicker/colorDetector.ts b/src/vs/editor/contrib/colorPicker/colorDetector.ts index b1ebfaa12f..40f51d379c 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 d6c2cb1595..a77302fc2e 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 59225a9be0..29b81d9ee8 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 5f7c2df44f..0b8cad0358 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 ab52ff724e..9ec22649c4 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 4076d02fab..bee8d01a4d 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 6d2b86c198..f8f3ea32d3 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 fef2d75cda..bca65f0fa7 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 927abc1576..966de575cc 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 d6d228993f..4b89666da4 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 a7828fee15..f4aee3992f 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 e3df2168f1..1a5fd8d53a 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 4b1b71014b..b500bd94d1 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 8624cd30f6..2d2bf8140e 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/toggleTabFocusMode/toggleTabFocusMode.ts b/src/vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode.ts index 94c9a238c1..3498ee790e 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 3dcbe172ae..c193e268fa 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 a961cbc712..b45f176a7b 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 61a667116c..36cd945fac 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 ee0a123da6..8685aa7826 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 63331082a6..a581e0b8a1 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 701ac49c8d..0a7e0414bb 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 2279303383..8f1f0fbce7 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 f8e059ab08..ffc0e34367 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 a6b3223af4..acfb20217b 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/toggleHighContrast/toggleHighContrast.ts b/src/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts index 8064f324c6..7bfb82fe07 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/platform/actions/browser/menuItemActionItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts similarity index 90% rename from src/vs/platform/actions/browser/menuItemActionItem.ts rename to src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 6f2d8cac33..9100366c47 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'; @@ -114,16 +114,16 @@ export function fillInActions(groups: [string, Array = new Map(); @@ -231,13 +231,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); @@ -255,10 +255,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(); @@ -273,7 +273,7 @@ export class ContextAwareMenuItemActionItem extends MenuItemActionItem { // Always show label for action items, instead of whether they don't have // an icon/CSS class. Useful for some toolbar scenarios in particular with // contributed actions from other extensions -export class LabeledMenuItemActionItem extends MenuItemActionItem { +export class LabeledMenuItemActionItem extends MenuEntryActionViewItem { private _labeledItemClassDispose: IDisposable; constructor( @@ -301,13 +301,13 @@ export class LabeledMenuItemActionItem extends MenuItemActionItem { 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, this._defaultCSSClassToAdd); diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index ed0afc5e78..396e49d6a8 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 c89e7d5559..1d0f5c8bb6 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/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index 476d02f4d4..7f7a939264 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 4995e98882..3b8d1d395f 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -63,7 +63,7 @@ export interface IBaseResourceInput { export interface IResourceInput extends IBaseResourceInput { /** - * The resource URL of the resource to open. + * The resource URI of the resource to open. */ resource: URI; @@ -71,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 7271fa87bc..0ad08e381a 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 // {{SQL CARBON EDIT}} aad?: boolean; database?: string; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 97e7efb1bc..e7b612ea04 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -95,8 +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 - // {{SQL CARBON EDIT}} { id: 'database', type: 'string', alias: 'D' }, { id: 'server', type: 'string', alias: 'S' }, diff --git a/src/vs/platform/files/node/fileConstants.ts b/src/vs/platform/files/node/fileConstants.ts deleted file mode 100644 index 9050d7d400..0000000000 --- a/src/vs/platform/files/node/fileConstants.ts +++ /dev/null @@ -1,15 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. 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 5f8be81b21..7788d772d1 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 9db9cab627..76e18d545f 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 7d71317230..e63be3ef21 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -221,7 +221,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 0f94f131ac..e56e69cd7d 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 084a314b8e..d4087f26bc 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 b9f0405a7d..d477d1679c 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 e44c624060..baa17f0af5 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 01b56cdf92..a1aea75a55 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/registry/common/platform.ts b/src/vs/platform/registry/common/platform.ts index 79a86a8baf..04c360723a 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/common/remoteAgentFileSystemChannel.ts b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts index f5354472d1..df41e95442 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 01d745119b..34c0934ac1 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -27,7 +27,8 @@ export class RemoteAuthorityResolverError extends Error { return true; } } - return false; + + return this.isTemporarilyNotAvailable(err); } public static isTemporarilyNotAvailable(err: any): boolean { diff --git a/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts index d450256ecb..131a211cb7 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/vscode.d.ts b/src/vs/vscode.d.ts index ad89bba4be..ce42128cf1 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 923f6de0e6..65e7223889 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 { diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index eed35c1a81..31b0e96b65 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/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index c53f566596..8a4bc5d66b 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -295,14 +295,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; } @@ -318,11 +322,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/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index b126ac0ab9..67356e70d8 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/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index b9fc9ef535..cf59f0805a 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2307,3 +2307,8 @@ export class QuickInputButtons { private constructor() { } } + +export enum ExtensionExecutionContext { + Local = 1, + Remote = 2 +} diff --git a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts index d75c163c3a..ab59191f70 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 b7be4d7cb2..c802c9d35c 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -800,6 +800,7 @@ export function createApiFactory( DocumentSymbol: extHostTypes.DocumentSymbol, EndOfLine: extHostTypes.EndOfLine, EventEmitter: Emitter, + ExtensionExecutionContext: extHostTypes.ExtensionExecutionContext, CustomExecution: extHostTypes.CustomExecution, FileChangeType: extHostTypes.FileChangeType, FileSystemError: extHostTypes.FileSystemError, diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 1d23139aa5..56e16d3aeb 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -36,7 +36,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 } from 'vs/workbench/api/common/extHostTypes'; +import { RemoteAuthorityResolverError, ExtensionExecutionContext } from 'vs/workbench/api/common/extHostTypes'; interface ITestRunner { run(testsRoot: string, clb: (error: Error, failures?: number) => void): void; @@ -367,7 +367,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 }); }); } @@ -628,7 +629,7 @@ 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}.`); } try { diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 8fedfa57b4..cc2f1465f7 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -21,6 +21,7 @@ import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; // {{SQL CARBON EDIT}} // 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; @@ -471,7 +472,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { .inspect(key.substr(key.lastIndexOf('.') + 1)); return this._apiInspectConfigToPlain(setting); }; - terminalEnvironment.mergeDefaultShellPathAndArgs(shellLaunchConfig, fetchSetting, isWorkspaceShellAllowed || false); + terminalEnvironment.mergeDefaultShellPathAndArgs(shellLaunchConfig, fetchSetting, isWorkspaceShellAllowed || false, getDefaultShell(platform.platform)); } // Get the initial cwd @@ -506,7 +507,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { // Fork the process and listen for messages 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); + 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)); diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index af67436be7..a6d1f5cede 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 9d6a8cdee2..8a08fb6582 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 92e9b8f71d..4d11c2605e 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 f0e18376df..03abb9d7d8 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 */); } diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index ef125258f3..d80a5a925e 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 22d63b3a97..bd043e6d97 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 8c59677c0d..a4c0b5b4de 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -10,7 +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 { IUntitledResourceInput, pathsToEditors } 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'; @@ -425,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 }); } diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 12873f7111..6f69614762 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 40bf14f901..1a7709f21d 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 5f3b186cb4..cc98b6a610 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'; @@ -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), @@ -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 @@ -380,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)); } @@ -446,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; }); @@ -466,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 81131222fd..aab53093e4 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 ee93c8cfb6..e77d9557f9 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 5e6a95a025..6bc5648cdc 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/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index e9ecda5eae..d7ce23e96a 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -70,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; diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 55decba633..4b41436b2b 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 ca335f65e1..fd4961f880 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -104,7 +104,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( interface ISerializedUntitledEditorInput { resource: string; resourceJSON: object; - modeId: string | null; + modeId: string | undefined; encoding: string; } @@ -136,7 +136,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() }; @@ -147,10 +147,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 language = deserialized.modeId; + const mode = deserialized.modeId; const encoding = deserialized.encoding; - return accessor.get(IEditorService).createInput({ resource, language, encoding, forceUntitled: true }) 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 09ea926a4f..8a48eea3e9 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 9fa7efad88..81b34791d1 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -40,7 +40,7 @@ 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'; // {{SQL CARBON EDIT}} import { ICommandService } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts index a718c15fcd..5b6f5905f7 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 2d90c43fd1..39dbaacb3b 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'; @@ -62,7 +62,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)); } } @@ -86,7 +94,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; } @@ -95,14 +103,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; @@ -123,7 +158,7 @@ class StateChange { this.metadata = this.metadata || other.metadata; } - public hasChanges(): boolean { + hasChanges(): boolean { return this.indentation || this.selectionStatus || this.mode @@ -182,42 +217,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; @@ -239,7 +281,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; @@ -251,15 +292,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'; @@ -267,7 +308,6 @@ class StatusBarItem { } } - export class EditorStatus implements IStatusbarItem { private state: State; private element: HTMLElement; @@ -664,7 +704,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; @@ -676,8 +716,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); @@ -774,7 +814,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 } } } @@ -887,7 +927,7 @@ export class ChangeModeAction extends Action { } } - return { + return { label: lang, iconClasses: getIconClasses(this.modelService, this.modeService, fakeResource), description @@ -950,50 +990,35 @@ export class ChangeModeAction extends Action { } // Change mode for active editor - // {{SQL CARBON EDIT}} - Get activeControl instead of activeEditor @todo anthonydresser 4/12/19 investigate - const activeEditor = this.editorService.activeControl; - 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); + const activeEditor = this.editorService.activeControl; // {{SQL CARBON EDIT}} @anthonydresser change to activeControl from active editor + if (activeEditor) { + const modeSupport = toEditorWithModeSupport(activeEditor.input); // {{SQL CARBON EDIT}} @anthonydresser reference input rather than activeeditor directly + if (modeSupport) { + + // Find mode + let languageSelection: ILanguageSelection | undefined; + if (pick === autoDetectMode) { + if (textModel) { + const resource = toResource(activeEditor.input, { supportSideBySide: SideBySideEditor.MASTER }); // {{SQL CARBON EDIT}} @anthonydresser reference input rather than activeeditor directly + if (resource) { + languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1)); + } + } + } else { + languageSelection = this.modeService.createByLanguageName(pick.label); } - if (diffEditorModel.modified) { - models.push(diffEditorModel.modified); + + // {{SQL CARBON EDIT}} @anthonydresser preform a check before we actuall set the mode + // Change mode + if (typeof languageSelection !== 'undefined') { + QueryEditorService.sqlLanguageModeCheck(textModel, languageSelection, activeEditor).then(newTextModel => { + if (newTextModel) { + modeSupport.setMode(languageSelection.languageIdentifier.language); + } + }); } } } - - // Find mode - let languageSelection: ILanguageSelection | undefined; - if (pick === autoDetectMode) { - if (textModel) { - // {{SQL CARBON EDIT}} - use activeEditor.input instead of activeEditor - const resource = toResource(activeEditor.input, { supportSideBySide: SideBySideEditor.MASTER }); - if (resource) { - languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1)); - } - } - } else { - languageSelection = this.modeService.createByLanguageName(pick.label); - } - - // {{SQL CARBON EDIT}} - // Change mode - models.forEach(textModel => { - QueryEditorService.sqlLanguageModeCheck(textModel, languageSelection, activeEditor).then((newTextModel) => { - if (newTextModel) { - this.modelService.setMode(newTextModel, languageSelection); - } - }); - }); }); } @@ -1004,9 +1029,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 @@ -1016,7 +1041,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] !== '.') { @@ -1167,6 +1192,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") }]); @@ -1257,10 +1283,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/resourceViewer.ts b/src/vs/workbench/browser/parts/editor/resourceViewer.ts index a022d952e2..31b92e92f0 100644 --- a/src/vs/workbench/browser/parts/editor/resourceViewer.ts +++ b/src/vs/workbench/browser/parts/editor/resourceViewer.ts @@ -370,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; @@ -501,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; } @@ -513,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)); })); diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index e85056a8d8..8c1006415d 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/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 55f67b8cc8..1cc53647f0 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,7 @@ 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 { @@ -129,7 +129,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), @@ -159,21 +159,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 { @@ -222,7 +222,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; @@ -289,7 +289,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; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index c3c5d81248..4cd89dfe30 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'; @@ -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 612eca7ad0..06803503dc 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 cf3f5cffa1..8eccef05cd 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.css +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.css @@ -119,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 879d54d8f1..fb53949b52 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'; @@ -1442,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. diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 7da7903fab..a026ac0775 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 a1d0e0caca..75f4afa9f9 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/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 064aff3a4d..b24b4e9152 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 90bb9e3693..b6bdf29506 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 c078e46b13..a8b77354df 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 98% rename from src/vs/workbench/browser/nodeless.main.ts rename to src/vs/workbench/browser/web.main.ts index dfc39bab1c..547e522af5 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'; diff --git a/src/vs/workbench/browser/nodeless.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts similarity index 96% rename from src/vs/workbench/browser/nodeless.simpleservices.ts rename to src/vs/workbench/browser/web.simpleservices.ts index 6db31aa25c..8bbcaf6e8a 100644 --- a/src/vs/workbench/browser/nodeless.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { ITextBufferFactory, ITextSnapshot } 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'; @@ -58,11 +58,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur 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 @@ -86,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 { @@ -234,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; @@ -264,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; @@ -702,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 ) { } @@ -732,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(); @@ -754,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; } @@ -766,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); @@ -775,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); } }); @@ -785,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); } } diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 172aeebe0c..c12956bb2a 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'; @@ -270,7 +270,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/common/composite.ts b/src/vs/workbench/common/composite.ts index 2ff7bf89b8..c3a6e06937 100644 --- a/src/vs/workbench/common/composite.ts +++ b/src/vs/workbench/common/composite.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. 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 61602d1ee8..0000000000 --- a/src/vs/workbench/common/contextkeys.ts +++ /dev/null @@ -1,25 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. 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 abb91f3dda..fa7b40bb15 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -5,7 +5,7 @@ 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'; @@ -144,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; } @@ -209,7 +209,7 @@ export interface IUntitledResourceInput extends IBaseResourceInput { /** * Optional language of the untitled resource. */ - language?: string; + mode?: string; /** * Optional contents of the untitled resource. @@ -516,19 +516,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. */ @@ -577,7 +593,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()); @@ -631,8 +647,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; @@ -998,9 +1013,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) { @@ -1009,7 +1024,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)) { @@ -1020,7 +1035,7 @@ export function toResource(editor: IEditorInput | null | undefined, options?: IR return resource; } - return null; + return undefined; } export const enum CloseDirection { diff --git a/src/vs/workbench/common/editor/dataUriEditorInput.ts b/src/vs/workbench/common/editor/dataUriEditorInput.ts index 467f2317b0..ffb78b7d76 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 6588c7f6fd..18951764ac 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 00633b8523..7a417fbaef 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. 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 54dcd62748..da3dae36f3 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 f520368338..046828b73f 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -4,21 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { ITextModel, ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; -import { EditorModel } from 'vs/workbench/common/editor'; +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 { 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( @@ -64,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); } @@ -83,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; @@ -110,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 4928bb315d..fe68c35210 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; } @@ -173,9 +165,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()); } } } @@ -199,6 +191,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 @@ -214,7 +222,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())); @@ -229,18 +237,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 240a4f389e..abbc289baa 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 { @@ -136,36 +127,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/contrib/backup/common/backupModelTracker.ts b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts index d97aa04106..319ad7d753 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 99662104d3..7fc056650e 100644 --- a/src/vs/workbench/contrib/backup/common/backupRestorer.ts +++ b/src/vs/workbench/contrib/backup/common/backupRestorer.ts @@ -78,10 +78,10 @@ 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 }; - // {{SQL CARBON EDIT}} - if (resource.scheme === Schemas.untitled - && !BackupRestorer.UNTITLED_REGEX.test(resource.fsPath) - && !BackupRestorer.SQLQUERY_REGEX.test(resource.fsPath)) { + // 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) && BackupRestorer.SQLQUERY_REGEX.test(resource.path)) { // {{SQL CARBON EDIT}} @anthonydresser add sql regex test return { resource: toLocalResource(resource, this.environmentService.configuration.remoteAuthority), options, forceUntitled: true }; } diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 37eed2a399..a1e5e9a8e1 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 1719929f91..01f17eaef3 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 224867a642..27896fa037 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/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 29d0258307..154d9fc2b5 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 16f2a7acee..06e13f322f 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 Source EULA. 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 9260ee47b2..d7acdaf730 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 678850bd2c..515b57629a 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/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index c23a62a120..38140d8bf8 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -840,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 8cb61d4d09..67876e7ada 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 a78360a712..7dca7bd8ac 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 5cf356aae9..7332251718 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 5ce1853970..81493c6502 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 2ecd3d574e..005a4590f3 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 9562f1221c..a823479349 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 51356eb37f..ca293d8b2e 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/electron-browser/debugService.ts b/src/vs/workbench/contrib/debug/electron-browser/debugService.ts index faca5408e2..d3ce1ebe6b 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/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts index aafff0590f..69e86086dd 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts @@ -253,9 +253,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; } @@ -565,7 +565,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/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index 36fa0a0f36..91cbd95e75 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -10,7 +10,7 @@ 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'; // {{SQL CARBON EDIT}} @@ -650,15 +650,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); } @@ -701,15 +701,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(); } @@ -720,7 +720,7 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { } } -export class DropDownMenuActionItem extends ExtensionActionItem { +export class DropDownMenuActionViewItem extends ExtensionActionViewItem { private disposables: IDisposable[] = []; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts index 7c0e7547bc..9e4a8883ee 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts @@ -13,7 +13,7 @@ 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, LocalInstallAction } 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'; @@ -81,11 +81,11 @@ export class Renderer implements IPagedRenderer { 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)); @@ -192,13 +192,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 8e2c1b6365..3eb62666d2 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts @@ -54,7 +54,7 @@ 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/common/contextkeys'; +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'; diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 5d162abee6..fd4f8a0ec1 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 028b62f98c..34b7c1c196 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -871,7 +871,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); } diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index e1ecf1ad89..6940f8e531 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -11,7 +11,7 @@ import { IWindowsService, IWindowService, IURIToOpen, IOpenSettings, INewWindowO 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, resourceToFileOnDisk } 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'; @@ -44,6 +44,7 @@ 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'; // {{SQL CARBON EDIT}} import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; @@ -348,7 +349,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)); } @@ -357,9 +358,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: resourceToFileOnDisk(COMPARE_WITH_SAVED_SCHEMA, uri), 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) { @@ -378,7 +379,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -let globalResourceToCompare: URI | null; +let globalResourceToCompare: URI | undefined; let resourceSelectedForCompareContext: IContextKey; CommandsRegistry.registerCommand({ id: SELECT_FOR_COMPARE_COMMAND_ID, @@ -553,9 +554,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)); } // {{SQL CARBON EDIT}} diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index c768f8fe74..e0f2276691 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -42,8 +42,8 @@ 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, @@ -126,8 +126,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 => { @@ -139,6 +139,7 @@ interface ISerializedFileInput { resource: string; resourceJSON: object; encoding?: string; + modeId?: string; } // Register Editor Input Factory @@ -146,25 +147,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; }); } } @@ -326,6 +329,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 e5018b2535..ca307d36a5 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 d609824caf..b58543f9c8 100644 --- a/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts @@ -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, resourceToFileOnDisk } 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)); } @@ -244,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: resourceToFileOnDisk(CONFLICT_RESOLUTION_SCHEME, resource), - 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 f2586ca831..13edb5fef1 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'; diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 36e110e1fe..06dc9f65da 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 39be01d3de..89b9b9c861 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -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, diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index 4460a70b2b..063b5d0228 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -23,6 +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 { ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; /** * Explorer viewlet id. @@ -58,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; } @@ -131,15 +133,7 @@ export const SortOrderConfiguration = { export type SortOrder = 'default' | 'mixed' | 'filesFirst' | 'type' | 'modified'; -export function resourceToFileOnDisk(scheme: string, resource: URI): URI { - return resource.with({ scheme, query: JSON.stringify({ scheme: resource.scheme }) }); -} - -export function fileOnDiskToResource(resource: URI): URI { - return resource.with({ scheme: JSON.parse(resource.query)['scheme'], query: null }); -} - -export class FileOnDiskContentProvider implements ITextModelContentProvider { +export class TextFileContentProvider implements ITextModelContentProvider { private fileWatcherDisposable: IDisposable | undefined; constructor( @@ -147,16 +141,34 @@ export class FileOnDiskContentProvider implements ITextModelContentProvider { @IFileService private readonly fileService: IFileService, @IModeService private readonly modeService: IModeService, @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 = fileOnDiskToResource(resource); + 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)) { @@ -179,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 = fileOnDiskToResource(resource); + const savedFileResource = TextFileContentProvider.textFileToResource(resource); 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); @@ -246,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 24f041e134..79d83c6f5d 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -16,6 +16,7 @@ import { FileOperationResult, FileOperationError } from 'vs/platform/files/commo 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 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/common/fileOnDiskProvider.test.ts b/src/vs/workbench/contrib/files/test/common/fileOnDiskProvider.test.ts index f53616a234..c356c32c1c 100644 --- a/src/vs/workbench/contrib/files/test/common/fileOnDiskProvider.test.ts +++ b/src/vs/workbench/contrib/files/test/common/fileOnDiskProvider.test.ts @@ -7,7 +7,7 @@ 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 { FileOnDiskContentProvider, resourceToFileOnDisk } from 'vs/workbench/contrib/files/common/files'; +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'; @@ -29,10 +29,10 @@ suite('Files - FileOnDiskContentProvider', () => { }); test('provideTextContent', async () => { - const provider = instantiationService.createInstance(FileOnDiskContentProvider); + const provider = instantiationService.createInstance(TextFileContentProvider); const uri = URI.parse('testFileOnDiskContentProvider://foo'); - const content = await provider.provideTextContent(resourceToFileOnDisk('conflictResolution', uri)); + 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 4963549901..bc8199aa45 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/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index 346939d3aa..08555275ac 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 56883723e6..1023c3e43a 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 6c20daaa90..a2a4026869 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/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index c988a17702..402cb768f6 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 c356957fed..cd2e5cac4e 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 2cbcd1b399..eedd3fedab 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 c0f4b2ee2a..42466560e3 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 0317fed422..cc5ae90899 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 2363faf286..2b253a63b9 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 e8fdb3edf7..b062bfec4f 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 c013254ed7..87b297db14 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,7 +508,7 @@ 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-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL)); @@ -525,7 +525,7 @@ export class SettingsTargetsWidget extends Widget { this.workspaceSettings.tooltip = this.workspaceSettings.label; const folderSettingsAction = new Action('folderSettings', localize('folderSettings', "Folder"), '.settings-tab', false, (folder: IWorkspaceFolder) => this.updateTarget(folder.uri)); - this.folderSettings = this.instantiationService.createInstance(FolderSettingsActionItem, folderSettingsAction); + this.folderSettings = this.instantiationService.createInstance(FolderSettingsActionViewItem, folderSettingsAction); this.update(); diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 8622fc8563..08e0ab9029 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -405,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 8fa236cd41..36dbbbd540 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'; 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 3f395789bb..0cd8afbe9c 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 894c8ec196..c63324ed97 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 9a7f61760c..898ed401c3 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 0a8ec572c0..32b3d0a388 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 efe92d0eba..d73fa11bf0 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 38a7358d85..27d4fe71cb 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 e8c3f58fd5..83199b384f 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 b2a72ec840..23938578e6 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 6e64bd2275..805d0b3b07 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/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index c82152c377..0c9782d51c 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/snippets/browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts index e7dca4560f..6ad5d797ee 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,8 +17,9 @@ 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'; @@ -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 ', @@ -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,9 +245,9 @@ 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); diff --git a/src/vs/workbench/contrib/tasks/browser/quickOpen.ts b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts index 19037be242..0b7d44d819 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/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts index 5256ae1698..a52b738698 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/electron-browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts index 94b017b473..a7e2240ac7 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts @@ -1360,7 +1360,6 @@ class TaskService extends Disposable implements ITaskService { this.modelService, this.configurationResolverService, this.telemetryService, this.contextService, this._environmentService, TaskService.OutputChannelId, - this.configurationService, (workspaceFolder: IWorkspaceFolder) => { if (!workspaceFolder) { return undefined; @@ -1974,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 { @@ -1984,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; @@ -2013,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); @@ -2219,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 @@ -2227,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 6386e7fcf8..9d10fe2681 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts @@ -44,7 +44,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { Schemas } from 'vs/base/common/network'; import { getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/terminal'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; interface TerminalData { terminal: ITerminalInstance; @@ -172,7 +171,6 @@ export class TerminalTaskSystem implements ITaskSystem { private contextService: IWorkspaceContextService, private environmentService: IWorkbenchEnvironmentService, private outputChannelId: string, - private readonly configurationService: IConfigurationService, taskSystemInfoResolver: TaskSystemInfoResovler, ) { @@ -756,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) { @@ -879,9 +877,6 @@ export class TerminalTaskSystem implements ITaskSystem { if (options.env) { shellLaunchConfig.env = options.env; } - - // Conpty doesn't do linefeeds in an expected way. Force winpty unless the user has requested otherwise. - shellLaunchConfig.forceWinpty = !this.configurationService.getValue('tasks.terminal.windowsAllowConpty'); return shellLaunchConfig; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 690430463e..e3a75356f3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -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 c6363c8fa6..dd4b778bc3 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 327f071ab5..de07d50a3a 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'; @@ -726,7 +726,7 @@ export class SwitchTerminalAction extends Action { } } -export class SwitchTerminalActionItem extends SelectActionItem { +export class SwitchTerminalActionViewItem extends SelectActionViewItem { constructor( action: IAction, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts b/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts index a6c3a619b8..145e54e243 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 3420195aeb..8e99da0d64 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -227,9 +227,9 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { return !!isWorkspaceShellAllowed; } - public mergeDefaultShellPathAndArgs(shell: IShellLaunchConfig, platformOverride: platform.Platform = platform.platform): void { + 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, platformOverride); + 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 19438959a3..3195af0e4a 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,18 +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); @@ -508,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) { @@ -745,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(); } @@ -859,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 @@ -970,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 : ''}`); @@ -981,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; @@ -997,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); } } } @@ -1101,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/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index ed936fbfd4..c65adf1a3f 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 ae75a15120..647b4e4a58 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -169,7 +169,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { private _launchProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): ITerminalChildProcess { if (!shellLaunchConfig.executable) { - this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig); + this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig, this._terminalInstanceService.getDefaultShell(platform.platform)); } const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file); @@ -181,8 +181,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions(); const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.setLocaleVariables); - this._logService.debug(`Terminal process launching`, shellLaunchConfig, initialCwd, cols, rows, env); - const useConpty = (shellLaunchConfig.forceWinpty !== true) && this._configHelper.config.windowsEnableConpty; + const useConpty = this._configHelper.config.windowsEnableConpty; return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 74e36a1f5f..60f25a591e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -44,7 +44,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); @@ -101,7 +101,7 @@ export abstract class TerminalService extends CommonTerminalService implements I } // 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)) { + 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; } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index b21f664d5a..701d3fd24b 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -58,6 +58,7 @@ 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'; @@ -112,7 +113,7 @@ 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(osOverride?: platform.OperatingSystem): boolean; @@ -192,12 +193,6 @@ export interface IShellLaunchConfig { * provided as nothing will be inherited from the process or any configuration. */ strictEnv?: boolean; - - /** - * Moving forward, conpty will be the default. However, there are cases where conpty is not ready - * to be the default. This property will force winpty to be used, even when conpty would normally be used. - */ - forceWinpty?: boolean; } export interface ITerminalService { @@ -259,6 +254,7 @@ export interface ITerminalService { findPrevious(): void; setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; + getDefaultShell(p: platform.Platform): string; selectDefaultWindowsShell(): Promise; setWorkspaceShellAllowed(isAllowed: boolean): void; @@ -694,7 +690,6 @@ export const enum ProcessState { KILLED_BY_PROCESS } - export interface ITerminalProcessExtHostProxy extends IDisposable { readonly terminalId: number; diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 4af692c014..e8f62a01c2 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -164,15 +164,14 @@ 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 shellConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.shell.${platformKey}`); const shellArgsConfigValue = fetchSetting(`terminal.integrated.shellArgs.${platformKey}`); - // const shellArgsConfigValue = this._workspaceConfigurationService.inspect(`terminal.integrated.shellArgs.${platformKey}`); - shell.executable = (isWorkspaceShellAllowed ? shellConfigValue.value : shellConfigValue.user) || shellConfigValue.default; + 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 diff --git a/src/vs/workbench/contrib/terminal/common/terminalService.ts b/src/vs/workbench/contrib/terminal/common/terminalService.ts index c628e4c1f2..8d661cc14d 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'; @@ -106,6 +106,7 @@ export abstract class TerminalService implements ITerminalService { 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; @@ -426,6 +427,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 be2c22374d..e746c3d018 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 728a676329..953aabe361 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 5dc59893c9..5b934f09bc 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalService.ts @@ -98,7 +98,7 @@ export class TerminalService extends BrowserTerminalService implements ITerminal }); } - protected _getDefaultShell(p: platform.Platform): string { + public getDefaultShell(p: platform.Platform): string { return getDefaultShell(p); } @@ -117,7 +117,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 +155,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 046588baa1..b06553f064 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') { @@ -69,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 { @@ -114,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); } @@ -146,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 } @@ -155,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); } @@ -176,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); } @@ -188,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 { @@ -198,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)); @@ -208,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 28d1193c6a..59f2815e48 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/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index be331acdf3..7552e2a32a 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -238,7 +238,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/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index cd2bea3e86..ab8897918e 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 16612e4235..65b46d808a 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 7c0bf67e75..c3f0a5a9da 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 40ed73b2df..80419757a9 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -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,7 +375,7 @@ 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, @@ -380,9 +385,14 @@ export class WebviewElement extends Disposable implements Webview { @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, 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/welcome/walkThrough/common/walkThroughInput.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts index 4380447820..d8e02a8ec5 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 f95b5526e7..bf3d22e161 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'; @@ -40,12 +40,12 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/electron-brow 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); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 373f1ddedb..3155e8ccfe 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -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; @@ -488,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 { diff --git a/src/vs/workbench/services/backup/common/backup.ts b/src/vs/workbench/services/backup/common/backup.ts index f4d8cfcacb..e9d7e45c72 100644 --- a/src/vs/workbench/services/backup/common/backup.ts +++ b/src/vs/workbench/services/backup/common/backup.ts @@ -9,6 +9,11 @@ import { ITextBufferFactory, ITextSnapshot } from 'vs/editor/common/model'; export const IBackupFileService = createDecorator('backupFileService'); +export interface IResolvedBackup { + value: ITextBufferFactory; + meta?: T; +} + /** * A service that handles any I/O and state associated with the backup system. */ @@ -42,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. @@ -55,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 7acc83272e..ef880c0dca 100644 --- a/src/vs/workbench/services/backup/node/backupFileService.ts +++ b/src/vs/workbench/services/backup/node/backupFileService.ts @@ -3,94 +3,112 @@ * Licensed under the Source EULA. 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 } from 'vs/workbench/services/backup/common/backup'; +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, ITextSnapshot } 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; @@ -116,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); } @@ -132,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; @@ -168,115 +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.readFileStream(backup).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: VSBuffer) => { - const chunkString = chunk.toString(); + // Metadata extraction + let metaRaw = ''; + let metaEndFound = false; - if (!metaFound && chunk) { - const metaIndex = chunkString.indexOf(BackupFileServiceImpl.META_MARKER); - if (metaIndex === -1) { - return VSBuffer.fromString(''); // meta not yet found, return empty string - } + // Add a filter method to filter out everything until the meta end marker + const metaPreambleFilter = (chunk: VSBuffer) => { + const chunkString = chunk.toString(); - metaFound = true; - return VSBuffer.fromString(chunkString.substr(metaIndex + 1)); // meta found, return everything after + 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)); } } @@ -290,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); @@ -299,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(); @@ -331,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 ef7a68b504..0000000000 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ /dev/null @@ -1,402 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. 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 { 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'; - -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 FileService(new NullLogService()); - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService())); - 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 0000000000..9785ba18bf --- /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 Source EULA. 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 b3d5a78aa8..9f7ca3582f 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -666,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 277022a9ae..e60df44362 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -396,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]; diff --git a/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts index b7ecb4f989..ef7f0fe4ea 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 0a43d18e62..e9821d1030 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 aaf81fcab6..aac9b726eb 100644 --- a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts @@ -22,11 +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; @@ -40,11 +41,6 @@ enum UpdateResult { InvalidPath } -// Reference: https://en.wikipedia.org/wiki/Filename -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 class RemoteFileDialog { private options: IOpenDialogOptions; private currentFolder: URI; @@ -63,6 +59,7 @@ export class RemoteFileDialog { private userHome: URI; private badPath: string | undefined; private remoteAgentEnvironment: IRemoteAgentEnvironment | null; + private separator: string; constructor( @IFileService private readonly fileService: IFileService, @@ -159,6 +156,7 @@ 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 stat: IFileStat | undefined; @@ -280,10 +278,11 @@ export class RemoteFileDialog { // 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 valueUri = this.remoteUriFrom(this.trimTrailingSlash(this.filePickBox.value)); + const filePickBoxUri = this.filePickBoxValue(); + const valueUri = resources.removeTrailingPathSeparator(filePickBoxUri); 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)); + if (!resources.isEqual(resources.removeTrailingPathSeparator(this.currentFolder), valueUri, true)) { + updated = await this.tryUpdateItems(value, filePickBoxUri); } if (updated === UpdateResult.NotUpdated) { this.setActiveItems(value); @@ -305,7 +304,7 @@ export class RemoteFileDialog { this.filePickBox.show(); this.contextKey.set(true); - await this.updateItems(homedir, false, this.trailing); + 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 { @@ -331,72 +330,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 { this.filePickBox.busy = true; - let resolveValue: URI | undefined; - let navigateValue: URI | undefined; - let inputUri: URI | undefined; - let inputUriDirname: URI | undefined; - let stat: IFileStat | undefined; - let statDirname: IFileStat | undefined; - try { - inputUri = resources.removeTrailingPathSeparator(this.remoteUriFrom(this.filePickBox.value)); - inputUriDirname = resources.dirname(inputUri); - statDirname = await this.fileService.resolve(inputUriDirname); - stat = await this.fileService.resolve(inputUri); - } catch (e) { - // do nothing + 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 undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check + } + } else { + // If the items have updated, don't try to resolve + if ((await this.tryUpdateItems(this.filePickBox.value, this.filePickBoxValue())) !== UpdateResult.NotUpdated) { + return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check + } } + 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 (navigateValue) { - // Try to navigate into the folder. - await this.updateItems(navigateValue, true, this.trailing); - } else { - if (resolveValue) { - resolveValue = this.addPostfix(resolveValue); - } - if (await this.validate(resolveValue)) { - this.filePickBox.busy = false; - return resolveValue; - } + if (resolveValue) { + resolveValue = this.addPostfix(resolveValue); + } + 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 (this.filePickBox.busy) { - this.badPath = undefined; - return UpdateResult.Updating; - } else if ((value.length > 0) && ((value[value.length - 1] === '~') || (value[0] === '~'))) { + 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); @@ -415,7 +423,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); @@ -606,7 +614,7 @@ export class RemoteFileDialog { // 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(uri, message); - } else if (!(await this.isValidBaseName(resources.basename(uri)))) { + } 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); @@ -638,24 +646,16 @@ export class RemoteFileDialog { this.userEnteredPathSegment = trailing ? trailing : ''; this.autoCompletePathSegment = ''; const newValue = trailing ? this.pathFromUri(resources.joinPath(newFolder, trailing)) : this.pathFromUri(newFolder, true); - const oldFolder = this.currentFolder; - const newFolderPath = this.pathFromUri(newFolder, true); - this.currentFolder = this.remoteUriFrom(newFolderPath); + 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.substring(0, newValue.length), newValue)) { - this.filePickBox.valueSelection = [0, this.filePickBox.value.length]; - this.insertText(newValue, newValue); - } else if (force || equalsIgnoreCase(this.pathFromUri(resources.dirname(oldFolder), true), newFolderPath)) { - // This is the case where the user went up one dir or is clicking on dirs. We need to make sure that we remove the final dir. - this.filePickBox.valueSelection = [newFolderPath.length, this.filePickBox.value.length]; - this.insertText(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); } if (force && trailing) { // Keep the cursor position in front of the save as name. @@ -668,15 +668,14 @@ export class RemoteFileDialog { } 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; } @@ -684,7 +683,7 @@ 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)); } @@ -699,37 +698,6 @@ export class RemoteFileDialog { return isWindowsOS; } - private async isValidBaseName(name: string): Promise { - if (!name || name.length === 0 || /^\s+$/.test(name)) { - return false; // require a name that is not just whitespace - } - - const isWindowsOS = await this.isWindowsOS(); - const INVALID_FILE_CHARS = isWindowsOS ? WINDOWS_INVALID_FILE_CHARS : UNIX_INVALID_FILE_CHARS; - 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 (isWindowsOS && WINDOWS_FORBIDDEN_NAMES.test(name)) { - return false; // check for certain invalid file names - } - - if (name === '.' || name === '..') { - return false; // check for reserved values - } - - if (isWindowsOS && name[name.length - 1] === '.') { - return false; // Windows: file cannot end with a "." - } - - if (isWindowsOS && name.length !== name.trim().length) { - return false; // Windows: file cannot end with a whitespace - } - - return true; - } - private endsWithSlash(s: string) { return /[\/\\]$/.test(s); } @@ -743,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; } @@ -801,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 58162b8e58..bac156956e 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -532,9 +532,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { const untitledInput = input; if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) { // {{SQL CARBON EDIT}} - - let modeId: string = untitledInput.language ? untitledInput.language : getFileMode(this.instantiationService, untitledInput.resource); - return convertEditorInput(this.untitledEditorService.createOrGet(untitledInput.resource, modeId, untitledInput.contents, untitledInput.encoding), undefined, this.instantiationService); + return convertEditorInput(this.untitledEditorService.createOrGet(untitledInput.resource, untitledInput.mode, untitledInput.contents, untitledInput.encoding), undefined, this.instantiationService); } // Resource Editor Support @@ -546,14 +544,14 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // {{SQL CARBON EDIT}} - return convertEditorInput(this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.forceFile) as EditorInput, + return convertEditorInput(this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.mode, resourceInput.forceFile) as EditorInput, undefined, this.instantiationService); } 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) { @@ -564,10 +562,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; @@ -577,7 +583,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 @@ -587,13 +593,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 eec96b9f65..1f5fbba7c9 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,6 +45,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 { } } 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 d112350ed7..987bd9c093 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -29,6 +29,8 @@ 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'; // {{SQL CARBON EDIT}} - Disable editor tests /* @@ -36,10 +38,10 @@ 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'; } @@ -58,6 +60,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 { @@ -77,7 +81,7 @@ class FileServiceProvider extends Disposable { } } -*/suite('Editor service', () => {/* +*/suite('EditorService', () => {/* function registerTestEditorInput(): void { Registry.as(Extensions.Editors).registerEditor(new EditorDescriptor(TestEditorControl, 'MyTestEditorForEditorService', 'My Test Editor For Next Editor Service'), new SyncDescriptor(TestEditorInput)); @@ -85,7 +89,7 @@ class FileServiceProvider extends Disposable { registerTestEditorInput(); - test('basics', function () { + test('basics', async () => { const partInstantiator = workbenchInstantiationService(); const part = partInstantiator.createInstance(EditorPart); @@ -114,51 +118,49 @@ class FileServiceProvider extends Disposable { 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); @@ -173,18 +175,16 @@ class FileServiceProvider extends Disposable { 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 () { @@ -236,10 +236,15 @@ class FileServiceProvider extends Disposable { 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); @@ -252,6 +257,18 @@ class FileServiceProvider extends Disposable { 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); @@ -259,6 +276,14 @@ class FileServiceProvider extends Disposable { // 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({ resource: URI.file('/some/path.txt'), forceUntitled: true, options: { selection: { startLineNumber: 1, startColumn: 1 } } }); @@ -278,6 +303,10 @@ class FileServiceProvider extends Disposable { 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) { @@ -300,7 +329,7 @@ class FileServiceProvider extends Disposable { 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); @@ -313,7 +342,7 @@ class FileServiceProvider extends Disposable { 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); @@ -329,30 +358,26 @@ class FileServiceProvider extends Disposable { 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); @@ -368,22 +393,20 @@ class FileServiceProvider extends Disposable { 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/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index ba73aa6bcb..90f69da182 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -255,7 +255,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/files/test/node/diskFileService.test.ts b/src/vs/workbench/services/files/test/node/diskFileService.test.ts index 19e141d72d..328718350c 100644 --- a/src/vs/workbench/services/files/test/node/diskFileService.test.ts +++ b/src/vs/workbench/services/files/test/node/diskFileService.test.ts @@ -1523,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'); diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index aec7bbf220..68ba90e0a6 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 21b3e6af8d..eeffa3f7db 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 6b54213771..74dda793dd 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 a0204f6cf2..7b50510857 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 @@ -274,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/preferences/common/keybindingsEditorModel.ts b/src/vs/workbench/services/preferences/common/keybindingsEditorModel.ts index cab5d30451..4c805f68f6 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/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts index 3a02fcdac8..ddd0314c10 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 9b6e031ec7..3fcd994409 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/test/progressService.test.ts b/src/vs/workbench/services/progress/test/progressService.test.ts index a491553d18..f0dc396eae 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/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index d678655a62..1b80a9ac46 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 6b63bd17ae..7fd3fb7de8 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).value; for (const entry of cachedEntries) { // Check if this entry is a match for the search value diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 42bd2082c0..8a07bf7270 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -9,7 +9,7 @@ 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, ITextFileStreamContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; @@ -18,13 +18,12 @@ 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_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'; @@ -33,6 +32,13 @@ import { isEqual, isEqualOrParent, extname, basename } from 'vs/base/common/reso 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. */ @@ -57,16 +63,16 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private resource: URI; - private contentEncoding: string; // encoding as reported from disk - private preferredEncoding: string; // encoding as chosen by the user + 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 blockModelContentChange: boolean; - private createTextEditorModelPromise: Promise | null; - - private lastResolvedDiskStat: IFileStatWithMetadata; + private lastResolvedFileStat: IFileStatWithMetadata; private autoSaveAfterMillies?: number; private autoSaveAfterMilliesEnabled: boolean; @@ -88,6 +94,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil constructor( resource: URI, preferredEncoding: string, + preferredMode: string, @INotificationService private readonly notificationService: INotificationService, @IModeService modeService: IModeService, @IModelService modelService: IModelService, @@ -104,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; @@ -199,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 { @@ -245,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. @@ -254,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: ITextFileStreamContent = { - resource: this.resource, - name: basename(this.resource), - mtime: Date.now(), - size: 0, - etag: ETAG_DISABLED, // always allow to save content restored from a backup (see https://github.com/Microsoft/vscode/issues/72343) - value: createTextBufferFactory(''), // will be filled later from backup - encoding: this.textFileService.encoding.getPreferredWriteEncoding(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 { @@ -302,8 +345,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil let etag: string | undefined; if (forceReadFromDisk) { etag = ETAG_DISABLED; // disable ETag if we enforce to read from disk - } else if (this.lastResolvedDiskStat) { - etag = this.lastResolvedDiskStat.etag; // otherwise respect etag to support caching + } 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 @@ -321,12 +364,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // 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; @@ -356,37 +398,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } } - private async loadWithContent(content: ITextFileStreamContent, options?: ILoadOptions, backup?: URI): Promise { - const model = await this.doLoadWithContent(content, backup); - - // 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: ITextFileStreamContent, 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, @@ -409,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 { @@ -435,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; } @@ -444,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 @@ -489,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); @@ -526,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 @@ -657,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(); } @@ -687,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 undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check + } + + // We require a resolved model from this point on, since we are about to write data to disk. + if (!this.isResolved()) { + return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check } // Under certain conditions we do a short-cut of flushing contents to disk when we can assume that @@ -713,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); @@ -736,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(); @@ -852,19 +855,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 */)); } @@ -898,23 +904,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; } } @@ -937,10 +943,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: @@ -1022,12 +1024,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return true; } - isResolved(): boolean { - return !isUndefinedOrNull(this.lastResolvedDiskStat); + isResolved(): boolean { // {{SQL CARBON EDIT}} @anthonydresser strict-null-check + return !!this.textEditorModel; } isReadonly(): boolean { - return !!(this.lastResolvedDiskStat && this.lastResolvedDiskStat.isReadonly); + return !!(this.lastResolvedFileStat && this.lastResolvedFileStat.isReadonly); } isDisposed(): boolean { @@ -1039,7 +1041,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } getStat(): IFileStatWithMetadata { - return this.lastResolvedDiskStat; + return this.lastResolvedFileStat; } dispose(): void { @@ -1048,8 +1050,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 3ca04417c6..6e5b3b6048 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 @@ -204,6 +204,11 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Remove from pending loads this.mapResourceToPendingModelLoaders.delete(resource); + // Apply mode if provided + if (options && options.mode) { + resolvedModel.setMode(options.mode); + } + return resolvedModel; } catch (error) { diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 445d7d01e6..f36e3b0221 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -40,6 +40,7 @@ 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. @@ -238,59 +239,44 @@ export abstract class TextFileService extends Disposable implements ITextFileSer 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 { @@ -503,10 +489,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer 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); })); } @@ -757,14 +740,14 @@ export abstract class TextFileService extends Disposable implements ITextFileSer 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[] { @@ -873,17 +856,12 @@ export abstract class TextFileService extends Disposable implements ITextFileSer // 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())); - if (sourceModel.textEditorModel) { - const language = sourceModel.textEditorModel.getLanguageIdentifier(); - if (language.id > 1) { - targetModel.textEditorModel.setMode(language); // only use if more specific than plain/text - } + const mode = sourceModel.textEditorModel.getLanguageIdentifier(); + if (mode.language !== PLAINTEXT_MODE_ID) { + targetModel.textEditorModel.setMode(mode); // only use if more specific than plain/text } } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 729429536d..8c46a858ac 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -6,7 +6,7 @@ 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 { 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'; @@ -367,6 +367,11 @@ export interface IModelLoadOrCreateOptions { */ reason?: LoadReason; + /** + * The language mode to use for the model text content. + */ + mode?: string; + /** * The encoding to use when resolving the model text content. */ @@ -443,19 +448,15 @@ export interface ILoadOptions { reason?: LoadReason; } -export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport { +export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport { readonly onDidContentChange: Event; readonly onDidStateChange: Event; - getVersionId(): number; - getResource(): URI; hasState(state: ModelState): boolean; - getETag(): string | null; - updatePreferredEncoding(encoding: string): void; save(options?: ISaveOptions): Promise; @@ -464,16 +465,17 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport revert(soft?: boolean): Promise; - createSnapshot(): ITextSnapshot | null; + backup(target?: URI): Promise; isDirty(): boolean; - isResolved(): boolean; + isResolved(): this is IResolvedTextFileEditorModel; isDisposed(): boolean; } export interface IResolvedTextFileEditorModel extends ITextFileEditorModel { + readonly textEditorModel: ITextModel; createSnapshot(): ITextSnapshot; diff --git a/src/vs/workbench/services/textfile/node/textFileService.ts b/src/vs/workbench/services/textfile/node/textFileService.ts index b246b83ff4..d5b739af9c 100644 --- a/src/vs/workbench/services/textfile/node/textFileService.ts +++ b/src/vs/workbench/services/textfile/node/textFileService.ts @@ -11,7 +11,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { URI } from 'vs/base/common/uri'; 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'; @@ -26,7 +26,6 @@ import { VSBufferReadable, VSBuffer, VSBufferReadableStream } from 'vs/base/comm import { Readable } from 'stream'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; -import { MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/platform/files/node/fileConstants'; import { ITextSnapshot } from 'vs/editor/common/model'; export class NodeTextFileService extends TextFileService { diff --git a/src/vs/workbench/services/textfile/node/textResourcePropertiesService.ts b/src/vs/workbench/services/textfile/node/textResourcePropertiesService.ts index acbc9cda7d..1780a2570c 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/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index a88e9ab674..cb870fd3d3 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -14,6 +14,7 @@ import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/commo 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 c8081048b0..96841bc45c 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.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index b4bdb0c4b1..3ad5111692 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); @@ -397,7 +395,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 587a60d641..d276b52b3f 100644 --- a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts @@ -53,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') { @@ -67,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 () => { @@ -135,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/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledEditorService.ts index 8fe6db11e1..e57ae79388 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). @@ -84,7 +84,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 @@ -191,10 +191,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 @@ -214,44 +214,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 @@ -266,7 +269,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/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index 5906a7e3a5..c667845604 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 6367574260..07a0555861 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 453e5d3707..727b95eb42 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 8f1f6660e9..c758eec3e6 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 995e9461e4..fff7ee2b6a 100644 --- a/src/vs/workbench/test/common/editor/resourceEditorInput.test.ts +++ b/src/vs/workbench/test/common/editor/resourceEditorInput.test.ts @@ -12,17 +12,16 @@ import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestSe import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; 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 5f7008bdde..1c2d513a99 100644 --- a/src/vs/workbench/test/common/editor/untitledEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledEditor.test.ts @@ -16,6 +16,7 @@ import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; 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/electron-browser/api/mainThreadSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts index 9709fb9238..2d084b99dd 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts @@ -37,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(); @@ -70,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(); @@ -105,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(); @@ -132,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 7232145354..844097ffba 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'; @@ -85,7 +85,7 @@ 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); @@ -1093,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(); } @@ -1108,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.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 99% rename from src/vs/workbench/workbench.nodeless.main.ts rename to src/vs/workbench/workbench.web.main.ts index 540f000684..0e4a8d5f5b 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 @@ -94,7 +94,7 @@ import { IBroadcastService, NullBroadcastService } from 'vs/workbench/services/b 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'; diff --git a/yarn.lock b/yarn.lock index 8caf03514d..feea5b4c92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -832,7 +832,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= @@ -898,7 +898,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= @@ -1721,6 +1721,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" @@ -1936,6 +1941,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@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-3.0.3.tgz#83b1c64aea1c6afa69beaf50b55ac1bc4d13e2b8" @@ -2175,6 +2185,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" @@ -2511,6 +2528,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" @@ -2956,6 +2983,11 @@ event-stream@3.3.4, event-stream@^3.3.4, event-stream@~3.3.4: stream-combiner "~0.0.4" through "~2.3.1" +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" @@ -3373,6 +3405,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" @@ -4256,6 +4295,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" @@ -4333,6 +4377,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.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -4908,6 +4975,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" @@ -5042,7 +5114,7 @@ js-tokens@^4.0.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -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== @@ -5050,7 +5122,7 @@ js-yaml@3.x, js-yaml@^3.5.1, js-yaml@^3.7.0: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^3.11.0: +js-yaml@^3.11.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== @@ -5773,7 +5845,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== @@ -6426,6 +6498,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" @@ -6433,7 +6517,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= @@ -6858,6 +6942,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" @@ -7314,6 +7407,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.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -7756,6 +7854,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" @@ -9030,36 +9133,30 @@ tslib@^1.8.1, tslib@^1.9.0: integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== tslint-microsoft-contrib@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/tslint-microsoft-contrib/-/tslint-microsoft-contrib-6.0.0.tgz#7bff73c9ad7a0b7eb5cdb04906de58f42a2bf7a2" - integrity sha512-R//efwn+34IUjTJeYgNDAJdzG0jyLWIehygPt/PHuZAieTolFVS56FgeFW7DOLap9ghXzMiFPTmDgm54qaL7QA== + version "6.1.1" + resolved "https://registry.yarnpkg.com/tslint-microsoft-contrib/-/tslint-microsoft-contrib-6.1.1.tgz#1de9b5c2867f6cec762bab9d8e1619f2b8eb59fc" + integrity sha512-u6tK+tgt8Z1YRJxe4kpWWEx/6FTFxdga50+osnANifsfC7BMzh8c/t/XbNntTRiwMfpHkQ9xtUjizCDLxN1Yeg== dependencies: tsutils "^2.27.2 <2.29.0" -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.27.2: - version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" - integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== - dependencies: - tslib "^1.8.1" + tsutils "^2.29.0" "tsutils@^2.27.2 <2.29.0": version "2.28.0" @@ -9068,6 +9165,13 @@ tsutils@^2.27.2: dependencies: tslib "^1.8.1" +tsutils@^2.29.0: + version "2.29.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== + dependencies: + tslib "^1.8.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" @@ -9266,6 +9370,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" @@ -9345,6 +9456,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" @@ -9740,10 +9856,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"