diff --git a/.yarnrc b/.yarnrc index ff946c7a25..e49da7f447 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "4.2.10" +target "6.0.9" runtime "electron" diff --git a/build/azure-pipelines/exploration-build.yml b/build/azure-pipelines/exploration-build.yml index 39a8f23b26..305852528b 100644 --- a/build/azure-pipelines/exploration-build.yml +++ b/build/azure-pipelines/exploration-build.yml @@ -1,12 +1,8 @@ pool: vmImage: 'Ubuntu-16.04' -trigger: - branches: - include: ['master'] -pr: - branches: - include: ['master'] +trigger: none +pr: none steps: - task: NodeTool@0 diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 4ecced99f3..0ca58327e7 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -45,6 +45,7 @@ var editorResources = [ '!out-build/vs/base/browser/ui/splitview/**/*', '!out-build/vs/base/browser/ui/toolbar/**/*', '!out-build/vs/base/browser/ui/octiconLabel/**/*', + '!out-build/vs/base/browser/ui/codiconLabel/**/*', '!out-build/vs/workbench/**', '!**/test/**' ]; @@ -91,6 +92,7 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { ], redirects: { 'vs/base/browser/ui/octiconLabel/octiconLabel': 'vs/base/browser/ui/octiconLabel/octiconLabel.mock', + 'vs/base/browser/ui/codiconLabel/codiconLabel': 'vs/base/browser/ui/codiconLabel/codiconLabel.mock', }, shakeLevel: 2, // 0-Files, 1-InnerFile, 2-ClassMembers importIgnorePattern: /(^vs\/css!)|(promise-polyfill\/polyfill)/, diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index b70ef55460..4d329b6169 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -73,6 +73,7 @@ const indentationFilter = [ // except multiple specific folders '!**/octicons/**', + '!**/codicon/**', '!**/fixtures/**', '!**/lib/**', '!extensions/**/out/**', @@ -382,8 +383,6 @@ function hygiene(some) { const sqlTsl = es.through(function (file) { //TODO restore const contents = file.contents.toString('utf8'); sqlTsLinter.lint(file.relative, contents, tslintSqlConfiguration.results); - - this.emit('data', file); }); const productJsonFilter = filter('product.json', { restore: true }); diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index eaeaad94e9..638e0ae8e7 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -79,6 +79,7 @@ const vscodeResources = [ 'out-build/vs/base/node/languagePacks.js', 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh,ps.sh}', 'out-build/vs/base/browser/ui/octiconLabel/octicons/**', + 'out-build/vs/base/browser/ui/codiconLabel/codicon/**', 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/contrib/debug/**/*.json', 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', @@ -86,7 +87,6 @@ const vscodeResources = [ 'out-build/vs/workbench/contrib/webview/electron-browser/pre/*.js', 'out-build/vs/**/markdown.css', 'out-build/vs/workbench/contrib/tasks/**/*.json', - 'out-build/vs/workbench/contrib/welcome/walkThrough/**/*.md', 'out-build/vs/platform/files/**/*.exe', 'out-build/vs/platform/files/**/*.md', 'out-build/vs/code/electron-browser/workbench/**', diff --git a/build/gulpfile.vscode.web.js b/build/gulpfile.vscode.web.js index 7978d3520e..e654979844 100644 --- a/build/gulpfile.vscode.web.js +++ b/build/gulpfile.vscode.web.js @@ -6,150 +6,11 @@ 'use strict'; const gulp = require('gulp'); -const path = require('path'); -const es = require('event-stream'); -const util = require('./lib/util'); -const task = require('./lib/task'); -const common = require('./lib/optimize'); -const product = require('../product.json'); -const rename = require('gulp-rename'); -const filter = require('gulp-filter'); -const json = require('gulp-json-editor'); -const _ = require('underscore'); -const deps = require('./dependencies'); -const vfs = require('vinyl-fs'); -const packageJson = require('../package.json'); -const { compileBuildTask } = require('./gulpfile.compile'); -const REPO_ROOT = path.dirname(__dirname); -const commit = util.getVersion(REPO_ROOT); -const BUILD_ROOT = path.dirname(REPO_ROOT); -const WEB_FOLDER = path.join(REPO_ROOT, 'remote', 'web'); +const noop = () => { return Promise.resolve(); }; -const productionDependencies = deps.getProductionDependencies(WEB_FOLDER); - -const nodeModules = Object.keys(product.dependencies || {}) - .concat(_.uniq(productionDependencies.map(d => d.name))); - -const vscodeWebResources = [ - - // Workbench - 'out-build/vs/{base,platform,editor,workbench}/**/*.{svg,png,html}', - 'out-build/vs/base/browser/ui/octiconLabel/octicons/**', - 'out-build/vs/**/markdown.css', - - // Webview - 'out-build/vs/workbench/contrib/webview/browser/pre/*.js', - - // Extension Worker - 'out-build/vs/workbench/services/extensions/worker/extensionHostWorkerMain.js', - - // Excludes - '!out-build/vs/**/{node,electron-browser,electron-main}/**', - '!out-build/vs/editor/standalone/**', - '!out-build/vs/workbench/**/*-tb.png', - '!**/test/**' -]; - -const buildfile = require('../src/buildfile'); - -const vscodeWebEntryPoints = [ - buildfile.workbenchWeb, - buildfile.serviceWorker, - buildfile.workerExtensionHost, - buildfile.keyboardMaps, - buildfile.base -]; - -const optimizeVSCodeWebTask = task.define('optimize-vscode-web', task.series( - util.rimraf('out-vscode-web'), - common.optimizeTask({ - src: 'out-build', - entryPoints: _.flatten(vscodeWebEntryPoints), - otherSources: [], - resources: vscodeWebResources, - loaderConfig: common.loaderConfig(nodeModules), - out: 'out-vscode-web', - bundleInfo: undefined - }) -)); - -const minifyVSCodeWebTask = task.define('minify-vscode-web', task.series( - optimizeVSCodeWebTask, - util.rimraf('out-vscode-web-min'), - common.minifyTask('out-vscode-web', `https://ticino.blob.core.windows.net/sourcemaps/${commit}/core`) -)); -gulp.task(minifyVSCodeWebTask); - -function packageTask(sourceFolderName, destinationFolderName) { - const destination = path.join(BUILD_ROOT, destinationFolderName); - - return () => { - const src = gulp.src(sourceFolderName + '/**', { base: '.' }) - .pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); })) - .pipe(filter(['**', '!**/*.js.map'])); - - const sources = es.merge(src); - - let version = packageJson.version; - const quality = product.quality; - - if (quality && quality !== 'stable') { - version += '-' + quality; - } - - const name = product.nameShort; - const packageJsonStream = gulp.src(['remote/web/package.json'], { base: 'remote/web' }) - .pipe(json({ name, version })); - - const date = new Date().toISOString(); - - const productJsonStream = gulp.src(['product.json'], { base: '.' }) - .pipe(json({ commit, date })); - - const license = gulp.src(['remote/LICENSE'], { base: 'remote' }); - - const dependenciesSrc = _.flatten(productionDependencies.map(d => path.relative(REPO_ROOT, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`])); - - const deps = gulp.src(dependenciesSrc, { base: 'remote/web', dot: true }) - .pipe(filter(['**', '!**/package-lock.json'])) - .pipe(util.cleanNodeModules(path.join(__dirname, '.nativeignore'))); - - const favicon = gulp.src('resources/server/favicon.ico', { base: 'resources/server' }); - - let all = es.merge( - packageJsonStream, - productJsonStream, - license, - sources, - deps, - favicon - ); - - let result = all - .pipe(util.skipDirectories()) - .pipe(util.fixWin32DirectoryPermissions()); - - return result.pipe(vfs.dest(destination)); - }; -} - -const dashed = (str) => (str ? `-${str}` : ``); - -['', 'min'].forEach(minified => { - const sourceFolderName = `out-vscode-web${dashed(minified)}`; - const destinationFolderName = `vscode-web`; - - const vscodeWebTaskCI = task.define(`vscode-web${dashed(minified)}-ci`, task.series( - minified ? minifyVSCodeWebTask : optimizeVSCodeWebTask, - util.rimraf(path.join(BUILD_ROOT, destinationFolderName)), - packageTask(sourceFolderName, destinationFolderName) - )); - gulp.task(vscodeWebTaskCI); - - const vscodeWebTask = task.define(`vscode-web${dashed(minified)}`, task.series( - compileBuildTask, - vscodeWebTaskCI - )); - gulp.task(vscodeWebTask); -}); +gulp.task('minify-vscode-web', noop); +gulp.task('vscode-web', noop); +gulp.task('vscode-web-min', noop); +gulp.task('vscode-web-ci', noop); +gulp.task('vscode-web-min-ci', noop); diff --git a/build/lib/i18n.js b/build/lib/i18n.js index d65b4a7ce4..4ee4e9c525 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -1086,7 +1086,7 @@ function prepareI18nPackFiles(externalExtensions, resultingTranslationPaths, pse resultingTranslationPaths.push({ id: 'vscode', resourceName: 'main.i18n.json' }); this.queue(translatedMainFile); for (let extension in extensionsPacks) { - const translatedExtFile = createI18nFile(`.build/extensions/${extension}`, extensionsPacks[extension]); + const translatedExtFile = createI18nFile(`extensions/${extension}`, extensionsPacks[extension]); this.queue(translatedExtFile); const externalExtensionId = externalExtensions[extension]; if (externalExtensionId) { diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index da50b1eb2d..1987d5ca9c 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -106,6 +106,10 @@ "name": "vs/workbench/contrib/quickopen", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/userData", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/remote", "project": "vscode-workbench" @@ -182,6 +186,10 @@ "name": "vs/workbench/contrib/outline", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/userDataSync", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/actions", "project": "vscode-workbench" @@ -277,6 +285,10 @@ { "name": "vs/workbench/services/notification", "project": "vscode-workbench" + }, + { + "name": "vs/workbench/services/userData", + "project": "vscode-workbench" } ] } diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index c3d7228f42..60997611ea 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -1253,7 +1253,7 @@ export function prepareI18nPackFiles(externalExtensions: Map, resultingT this.queue(translatedMainFile); for (let extension in extensionsPacks) { - const translatedExtFile = createI18nFile(`.build/extensions/${extension}`, extensionsPacks[extension]); + const translatedExtFile = createI18nFile(`extensions/${extension}`, extensionsPacks[extension]); this.queue(translatedExtFile); const externalExtensionId = externalExtensions[extension]; diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js index 1cbc34b916..6b1f5982b9 100644 --- a/build/npm/preinstall.js +++ b/build/npm/preinstall.js @@ -31,4 +31,4 @@ if (!/yarn\.js$|yarnpkg$/.test(process.env['npm_execpath'])) { if (err) { console.error(''); process.exit(1); -} \ No newline at end of file +} diff --git a/cglicenses.json b/cglicenses.json index df57f699bd..1e9287cbaf 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -46,58 +46,6 @@ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." ] }, - { - // Reason: The npm module does not contain a repository field. - // waiting for https://github.com/xtermjs/xterm.js/issues/2395 - "name": "xterm-addon-search", - "fullLicenseText": [ - "Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and associated documentation files (the \"Software\"), to deal", - "in the Software without restriction, including without limitation the rights", - "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", - "copies of the Software, and to permit persons to whom the Software is", - "furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in", - "all copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", - "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", - "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN", - "THE SOFTWARE." - ] - }, - { - // Reason: The npm module does not contain a repository field. - // waiting for https://github.com/xtermjs/xterm.js/issues/2395 - "name": "xterm-addon-web-links", - "fullLicenseText": [ - "Copyright (c) 2017, The xterm.js authors (https://github.com/xtermjs/xterm.js)", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy", - "of this software and associated documentation files (the \"Software\"), to deal", - "in the Software without restriction, including without limitation the rights", - "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", - "copies of the Software, and to permit persons to whom the Software is", - "furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in", - "all copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", - "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", - "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN", - "THE SOFTWARE." - ] - }, { // Reason: The license at https://git.coolaj86.com/coolaj86/atob.js/src/branch/master/LICENSE // cannot be found by the OSS tool automatically. diff --git a/cgmanifest.json b/cgmanifest.json index d26f37b620..8e8fc3484f 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "c6a08e5368de4352903e702cde750b33239a50ab" + "commitHash": "91f08db83c2ce8c722ddf0911ead8f7c473bedfa" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "69.0.3497.128" + "version": "76.0.3809.146" }, { "component": { @@ -48,11 +48,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "8c70b2084ce5f76ea1e3b3c4ccdeee4483fe338b" + "commitHash": "64219741218aa87e259cf8257596073b8e747f0a" } }, "isOnlyProductionDependency": true, - "version": "10.11.0" + "version": "12.4.0" }, { "component": { @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "4e4c7527c63fcf27dffaeb58bde996b8d859c0ed" + "commitHash": "407747b48c47cdeed156a73dde1c47609470c95a" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "4.2.10" + "version": "6.0.9" }, { "component": { diff --git a/extensions/configuration-editing/src/settingsDocumentHelper.ts b/extensions/configuration-editing/src/settingsDocumentHelper.ts index dee6fe8f7e..b37c984b6a 100644 --- a/extensions/configuration-editing/src/settingsDocumentHelper.ts +++ b/extensions/configuration-editing/src/settingsDocumentHelper.ts @@ -54,6 +54,7 @@ export class SettingsDocument { completions.push(this.newSimpleCompletionItem('${folderName}', range, localize('folderName', "name of the workspace folder the file is contained in (e.g. myFolder)"))); completions.push(this.newSimpleCompletionItem('${folderPath}', range, localize('folderPath', "file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)"))); completions.push(this.newSimpleCompletionItem('${appName}', range, localize('appName', "e.g. VS Code"))); + completions.push(this.newSimpleCompletionItem('${remoteName}', range, localize('remoteName', "e.g. SSH"))); completions.push(this.newSimpleCompletionItem('${dirty}', range, localize('dirty', "a dirty indicator if the active editor is dirty"))); completions.push(this.newSimpleCompletionItem('${separator}', range, localize('separator', "a conditional separator (' - ') that only shows when surrounded by variables with values"))); diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index e022411cc9..1f0a02567a 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -9,7 +9,7 @@ import { Repository, GitResourceGroup } from './repository'; import { Model } from './model'; import { debounce } from './decorators'; import { filterEvent, dispose, anyEvent, fireEvent } from './util'; -import { GitErrorCodes } from './api/git'; +import { GitErrorCodes, Status } from './api/git'; type Callback = { resolve: (status: boolean) => void, reject: (err: any) => void }; @@ -122,12 +122,18 @@ class GitDecorationProvider implements DecorationProvider { } private collectDecorationData(group: GitResourceGroup, bucket: Map): void { - group.resourceStates.forEach(r => { - if (r.resourceDecoration) { + for (const r of group.resourceStates) { + const decoration = r.resourceDecoration; + + if (decoration) { // not deleted and has a decoration - bucket.set(r.original.toString(), r.resourceDecoration); + bucket.set(r.original.toString(), decoration); + + if (r.type === Status.INDEX_RENAMED) { + bucket.set(r.resourceUri.toString(), decoration); + } } - }); + } } private collectSubmoduleDecorationData(bucket: Map): void { diff --git a/extensions/image-preview/icon.png b/extensions/image-preview/icon.png new file mode 100644 index 0000000000..64dcf7d463 Binary files /dev/null and b/extensions/image-preview/icon.png differ diff --git a/extensions/image-preview/icon.svg b/extensions/image-preview/icon.svg new file mode 100644 index 0000000000..0eb0f4c103 --- /dev/null +++ b/extensions/image-preview/icon.svg @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/extensions/image-preview/media/main.js b/extensions/image-preview/media/main.js index f2bb9e4eb7..70ed69aad8 100644 --- a/extensions/image-preview/media/main.js +++ b/extensions/image-preview/media/main.js @@ -58,7 +58,8 @@ 20 ]; - const isMac = getSettings().isMac; + const settings = getSettings(); + const isMac = settings.isMac; const vscode = acquireVsCodeApi(); @@ -71,7 +72,7 @@ // Elements const container = /** @type {HTMLElement} */(document.querySelector('body')); - const image = document.querySelector('img'); + const image = document.createElement('img'); function updateScale(newScale) { if (!image || !image.parentElement) { @@ -248,6 +249,9 @@ } }); + image.src = decodeURI(settings.src); + document.body.append(image); + window.addEventListener('message', e => { switch (e.data.type) { case 'setScale': diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index aad1416c7b..b7f2aeb6b8 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -4,6 +4,7 @@ "description": "%description%", "version": "1.0.0", "publisher": "vscode", + "icon": "icon.png", "enableProposedApi": true, "license": "MIT", "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", @@ -24,7 +25,8 @@ "displayName": "%webviewEditors.displayName%", "selector": [ { - "filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,tga,tif,tiff,webp}" + "filenamePattern": "*.{jpg,jpe,jpeg,png,bmp,gif,ico,tga,tif,tiff,webp}", + "mime": "image/*" } ] } diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index 97d4d7c29b..daf7bc520d 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -80,7 +80,8 @@ export class Preview extends Disposable { private getWebiewContents(webviewEditor: vscode.WebviewEditor, resource: vscode.Uri): string { const settings = { - isMac: process.platform === 'darwin' + isMac: process.platform === 'darwin', + src: this.getResourcePath(webviewEditor, resource) }; return /* html */` @@ -95,12 +96,19 @@ export class Preview extends Disposable { - `; } + private getResourcePath(webviewEditor: vscode.WebviewEditor, resource: vscode.Uri) { + if (resource.scheme === 'data') { + return encodeURI(resource.toString(true)); + } + + return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString(true)); + } + private extensionResource(path: string) { return this.webviewEditor.webview.asWebviewUri(this.extensionRoot.with({ path: this.extensionRoot.path + path diff --git a/extensions/image-preview/src/typings/ref.d.ts b/extensions/image-preview/src/typings/ref.d.ts index 954bab971e..37d9f00e11 100644 --- a/extensions/image-preview/src/typings/ref.d.ts +++ b/extensions/image-preview/src/typings/ref.d.ts @@ -1,6 +1,6 @@ /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. + * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ /// diff --git a/extensions/json/syntaxes/JSON.tmLanguage.json b/extensions/json/syntaxes/JSON.tmLanguage.json index 910045be39..d296aac33e 100644 --- a/extensions/json/syntaxes/JSON.tmLanguage.json +++ b/extensions/json/syntaxes/JSON.tmLanguage.json @@ -5,7 +5,7 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/Microsoft/vscode-JSON.tmLanguage/commit/9bd83f1c252b375e957203f21793316203f61f70", - "name": "JSON (Javascript Next)", + "name": "JSON (JavaScript Next)", "scopeName": "source.json", "patterns": [ { @@ -210,4 +210,4 @@ ] } } -} \ No newline at end of file +} diff --git a/extensions/markdown-language-features/src/docIndex.ts b/extensions/markdown-language-features/src/docIndex.ts deleted file mode 100644 index 7276821110..0000000000 --- a/extensions/markdown-language-features/src/docIndex.ts +++ /dev/null @@ -1,50 +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 vscode from 'vscode'; -import { Disposable } from './util/dispose'; - - -export class DocumentIndex extends Disposable { - private readonly _uriMap = new Map(); - - constructor() { - super(); - - for (let doc of vscode.workspace.textDocuments) { - this._registerDoc(doc); - } - - this._register( - vscode.workspace.onDidOpenTextDocument((doc) => { - this._registerDoc(doc); - }) - ); - this._register( - vscode.workspace.onDidCloseTextDocument((doc) => { - this._unregisterDoc(doc.uri); - }) - ); - } - - getByUri(uri: vscode.Uri): vscode.TextDocument | undefined { - return this._uriMap.get(uri.toString()); - } - - private _registerDoc(doc: vscode.TextDocument) { - const uri = doc.uri.toString(); - if (this._uriMap.has(uri)) { - throw new Error(`The document ${uri} is already registered.`); - } - this._uriMap.set(uri, doc); - } - - private _unregisterDoc(uri: vscode.Uri) { - if (!this._uriMap.has(uri.toString())) { - throw new Error(`The document ${uri.toString()} is not registered.`); - } - this._uriMap.delete(uri.toString()); - } -} diff --git a/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts b/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts index 6a5ab7af9f..a406711c5d 100644 --- a/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts +++ b/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts @@ -10,7 +10,6 @@ import { Lazy, lazy } from '../util/lazy'; import MDDocumentSymbolProvider from './documentSymbolProvider'; import { SkinnyTextDocument, SkinnyTextLine } from '../tableOfContentsProvider'; import { flatten } from '../util/arrays'; -import { DocumentIndex } from '../docIndex'; export interface WorkspaceMarkdownDocumentProvider { getAllMarkdownDocuments(): Thenable>; @@ -27,7 +26,6 @@ class VSCodeWorkspaceMarkdownDocumentProvider extends Disposable implements Work private readonly _onDidDeleteMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); private _watcher: vscode.FileSystemWatcher | undefined; - private _docIndex: DocumentIndex = this._register(new DocumentIndex()); async getAllMarkdownDocuments() { const resources = await vscode.workspace.findFiles('**/*.md', '**/node_modules/**'); @@ -83,9 +81,9 @@ class VSCodeWorkspaceMarkdownDocumentProvider extends Disposable implements Work } private async getMarkdownDocument(resource: vscode.Uri): Promise { - const existingDocument = this._docIndex.getByUri(resource); - if (existingDocument) { - return existingDocument; + const matchingDocuments = vscode.workspace.textDocuments.filter((doc) => doc.uri.toString() === resource.toString()); + if (matchingDocuments.length !== 0) { + return matchingDocuments[0]; } const bytes = await vscode.workspace.fs.readFile(resource); diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json index c1720ef582..a835b501c8 100644 --- a/extensions/mssql/package.json +++ b/extensions/mssql/package.json @@ -941,7 +941,7 @@ "figures": "^2.0.0", "find-remove": "1.2.1", "fs-extra": "^3.0.1", - "kerberos": "^1.1.2", + "kerberos": "^1.1.3", "request": "^2.88.0", "request-promise": "^4.2.2", "service-downloader": "github:anthonydresser/service-downloader#0.1.6", diff --git a/extensions/mssql/yarn.lock b/extensions/mssql/yarn.lock index 852e5ff1b9..2fffe6c231 100644 --- a/extensions/mssql/yarn.lock +++ b/extensions/mssql/yarn.lock @@ -132,7 +132,7 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -bindings@^1.3.0: +bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== @@ -147,6 +147,13 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" +bl@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" + integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== + dependencies: + readable-stream "^3.0.1" + bluebird@^3.5.0: version "3.5.3" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" @@ -206,7 +213,7 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chownr@^1.0.1: +chownr@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== @@ -372,7 +379,7 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -end-of-stream@^1.0.0, end-of-stream@^1.1.0: +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== @@ -738,14 +745,14 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" -kerberos@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/kerberos/-/kerberos-1.1.2.tgz#f213dae89c57e729786664fcf99117221e18e257" - integrity sha512-N+CeRTi0f6ov85Fx+w/epVPTBy6bovoWjUCD6pvXoHWBWeqI6+ViV2psdoSc4CRtiZfJkhvYO3b47I4hJfOu7Q== +kerberos@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/kerberos/-/kerberos-1.1.3.tgz#74c5620d09289e4a53beddc2d9a93eccfebd964d" + integrity sha512-R6LdXnkSTALLnrKaByYdIc+eRNhDf+ezA83BeqHiCcKhKE9R6m1PBD579YuX9KANPAakhWHEu/u6H7ZQBc/+Wg== dependencies: - bindings "^1.3.0" - nan "^2.10.0" - prebuild-install "^5.0.0" + bindings "^1.5.0" + nan "^2.14.0" + prebuild-install "^5.3.0" lodash@^4.13.1: version "4.17.11" @@ -810,7 +817,7 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -nan@^2.10.0: +nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -864,11 +871,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -os-homedir@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -911,10 +913,10 @@ pinkie@^2.0.0: resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= -prebuild-install@^5.0.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.0.tgz#58b4d8344e03590990931ee088dd5401b03004c8" - integrity sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg== +prebuild-install@^5.3.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.2.tgz#6392e9541ac0b879ef0f22b3d65037417eb2035e" + integrity sha512-INDfXzTPnhT+WYQemqnAXlP7SvfiFMopMozSgXCZ+RDLb279gKfIuLk4o7PgEawLp3WrMgIYGBpkxpraROHsSA== dependencies: detect-libc "^1.0.3" expand-template "^2.0.3" @@ -925,11 +927,10 @@ prebuild-install@^5.0.0: node-abi "^2.7.0" noop-logger "^0.1.1" npmlog "^4.0.1" - os-homedir "^1.0.1" - pump "^2.0.1" + pump "^3.0.0" rc "^1.2.7" - simple-get "^2.7.0" - tar-fs "^1.13.0" + simple-get "^3.0.3" + tar-fs "^2.0.0" tunnel-agent "^0.6.0" which-pm-runs "^1.0.0" @@ -943,18 +944,10 @@ psl@^1.1.24, psl@^1.1.28: resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== -pump@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.3.tgz#5dfe8311c33bbf6fc18261f9f34702c47c08a954" - integrity sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pump@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== dependencies: end-of-stream "^1.1.0" once "^1.3.1" @@ -984,7 +977,7 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -"readable-stream@2 || 3": +"readable-stream@2 || 3", readable-stream@^3.0.1, readable-stream@^3.1.1: version "3.4.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== @@ -1119,10 +1112,10 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= -simple-get@^2.7.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.1.tgz#0e22e91d4575d87620620bc91308d57a77f44b5d" - integrity sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw== +simple-get@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.0.3.tgz#924528ac3f9d7718ce5e9ec1b1a69c0be4d62efa" + integrity sha512-Wvre/Jq5vgoz31Z9stYWPLn0PqRqmBDpFSdypAnHu5AvRVCYPRYGnvryNLiXu8GOBNDH82J2FRHUGMjjHUpXFw== dependencies: decompress-response "^3.3.0" once "^1.3.1" @@ -1212,17 +1205,17 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -tar-fs@^1.13.0: - version "1.16.3" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.3.tgz#966a628841da2c4010406a82167cbd5e0c72d509" - integrity sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw== +tar-fs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad" + integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA== dependencies: - chownr "^1.0.1" + chownr "^1.1.1" mkdirp "^0.5.1" - pump "^1.0.0" - tar-stream "^1.1.2" + pump "^3.0.0" + tar-stream "^2.0.0" -tar-stream@^1.1.2, tar-stream@^1.5.2: +tar-stream@^1.5.2: version "1.6.2" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== @@ -1235,6 +1228,17 @@ tar-stream@^1.1.2, tar-stream@^1.5.2: to-buffer "^1.1.1" xtend "^4.0.0" +tar-stream@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" + integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== + dependencies: + bl "^3.0.0" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + through2@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.1.tgz#39276e713c3302edf9e388dd9c812dd3b825bd5a" diff --git a/extensions/package.json b/extensions/package.json index cbdafd7231..d70cdbe0ca 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.6.3-insiders.20190909" + "typescript": "3.6.3" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index f12d3d7633..3efcc124ff 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -170,6 +170,73 @@ "settings": { "foreground": "#d7ba7d" } + }, + // Scopes that are potentially C++ only follow + { + "scope": "source.cpp entity.name", + "settings": { + "foreground": "#C8C8C8" + } + }, + { + "scope": "source.cpp keyword.control.directive", + "settings": { + "foreground": "#9B9B9B" + } + }, + { + "scope": "source.cpp entity.name.function.operator", + "settings": { + "foreground": "#B4B4B4" + } + }, + { + "scope": "source.cpp entity.name.function.preprocessor", + "settings": { + "foreground": "#C586C0" + } + }, + { + "scope": "source.cpp entity.name.label", + "settings": { + "foreground": "#C8C8C8" + } + }, + { + "scope": "source.cpp entity.name.operator.custom-literal", + "settings": { + "foreground": "#DADADA" + } + }, + { + "scope": "source.cpp entity.name.operator.custom-literal.number", + "settings": { + "foreground": "#B5CEA8" + } + }, + { + "scope": "source.cpp entity.name.operator.custom-literal.string", + "settings": { + "foreground": "#ce9178" + } + }, + { + "scope": "source.cpp variable.other.enummember", + "settings": { + "foreground": "#B8D7A3" + } + }, + { + "scope": "source.cpp variable.other.property", + "settings": { + "foreground": "#DADADA" + } + }, + { + "scope": "source.cpp variable.parameter", + "settings": { + "foreground": "#7F7F7F" + } } ] } diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index 3d30c5d17f..e6a4c92235 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -170,7 +170,55 @@ "settings": { "foreground": "#ff0000" } + }, + // Scopes that are potentially C++ only follow + { + "scope": "source.cpp entity.name", + "settings": { + "foreground": "#000000" + } + }, + { + "scope": "source.cpp keyword.control.directive", + "settings": { + "foreground": "#808080" + } + }, + { + "scope": "source.cpp entity.name.function.operator", + "settings": { + "foreground": "#008080" + } + }, + { + "scope": "source.cpp entity.name.function.preprocessor", + "settings": { + "foreground": "#AF00DB" + } + }, + { + "scope": "source.cpp entity.name.label", + "settings": { + "foreground": "#000000" + } + }, + { + "scope": "source.cpp entity.name.operator.custom-literal.string", + "settings": { + "foreground": "#0451a5" + } + }, + { + "scope": "source.cpp variable.other.enummember", + "settings": { + "foreground": "#2F4F4F" + } + }, + { + "scope": "source.cpp variable.parameter", + "settings": { + "foreground": "#808080" + } } - ] } diff --git a/extensions/yarn.lock b/extensions/yarn.lock index f6ffdca517..2595e3b441 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.6.3-insiders.20190909: - version "3.6.3-insiders.20190909" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3-insiders.20190909.tgz#65b6b2d809288311a970819849e1964ac73e1fda" - integrity sha512-Lr7ONd8Y05EhrI+zKoI5tgvO5dhuRDrK5pyOLG33DeMln8zb8w7Yc8AoIEyqvxB5Btj9F7zBmXBXJdTI3SuX0Q== +typescript@3.6.3: + version "3.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da" + integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw== diff --git a/package.json b/package.json index 926d64b723..452a31e3ee 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "ansi_up": "^3.0.0", "applicationinsights": "1.0.8", "chart.js": "^2.6.0", + "chokidar": "3.1.0", "graceful-fs": "4.1.11", "html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6", "http-proxy-agent": "^2.1.0", @@ -71,7 +72,6 @@ "sudo-prompt": "9.0.0", "underscore": "^1.8.2", "v8-inspect-profiler": "^0.0.20", - "vscode-chokidar": "2.1.7", "vscode-minimist": "^1.2.1", "vscode-proxy-agent": "0.4.0", "vscode-ripgrep": "^1.5.7", @@ -128,7 +128,7 @@ "gulp-rename": "^1.2.0", "gulp-replace": "^0.5.4", "gulp-shell": "^0.6.5", - "gulp-tsb": "4.0.2", + "gulp-tsb": "4.0.4", "gulp-tslint": "^8.1.3", "gulp-untar": "^0.0.7", "gulp-vinyl-zip": "^2.1.2", diff --git a/remote/.yarnrc b/remote/.yarnrc index b28191e6ba..1e16cde724 100644 --- a/remote/.yarnrc +++ b/remote/.yarnrc @@ -1,3 +1,3 @@ disturl "http://nodejs.org/dist" -target "10.11.0" +target "12.4.0" runtime "node" diff --git a/remote/package.json b/remote/package.json index 0d57945f25..4c37100ec6 100644 --- a/remote/package.json +++ b/remote/package.json @@ -4,6 +4,7 @@ "dependencies": { "@microsoft/applicationinsights-web": "^2.1.1", "applicationinsights": "1.0.8", + "chokidar": "3.1.0", "cookie": "^0.4.0", "graceful-fs": "4.1.11", "http-proxy-agent": "^2.1.0", @@ -16,7 +17,6 @@ "onigasm-umd": "^2.2.2", "semver-umd": "^5.5.3", "spdlog": "^0.9.0", - "vscode-chokidar": "2.1.7", "vscode-minimist": "^1.2.1", "vscode-proxy-agent": "0.4.0", "vscode-ripgrep": "^1.5.7", diff --git a/remote/yarn.lock b/remote/yarn.lock index 6a545c7829..037aedc8cf 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -79,6 +79,14 @@ agent-base@~4.2.0: dependencies: es6-promisify "^5.0.0" +anymatch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.0.tgz#e609350e50a9313b472789b2f14ef35808ee14d6" + integrity sha512-Ozz7l4ixzI7Oxj2+cw+p0tVUt27BpaJ+1+q1TCeANWxHpvyn2+Un+YamBdfKu0uh8xLodGhoa1v7595NhKDAuA== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + applicationinsights@1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.8.tgz#db6e3d983cf9f9405fe1ee5ba30ac6e1914537b5" @@ -88,58 +96,10 @@ applicationinsights@1.0.8: diagnostic-channel-publishers "0.2.1" zone.js "0.7.6" -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -atob@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -binary-extensions@^1.0.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" - integrity sha1-muuabF6IY4qtFx4Wf1kAq+JINdA= +binary-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" + integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== bindings@^1.5.0: version "1.5.0" @@ -148,80 +108,38 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" + fill-range "^7.0.1" buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== +chokidar@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.1.0.tgz#ff23d077682a90eadd209bfa76eb10ed6d359668" + integrity sha512-6vZfo+7W0EOlbSo0nhVKMz4yyssrwiPbBZ8wj1lq8/+l4ZhGZ2U4Md7PspvmijXp1a26D3B7AHEBmIB7aVtaOQ== dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + anymatch "^3.1.0" + braces "^3.0.2" + glob-parent "^5.0.0" + is-binary-path "^2.1.0" + is-glob "^4.0.1" + normalize-path "^3.0.0" + readdirp "^3.1.1" + optionalDependencies: + fsevents "^2.0.6" cookie@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - debug@3.1.0, debug@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -229,40 +147,6 @@ debug@3.1.0, debug@^3.1.0: dependencies: ms "2.0.0" -debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - diagnostic-channel-publishers@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" @@ -287,48 +171,6 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -341,27 +183,12 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" + to-regex-range "^5.0.1" fs-extra@^7.0.0: version "7.0.1" @@ -372,60 +199,28 @@ fs-extra@^7.0.0: jsonfile "^4.0.0" universalify "^0.1.0" -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= +fsevents@^2.0.6: + version "2.0.7" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a" + integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ== -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= +glob-parent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954" + integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg== dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" + is-glob "^4.0.1" graceful-fs@4.1.11, graceful-fs@^4.1.2: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= -graceful-fs@^4.1.11, graceful-fs@^4.1.6: +graceful-fs@^4.1.6: version "4.2.0" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b" integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg== -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - http-proxy-agent@2.1.0, http-proxy-agent@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" @@ -449,150 +244,34 @@ iconv-lite@0.5.0: dependencies: safer-buffer ">= 2.1.2 < 3" -inherits@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= +is-binary-path@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== dependencies: - kind-of "^3.0.2" + binary-extensions "^2.0.0" -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= - dependencies: - binary-extensions "^1.0.0" - -is-buffer@^1.0.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.4.tgz#cfc86ccd5dc5a52fa80489111c6920c457e2d98b" - integrity sha1-z8hszV3FpS+oBIkRHGkgxFfi2Ys= - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: +is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0: +is-glob@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== dependencies: is-extglob "^2.1.1" -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -isarray@1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== jschardet@1.6.0: version "1.6.0" @@ -606,37 +285,6 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" -kind-of@^3.0.2: - version "3.0.4" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.0.4.tgz#7b8ecf18a4e17f8269d73b501c9f232c96887a74" - integrity sha1-e47PGKThf4Jp1ztQHJ8jLJaIenQ= - dependencies: - is-buffer "^1.0.2" - -kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== - lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" @@ -647,50 +295,11 @@ lodash.isundefined@^3.0.1: resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48" integrity sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g= -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - -micromatch@^3.1.10: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - mkdirp@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -708,23 +317,6 @@ nan@^2.0.0, nan@^2.13.2, nan@^2.14.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - native-watchdog@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.2.0.tgz#9c710093ac6e9e60b19517cb1ef4ac9d7c997395" @@ -757,29 +349,6 @@ nsfw@1.2.5: lodash.isundefined "^3.0.1" nan "^2.0.0" -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - onigasm-umd@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/onigasm-umd/-/onigasm-umd-2.2.2.tgz#b989d762df61f899a3052ac794a50bd93fe20257" @@ -792,21 +361,6 @@ oniguruma@^7.2.0: dependencies: nan "^2.14.0" -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -817,77 +371,12 @@ picomatch@^2.0.4: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== -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" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= - -readable-stream@^2.0.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" - integrity sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ== +readdirp@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.2.tgz#fa85d2d14d4289920e4671dead96431add2ee78a" + integrity sha512-8rhl0xs2cxfVsqzreYCvs8EwBfn/DhVdqtoLmw19uI3SC5avYX9teCurlErfpPXGmYtMHReGaP2RsLnFvz/lnw== dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" - -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - integrity sha1-7wiaF40Ug7quTZPrmLT55OEdmQo= - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" + picomatch "^2.0.4" "safer-buffer@>= 2.1.2 < 3": version "2.1.2" @@ -904,51 +393,11 @@ semver@^5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - smart-buffer@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.1.tgz#07ea1ca8d4db24eb4cac86537d7d18995221ace3" integrity sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg== -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - socks-proxy-agent@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz#5936bf8b707a993079c6f37db2091821bffa6473" @@ -965,27 +414,6 @@ socks@~2.2.0: ip "^1.1.5" smart-buffer "^4.0.1" -source-map-resolve@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" - integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== - dependencies: - atob "^2.1.1" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - spdlog@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.9.0.tgz#c85dd9d0b9cd385f6f3f5b92dc9d2e1691092b5c" @@ -995,135 +423,23 @@ spdlog@^0.9.0: mkdirp "^0.5.1" nan "^2.14.0" -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: - extend-shallow "^3.0.0" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" - integrity sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ== - dependencies: - safe-buffer "~5.1.0" - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" + is-number "^7.0.0" tslib@^1.9.3: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" - integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -vscode-anymatch@3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/vscode-anymatch/-/vscode-anymatch-3.0.3.tgz#5a79101e6df7e659a1f070367bc42f190eb4ae76" - integrity sha512-qQgfbzJJ5nNShh4jjC3BBekY4d8emcxHFgnqcXwsB/PUKvJPCg7AZYXM7hqS7EDnKrX9tsIFwFMihZ7yut92Qg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -vscode-chokidar@2.1.7: - version "2.1.7" - resolved "https://registry.yarnpkg.com/vscode-chokidar/-/vscode-chokidar-2.1.7.tgz#c5b31eb87402f4779bb4170915245bdcb6f7854b" - integrity sha512-uSNEQetPjAlgIAHmcF9E6M+KCw0f842rsEnJ64aamUAV6TO7gkXNCvLSzb4MuLsPU7ZQyCa++DrLQFjvciK5dg== - dependencies: - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - vscode-anymatch "3.0.3" - optionalDependencies: - vscode-fsevents "1.2.12" - -vscode-fsevents@1.2.12: - version "1.2.12" - resolved "https://registry.yarnpkg.com/vscode-fsevents/-/vscode-fsevents-1.2.12.tgz#01a71a01f90ee95ca822c34427aba437a17c03a7" - integrity sha512-bH/jRdDpSesGpqiVLjp6gHLSKUOh7oNvppzZ17JIrdbRYCcDmV7dIWR5gQc27DFy0RD9JDT+t+ixMid94MkM1A== - dependencies: - nan "^2.14.0" - vscode-minimist@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.1.tgz#e63d3f4a9bf3680dcb8f9304eed612323fd6926a" diff --git a/resources/linux/code-url-handler.desktop b/resources/linux/code-url-handler.desktop index b8e921920b..caf34337a3 100644 --- a/resources/linux/code-url-handler.desktop +++ b/resources/linux/code-url-handler.desktop @@ -2,7 +2,7 @@ Name=@@NAME_LONG@@ - URL Handler Comment=Azure Data Studio GenericName=Text Editor -Exec=@@EXEC@@ --open-url %U +Exec=@@EXEC@@ --no-sandbox --open-url %U Icon=@@ICON@@ Type=Application NoDisplay=true diff --git a/resources/linux/code.desktop b/resources/linux/code.desktop index 3940900ed6..53ad8dc607 100644 --- a/resources/linux/code.desktop +++ b/resources/linux/code.desktop @@ -2,7 +2,7 @@ Name=@@NAME_LONG@@ Comment=Data Management Tool that enables you to work with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux. GenericName=Text Editor -Exec=@@EXEC@@ --unity-launch %F +Exec=@@EXEC@@ --no-sandbox --unity-launch %F Icon=@@ICON@@ Type=Application StartupNotify=false @@ -14,5 +14,5 @@ Keywords=azuredatastudio; [Desktop Action new-empty-window] Name=New Empty Window -Exec=@@EXEC@@ --new-window %F +Exec=@@EXEC@@ --no-sandbox --new-window %F Icon=@@ICON@@ diff --git a/resources/win32/bin/code.sh b/resources/win32/bin/code.sh index 54f9429b7d..a915930e70 100644 --- a/resources/win32/bin/code.sh +++ b/resources/win32/bin/code.sh @@ -27,15 +27,15 @@ if grep -qi Microsoft /proc/version; then WSL_EXT_ID="ms-vscode-remote.remote-wsl" if [ $WSL_BUILD -ge 41955 -a $WSL_BUILD -lt 41959 ]; then - # WSL2 in workaround for https://github.com/microsoft/WSL/issues/4337 + # WSL2 workaround for https://github.com/microsoft/WSL/issues/4337 CWD="$(pwd)" cd "$VSCODE_PATH" - cmd.exe /C ".\\bin\\$APP_NAME.cmd --locate-extension $WSL_EXT_ID >remote-wsl-loc.txt" - WSL_EXT_WLOC="$(cat ./remote-wsl-loc.txt)" - rm remote-wsl-loc.txt + cmd.exe /C ".\\bin\\$APP_NAME.cmd --locate-extension $WSL_EXT_ID >%TEMP%\\remote-wsl-loc.txt" + WSL_EXT_WLOC=$(cmd.exe /C type %TEMP%\\remote-wsl-loc.txt) cd "$CWD" else - WSL_EXT_WLOC=$(ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --locate-extension $WSL_EXT_ID) + ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --locate-extension $WSL_EXT_ID >/tmp/remote-wsl-loc.txt + WSL_EXT_WLOC=$(cat /tmp/remote-wsl-loc.txt) fi if [ -n "$WSL_EXT_WLOC" ]; then # replace \r\n with \n in WSL_EXT_WLOC diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 9dd5449c32..42a14cd2a3 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -8,6 +8,7 @@ if [[ "$OSTYPE" == "darwin"* ]]; then else ROOT=$(dirname $(dirname $(readlink -f $0))) VSCODEUSERDATADIR=`mktemp -d 2>/dev/null` + LINUX_NO_SANDBOX="--no-sandbox" # Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox. fi cd $ROOT @@ -36,14 +37,14 @@ fi ./scripts/test.sh --runGlob **/*.integrationTest.js "$@" # Tests in the extension host -# "$INTEGRATION_TEST_ELECTRON_PATH" $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR -# "$INTEGRATION_TEST_ELECTRON_PATH" $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR +# "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/workspace-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR "$INTEGRATION_TEST_ELECTRON_PATH" $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR -"$INTEGRATION_TEST_ELECTRON_PATH" $ROOT/extensions/markdown-language-features/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR -"$INTEGRATION_TEST_ELECTRON_PATH" $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/markdown-language-features/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR mkdir -p $ROOT/extensions/emmet/test-fixtures -"$INTEGRATION_TEST_ELECTRON_PATH" $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR +"$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR rm -rf $ROOT/extensions/emmet/test-fixtures # Remote Integration Tests diff --git a/scripts/test.sh b/scripts/test.sh index 9b6a3b86b8..ac09157151 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -34,5 +34,5 @@ else cd $ROOT ; \ ELECTRON_ENABLE_LOGGING=1 \ "$CODE" \ - test/electron/index.js "$@" + test/electron/index.js --no-sandbox "$@" # Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox. fi diff --git a/src/bootstrap.js b/src/bootstrap.js index 8d95ffee95..8a6a02ab5a 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -21,6 +21,7 @@ process.on('SIGPIPE', () => { //#endregion //#region Add support for redirecting the loading of node modules + exports.injectNodeModuleLookupPath = function (injectPath) { if (!injectPath) { throw new Error('Missing injectPath'); @@ -36,10 +37,8 @@ exports.injectNodeModuleLookupPath = function (injectPath) { const originalResolveLookupPaths = Module._resolveLookupPaths; // @ts-ignore - Module._resolveLookupPaths = function (moduleName, parent, newReturn) { - const result = originalResolveLookupPaths(moduleName, parent, newReturn); - - const paths = newReturn ? result : result[1]; + Module._resolveLookupPaths = function (moduleName, parent) { + const paths = originalResolveLookupPaths(moduleName, parent); for (let i = 0, len = paths.length; i < len; i++) { if (paths[i] === nodeModulesPath) { paths.splice(i, 0, injectPath); @@ -47,7 +46,7 @@ exports.injectNodeModuleLookupPath = function (injectPath) { } } - return result; + return paths; }; }; //#endregion @@ -71,11 +70,10 @@ exports.enableASARSupport = function (nodeModulesPath) { // @ts-ignore const originalResolveLookupPaths = Module._resolveLookupPaths; - // @ts-ignore - Module._resolveLookupPaths = function (request, parent, newReturn) { - const result = originalResolveLookupPaths(request, parent, newReturn); - const paths = newReturn ? result : result[1]; + // @ts-ignore + Module._resolveLookupPaths = function (request, parent) { + const paths = originalResolveLookupPaths(request, parent); for (let i = 0, len = paths.length; i < len; i++) { if (paths[i] === NODE_MODULES_PATH) { paths.splice(i, 0, NODE_MODULES_ASAR_PATH); @@ -83,7 +81,7 @@ exports.enableASARSupport = function (nodeModulesPath) { } } - return result; + return paths; }; }; //#endregion diff --git a/src/main.js b/src/main.js index d50913a265..1059583cd4 100644 --- a/src/main.js +++ b/src/main.js @@ -17,7 +17,7 @@ const paths = require('./paths'); // @ts-ignore const product = require('../product.json'); // @ts-ignore -const app = require('electron').app; +const { app, protocol } = require('electron'); // Enable portable support const portable = bootstrap.configurePortable(); @@ -33,6 +33,11 @@ app.setPath('userData', userDataPath); // Update cwd based on environment and platform setCurrentWorkingDirectory(); +// Register custom schemes with privileges +protocol.registerSchemesAsPrivileged([ + { scheme: 'vscode-resource', privileges: { secure: true, supportFetchAPI: true, corsEnabled: true } } +]); + // Global app listeners registerListeners(); @@ -102,7 +107,7 @@ function onReady() { }); }; - // We recevied a valid nlsConfig from a user defined locale + // We received a valid nlsConfig from a user defined locale if (nlsConfig) { startup(nlsConfig); } diff --git a/src/sql/base/browser/ui/table/media/slickColorTheme.css b/src/sql/base/browser/ui/table/media/slickColorTheme.css index 1b16834fcd..c2c6285e77 100644 --- a/src/sql/base/browser/ui/table/media/slickColorTheme.css +++ b/src/sql/base/browser/ui/table/media/slickColorTheme.css @@ -87,42 +87,42 @@ } /* icons */ -.vs .icon.extendFullScreen { +.vs .codicon.extendFullScreen { /* ExtendToFullScreen_16x_vscode */ background-image: url("extendFullScreen.svg"); } -.vs .icon.exitFullScreen { +.vs .codicon.exitFullScreen { /* ExitFullScreen_16x_vscode */ background-image: url("exitFullScreen.svg"); } -.vs .icon.saveJson { +.vs .codicon.saveJson { /* ResultToJSON_16x_vscode */ background-image: url("saveJson.svg"); } -.vs .icon.saveCsv { +.vs .codicon.saveCsv { /* ResultToCSV_16x_vscode */ background-image: url("saveCsv.svg"); } -.vs .icon.saveExcel { +.vs .codicon.saveExcel { /* ResultToXlsx_16x_vscode */ background-image: url("saveExcel.svg"); } -.vs .icon.saveXml { +.vs .codicon.saveXml { /* ResultToXML_16x_vscode */ background-image: url("saveXml.svg"); } -.vs .icon.viewChart { +.vs .codicon.viewChart { /* ResultToXlsx_16x_vscode */ background-image: url("viewChart.svg"); } -.vs .icon.viewVisualizer { +.vs .codicon.viewVisualizer { background-image: url("viewVisualizer.svg"); } @@ -222,54 +222,54 @@ } /* icons */ -.vs-dark .icon.extendFullScreen, -.hc-black .icon.extendFullScreen { +.vs-dark .codicon.extendFullScreen, +.hc-black .codicon.extendFullScreen { /* ExtendToFullScreen_16x_vscode_inverse.svg */ background-image: url("extendFullScreen_inverse.svg"); } -.vs-dark .icon.exitFullScreen, -.hc-black .icon.exitFullScreen { +.vs-dark .codicon.exitFullScreen, +.hc-black .codicon.exitFullScreen { /* ExitFullScreen_16x_vscode_inverse.svg */ background-image: url("exitFullScreen_inverse.svg"); } -.vs-dark .icon.saveJson, -.hc-black .icon.saveJson { +.vs-dark .codicon.saveJson, +.hc-black .codicon.saveJson { /* ResultToJSON_16x_vscode_inverse.svg */ background-image: url("saveJson_inverse.svg"); } -.vs-dark .icon.saveCsv, -.hc-black .icon.saveCsv { +.vs-dark .codicon.saveCsv, +.hc-black .codicon.saveCsv { /* ResultToCSV_16x_vscode_inverse.svg */ background-image: url("saveCsv_inverse.svg"); } -.vs-dark .icon.saveExcel, -.hc-black .icon.saveExcel { +.vs-dark .codicon.saveExcel, +.hc-black .codicon.saveExcel { /* ResultToXlsx_16x_vscode_inverse.svg */ background-image: url("saveExcel_inverse.svg"); } -.vs-dark .icon.saveXml, -.hc-black .icon.saveXml { +.vs-dark .codicon.saveXml, +.hc-black .codicon.saveXml { /* ResultToXml_16x_vscode_inverse.svg */ background-image: url("saveXml_inverse.svg"); } -.vs-dark .icon.viewChart, -.hc-black .icon.viewChart { +.vs-dark .codicon.viewChart, +.hc-black .codicon.viewChart { /* ResultToXlsx_16x_vscode */ background-image: url("viewChart_inverse.svg"); } -.vs-dark .icon.viewVisualizer, -.hc-black .icon.viewVisualizer { +.vs-dark .codicon.viewVisualizer, +.hc-black .codicon.viewVisualizer { background-image: url("viewVisualizer_inverse.svg"); } -.grid-panel .action-label.icon { +.grid-panel .action-label.codicon { height: 16px; min-width: 28px; background-size: 16px; diff --git a/src/sql/base/browser/ui/taskbar/media/icons.css b/src/sql/base/browser/ui/taskbar/media/icons.css index c9a41d0a31..deb3395318 100644 --- a/src/sql/base/browser/ui/taskbar/media/icons.css +++ b/src/sql/base/browser/ui/taskbar/media/icons.css @@ -12,109 +12,109 @@ background-image: url('ellipsis-inverse.svg'); } -.vs .icon.start, -.vs-dark .icon.start, -.hc-black .icon.start { +.vs .codicon.start, +.vs-dark .codicon.start, +.hc-black .codicon.start { background-image: url('start.svg'); } -.vs .icon.add, -.vs-dark .icon.add, -.hc-black .icon.add { +.vs .codicon.add, +.vs-dark .codicon.add, +.hc-black .codicon.add { background-image: url('add.svg'); } -.vs .icon.stop, -.vs-dark .icon.stop, -.hc-black .icon.stop { +.vs .codicon.stop, +.vs-dark .codicon.stop, +.hc-black .codicon.stop { background-image: url('stop.svg'); } -.vs .icon.disconnect { +.vs .codicon.disconnect { background-image: url('disconnect.svg'); } -.vs-dark .icon.disconnect, -.hc-black .icon.disconnect { +.vs-dark .codicon.disconnect, +.hc-black .codicon.disconnect { background-image: url('disconnect_inverse.svg'); background-repeat: no-repeat; } -.vs .icon.connect { +.vs .codicon.connect { background-image: url('connect.svg'); } -.vs-dark .icon.connect, -.hc-black .icon.connect { +.vs-dark .codicon.connect, +.hc-black .codicon.connect { background-image: url('connect_inverse.svg'); background-repeat: no-repeat; } -.vs .icon.changeConnection { +.vs .codicon.changeConnection { background-image: url('change_connection.svg'); } -.vs-dark .icon.changeConnection, -.hc-black .icon.changeConnection { +.vs-dark .codicon.changeConnection, +.hc-black .codicon.changeConnection { background-image: url('change_connection_inverse.svg'); background-repeat: no-repeat; } -.vs .icon.estimatedQueryPlan, -.vs .icon.actualQueryPlan { +.vs .codicon.estimatedQueryPlan, +.vs .codicon.actualQueryPlan { background-image: url('query-plan.svg'); } -.vs-dark .icon.estimatedQueryPlan, -.hc-black .icon.estimatedQueryPlan, -.vs-dark .icon.actualQueryPlan, -.hc-black .icon.actualQueryPlan { +.vs-dark .codicon.estimatedQueryPlan, +.hc-black .codicon.estimatedQueryPlan, +.vs-dark .codicon.actualQueryPlan, +.hc-black .codicon.actualQueryPlan { background-image: url('query-plan-inverse.svg'); } -.vs .icon.createInsight { +.vs .codicon.createInsight { background-image: url('create_insight.svg'); } -.vs-dark .icon.createInsight, -.hc-black .icon.createInsight { +.vs-dark .codicon.createInsight, +.hc-black .codicon.createInsight { background-image: url('create_insight_inverse.svg'); } -.vs .icon.copyImage { +.vs .codicon.copyImage { background-image: url('copy_image.svg'); } -.vs-dark .icon.copyImage, -.hc-black .icon.copyImage { +.vs-dark .codicon.copyImage, +.hc-black .codicon.copyImage { background-image: url('copy_image_inverse.svg'); } -.vs .icon.saveAsImage { +.vs .codicon.saveAsImage { background-image: url('save_as_image.svg'); } -.vs-dark .icon.saveAsImage, -.hc-black .icon.saveAsImage { +.vs-dark .codicon.saveAsImage, +.hc-black .codicon.saveAsImage { background-image: url('save_as_image_inverse.svg'); } -.vs .icon.enablesqlcmd { +.vs .codicon.enablesqlcmd { background-image: url('enable_sqlcmd.svg'); } -.vs-dark .icon.enablesqlcmd, -.hc-black .icon.enablesqlcmd { +.vs-dark .codicon.enablesqlcmd, +.hc-black .codicon.enablesqlcmd { background-image: url('enable_sqlcmd_inverse.svg'); background-repeat: no-repeat; } -.vs .icon.disablesqlcmd { +.vs .codicon.disablesqlcmd { background-image: url('disable_sqlcmd.svg'); } -.vs-dark .icon.disablesqlcmd, -.hc-black .icon.disablesqlcmd { +.vs-dark .codicon.disablesqlcmd, +.hc-black .codicon.disablesqlcmd { background-image: url('disable_sqlcmd_inverse.svg'); background-repeat: no-repeat; } diff --git a/src/sql/base/browser/ui/taskbar/media/taskbar.css b/src/sql/base/browser/ui/taskbar/media/taskbar.css index 79f96dd60b..701d37cc0b 100644 --- a/src/sql/base/browser/ui/taskbar/media/taskbar.css +++ b/src/sql/base/browser/ui/taskbar/media/taskbar.css @@ -83,6 +83,6 @@ /* Taskbar Icons */ -.carbon-taskbar .icon { +.carbon-taskbar .codicon { background-size: 11px; } diff --git a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts index 5933b8ef3c..ea8e369971 100644 --- a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts +++ b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts @@ -112,14 +112,14 @@ export class QueryTextEditor extends BaseTextEditor { public setWidth(width: number) { if (this._dimension) { - this._dimension.width = width; + this._dimension = new DOM.Dimension(width, this._dimension.height); this.layout(); } } public setHeight(height: number) { if (this._dimension) { - this._dimension.height = height; + this._dimension = new DOM.Dimension(this._dimension.width, height); this.layout(this._dimension); } } diff --git a/src/sql/workbench/common/enablePreviewFeatures.ts b/src/sql/workbench/common/enablePreviewFeatures.ts index a891729585..0366828678 100644 --- a/src/sql/workbench/common/enablePreviewFeatures.ts +++ b/src/sql/workbench/common/enablePreviewFeatures.ts @@ -5,13 +5,12 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class EnablePreviewFeatures implements IWorkbenchContribution { @@ -19,11 +18,9 @@ export class EnablePreviewFeatures implements IWorkbenchContribution { constructor( @IStorageService storageService: IStorageService, - @IOpenerService openerService: IOpenerService, @INotificationService notificationService: INotificationService, @IWindowService windowService: IWindowService, - @IWindowsService windowsService: IWindowsService, - @ITelemetryService telemetryService: ITelemetryService, + @IHostService hostService: IHostService, @IConfigurationService configurationService: IConfigurationService ) { let previewFeaturesEnabled = configurationService.getValue('workbench')['enablePreviewFeatures']; @@ -32,7 +29,7 @@ export class EnablePreviewFeatures implements IWorkbenchContribution { } Promise.all([ windowService.isFocused(), - windowsService.getWindowCount() + hostService.windowCount ]).then(([focused, count]) => { if (!focused && count > 1) { return null; diff --git a/src/sql/workbench/common/workspaceActions.ts b/src/sql/workbench/common/workspaceActions.ts index 1731e0bfdb..d8f1deae3c 100644 --- a/src/sql/workbench/common/workspaceActions.ts +++ b/src/sql/workbench/common/workspaceActions.ts @@ -5,16 +5,17 @@ import { Action } from 'vs/base/common/actions'; import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { URI } from 'vs/base/common/uri'; +//tslint:disable-next-line:layering +import { ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; export class ShowFileInFolderAction extends Action { - constructor(private path: string, label: string, private windowsService: IWindowsService) { + constructor(private path: string, label: string, private windowsService: ElectronMainService) { super('showItemInFolder.action.id', label); } run(): Promise { - return this.windowsService.showItemInFolder(URI.file(this.path)); + return this.windowsService.showItemInFolder(this.path); } } diff --git a/src/sql/workbench/contrib/welcome/page/browser/az_data_welcome_page.ts b/src/sql/workbench/contrib/welcome/page/browser/az_data_welcome_page.ts index 3b218b1bdf..3f86fa7a0e 100644 --- a/src/sql/workbench/contrib/welcome/page/browser/az_data_welcome_page.ts +++ b/src/sql/workbench/contrib/welcome/page/browser/az_data_welcome_page.ts @@ -6,9 +6,6 @@ import { escape } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; -export function used() { -} - let productQuality: string; export function setProductQuality(quality: string): void { diff --git a/src/sql/workbench/parts/query/browser/queryEditor.ts b/src/sql/workbench/parts/query/browser/queryEditor.ts index 73a774275a..47acb69f40 100644 --- a/src/sql/workbench/parts/query/browser/queryEditor.ts +++ b/src/sql/workbench/parts/query/browser/queryEditor.ts @@ -22,7 +22,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; import { Event } from 'vs/base/common/event'; -import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { ISelectionData } from 'azdata'; import { Action, IActionViewItem } from 'vs/base/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; diff --git a/src/sql/workbench/parts/query/common/resultSerializer.ts b/src/sql/workbench/parts/query/common/resultSerializer.ts index cb2ae9db92..017e5e2dcd 100644 --- a/src/sql/workbench/parts/query/common/resultSerializer.ts +++ b/src/sql/workbench/parts/query/common/resultSerializer.ts @@ -4,14 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as ConnectionConstants from 'sql/platform/connection/common/constants'; -import * as Constants from 'sql/workbench/parts/query/common/constants'; import * as LocalizedConstants from 'sql/workbench/parts/query/common/localizedConstants'; import { SaveResultsRequestParams } from 'azdata'; import { IQueryManagementService } from 'sql/platform/query/common/queryManagement'; import { ISaveRequest, SaveFormat } from 'sql/workbench/parts/grid/common/interfaces'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IWindowsService, FileFilter } from 'vs/platform/windows/common/windows'; +import { FileFilter } from 'vs/platform/windows/common/windows'; import { Registry } from 'vs/platform/registry/common/platform'; import { URI } from 'vs/base/common/uri'; import * as path from 'vs/base/common/path'; @@ -26,6 +25,7 @@ import { getRootPath, resolveCurrentDirectory, resolveFilePath } from 'sql/platf import { IOutputService, IOutputChannelRegistry, IOutputChannel, Extensions as OutputExtensions } from 'vs/workbench/contrib/output/common/output'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; let prevSavePath: string; @@ -60,9 +60,9 @@ export class ResultSerializer { @IConfigurationService private _configurationService: IConfigurationService, @IEditorService private _editorService: IEditorService, @IWorkspaceContextService private _contextService: IWorkspaceContextService, - @IWindowsService private _windowsService: IWindowsService, @IFileDialogService private readonly fileDialogService: IFileDialogService, - @INotificationService private _notificationService: INotificationService + @INotificationService private _notificationService: INotificationService, + @IInstantiationService private readonly _instantiationService: IInstantiationService ) { } /** @@ -313,14 +313,14 @@ export class ResultSerializer { [{ label: nls.localize('openLocation', "Open file location"), run: () => { - let action = new ShowFileInFolderAction(savedFilePath, label || path.sep, this._windowsService); + let action = this._instantiationService.createInstance(ShowFileInFolderAction, savedFilePath, label || path.sep); action.run(); action.dispose(); } }, { label: nls.localize('openFile', "Open file"), run: () => { - let action = new OpenFileInFolderAction(savedFilePath, label || path.sep, this._windowsService); + let action = this._instantiationService.createInstance(OpenFileInFolderAction, savedFilePath, label || path.sep); action.run(); action.dispose(); } diff --git a/src/sql/workbench/services/connection/browser/media/connectionDialog.css b/src/sql/workbench/services/connection/browser/media/connectionDialog.css index 8baa9a1a11..ab4cd29275 100644 --- a/src/sql/workbench/services/connection/browser/media/connectionDialog.css +++ b/src/sql/workbench/services/connection/browser/media/connectionDialog.css @@ -106,9 +106,9 @@ height: 100%; } -.vs-dark .connection-dialog .connection-history-actions .action-label.icon, -.hc-black .connection-dialog .connection-history-actions .action-label.icon, -.connection-dialog .connection-history-actions .action-label.icon { +.vs-dark .connection-dialog .connection-history-actions .action-label.codicon, +.hc-black .connection-dialog .connection-history-actions .action-label.codicon, +.connection-dialog .connection-history-actions .action-label.codicon { display: block; height: 20px; line-height: 20px; diff --git a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts index a750245a94..36e12e5785 100644 --- a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts +++ b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts @@ -25,6 +25,8 @@ import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; class TestEnvironmentService implements IWorkbenchEnvironmentService { + settingsSyncPreviewResource: URI; + webviewExternalEndpoint: string; logFile: URI; options?: IWorkbenchConstructionOptions; galleryMachineIdResource?: URI; diff --git a/src/sql/workbench/update/electron-browser/releaseNotes.ts b/src/sql/workbench/update/electron-browser/releaseNotes.ts index c3cc330d88..9bf4fc4413 100644 --- a/src/sql/workbench/update/electron-browser/releaseNotes.ts +++ b/src/sql/workbench/update/electron-browser/releaseNotes.ts @@ -5,8 +5,7 @@ import nls = require('vs/nls'); import { Action } from 'vs/base/common/actions'; -import pkg from 'vs/platform/product/node/package'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -37,7 +36,7 @@ export class ShowCurrentReleaseNotesAction extends AbstractShowReleaseNotesActio label = ShowCurrentReleaseNotesAction.LABEL, @IInstantiationService instantiationService: IInstantiationService ) { - super(id, label, pkg.version, instantiationService); + super(id, label, product.version, instantiationService); } } diff --git a/src/typings/chokidar.d.ts b/src/typings/chokidar.d.ts index 1584d20fbb..87d93fd5ab 100644 --- a/src/typings/chokidar.d.ts +++ b/src/typings/chokidar.d.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -declare module 'vscode-chokidar' { +declare module 'chokidar' { // TypeScript Version: 3.0 @@ -197,4 +197,4 @@ declare module 'vscode-chokidar' { paths: string | string[], options?: WatchOptions ): FSWatcher; -} \ No newline at end of file +} diff --git a/src/typings/electron.d.ts b/src/typings/electron.d.ts index 6f44785756..3e55cdc4bf 100644 --- a/src/typings/electron.d.ts +++ b/src/typings/electron.d.ts @@ -1,4 +1,4 @@ -// Type definitions for Electron 4.2.10 +// Type definitions for Electron 6.0.9 // Project: http://electronjs.org/ // Definitions by: The Electron Team // Definitions: https://github.com/electron/electron-typescript-definitions @@ -8,6 +8,7 @@ type GlobalEvent = Event; declare namespace Electron { + // TODO: Replace this declaration with NodeJS.EventEmitter class EventEmitter { addListener(event: string, listener: Function): this; on(event: string, listener: Function): this; @@ -21,28 +22,17 @@ declare namespace Electron { listenerCount(type: string): number; prependListener(event: string, listener: Function): this; prependOnceListener(event: string, listener: Function): this; - eventNames(): string[]; + eventNames(): Array<(string | symbol)>; } class Accelerator extends String { } - interface Event extends GlobalEvent { - preventDefault: () => void; - sender: WebContents; - returnValue: any; - ctrlKey?: boolean; - metaKey?: boolean; - shiftKey?: boolean; - altKey?: boolean; - } - interface CommonInterface { clipboard: Clipboard; crashReporter: CrashReporter; nativeImage: typeof NativeImage; - screen: Screen; shell: Shell; } @@ -69,6 +59,7 @@ declare namespace Electron { powerMonitor: PowerMonitor; powerSaveBlocker: PowerSaveBlocker; protocol: Protocol; + screen: Screen; session: typeof Session; systemPreferences: SystemPreferences; TouchBar: typeof TouchBar; @@ -203,9 +194,9 @@ declare namespace Electron { userInfo: any) => void): this; /** * Emitted before the application starts closing its windows. Calling - * event.preventDefault() will prevent the default behaviour, which is terminating + * event.preventDefault() will prevent the default behavior, which is terminating * the application. Note: If application quit was initiated by - * autoUpdater.quitAndInstall() then before-quit is emitted after emitting close + * autoUpdater.quitAndInstall(), then before-quit is emitted after emitting close * event on all windows and closing them. Note: On Windows, this event will not be * emitted if the app is closed due to a shutdown/restart of the system or a user * logout. @@ -372,6 +363,18 @@ declare namespace Electron { * A string with the error's localized description. */ error: string) => void): this; + /** + * Emitted when desktopCapturer.getSources() is called in the renderer process of + * webContents. Calling event.preventDefault() will make it return empty sources. + */ + on(event: 'desktop-capturer-get-sources', listener: (event: Event, + webContents: WebContents) => void): this; + once(event: 'desktop-capturer-get-sources', listener: (event: Event, + webContents: WebContents) => void): this; + addListener(event: 'desktop-capturer-get-sources', listener: (event: Event, + webContents: WebContents) => void): this; + removeListener(event: 'desktop-capturer-get-sources', listener: (event: Event, + webContents: WebContents) => void): this; /** * Emitted when the gpu process crashes or is killed. */ @@ -385,7 +388,7 @@ declare namespace Electron { killed: boolean) => void): this; /** * Emitted when webContents wants to do basic auth. The default behavior is to - * cancel all authentications, to override this you should prevent the default + * cancel all authentications. To override this you should prevent the default * behavior with event.preventDefault() and call callback(username, password) with * the credentials. */ @@ -566,13 +569,30 @@ declare namespace Electron { removeListener(event: 'remote-require', listener: (event: Event, webContents: WebContents, moduleName: string) => void): this; + /** + * Emitted when the renderer process of webContents crashes or is killed. + */ + on(event: 'renderer-process-crashed', listener: (event: Event, + webContents: WebContents, + killed: boolean) => void): this; + once(event: 'renderer-process-crashed', listener: (event: Event, + webContents: WebContents, + killed: boolean) => void): this; + addListener(event: 'renderer-process-crashed', listener: (event: Event, + webContents: WebContents, + killed: boolean) => void): this; + removeListener(event: 'renderer-process-crashed', listener: (event: Event, + webContents: WebContents, + killed: boolean) => void): this; /** * This event will be emitted inside the primary instance of your application when - * a second instance has been executed. argv is an Array of the second instance's - * command line arguments, and workingDirectory is its current working directory. - * Usually applications respond to this by making their primary window focused and - * non-minimized. This event is guaranteed to be emitted after the ready event of - * app gets emitted. + * a second instance has been executed and calls app.requestSingleInstanceLock(). + * argv is an Array of the second instance's command line arguments, and + * workingDirectory is its current working directory. Usually applications respond + * to this by making their primary window focused and non-minimized. This event is + * guaranteed to be emitted after the ready event of app gets emitted. Note: Extra + * command line arguments might be added by Chromium, such as + * --original-process-start-time. */ on(event: 'second-instance', listener: (event: Event, /** @@ -647,8 +667,8 @@ declare namespace Electron { * Emitted when Handoff is about to be resumed on another device. If you need to * update the state to be transferred, you should call event.preventDefault() * immediately, construct a new userInfo dictionary and call - * app.updateCurrentActiviy() in a timely manner. Otherwise the operation will fail - * and continue-activity-error will be called. + * app.updateCurrentActiviy() in a timely manner. Otherwise, the operation will + * fail and continue-activity-error will be called. */ on(event: 'update-activity-state', listener: (event: Event, /** @@ -760,8 +780,8 @@ declare namespace Electron { removeListener(event: 'window-all-closed', listener: Function): this; /** * Adds path to the recent documents list. This list is managed by the OS. On - * Windows you can visit the list from the task bar, and on macOS you can visit it - * from dock menu. + * Windows, you can visit the list from the task bar, and on macOS, you can visit + * it from dock menu. */ addRecentDocument(path: string): void; /** @@ -779,11 +799,6 @@ declare namespace Electron { * before app is ready. */ disableHardwareAcceleration(): void; - /** - * Enables mixed sandbox mode on the app. This method can only be called before app - * is ready. - */ - enableMixedSandbox(): void; /** * Enables full sandbox mode on the app. This method can only be called before app * is ready. @@ -791,8 +806,8 @@ declare namespace Electron { enableSandbox(): void; /** * Exits immediately with exitCode. exitCode defaults to 0. All windows will be - * closed immediately without asking user and the before-quit and will-quit events - * will not be emitted. + * closed immediately without asking the user, and the before-quit and will-quit + * events will not be emitted. */ exit(exitCode?: number): void; /** @@ -808,12 +823,19 @@ declare namespace Electron { * Fetches a path's associated icon. On Windows, there a 2 kinds of icons: On Linux * and macOS, icons depend on the application associated with file mime type. */ - getFileIcon(path: string, callback: (error: Error, icon: NativeImage) => void): void; + getFileIcon(path: string, options?: FileIconOptions): Promise; /** - * Fetches a path's associated icon. On Windows, there a 2 kinds of icons: On Linux - * and macOS, icons depend on the application associated with file mime type. + * Fetches a path's associated icon. On Windows, there are 2 kinds of icons: On + * Linux and macOS, icons depend on the application associated with file mime type. + * Deprecated Soon */ getFileIcon(path: string, options: FileIconOptions, callback: (error: Error, icon: NativeImage) => void): void; + /** + * Fetches a path's associated icon. On Windows, there are 2 kinds of icons: On + * Linux and macOS, icons depend on the application associated with file mime type. + * Deprecated Soon + */ + getFileIcon(path: string, callback: (error: Error, icon: NativeImage) => void): void; getGPUFeatureStatus(): GPUFeatureStatus; /** * For infoType equal to complete: Promise is fulfilled with Object containing all @@ -828,12 +850,16 @@ declare namespace Electron { /** * To set the locale, you'll want to use a command line switch at app startup, * which may be found here. Note: When distributing your packaged app, you have to - * also ship the locales folder. Note: On Windows you have to call it after the + * also ship the locales folder. Note: On Windows, you have to call it after the * ready events gets emitted. */ getLocale(): string; /** - * If you provided path and args options to app.setLoginItemSettings then you need + * Note: When unable to detect locale country code, it returns empty string. + */ + getLocaleCountryCode(): string; + /** + * If you provided path and args options to app.setLoginItemSettings, then you need * to pass the same arguments here for openAtLogin to be set correctly. */ getLoginItemSettings(options?: LoginItemSettingsOptions): LoginItemSettings; @@ -870,6 +896,9 @@ declare namespace Electron { * Invalidates the current Handoff user activity. */ invalidateCurrentActivity(type: string): void; + /** + * Deprecated Soon + */ isAccessibilitySupportEnabled(): boolean; /** * This method checks if the current executable is the default handler for a @@ -881,15 +910,16 @@ declare namespace Electron { * the Windows Registry and LSCopyDefaultHandlerForURLScheme internally. */ isDefaultProtocolClient(protocol: string, path?: string, args?: string[]): boolean; + isEmojiPanelSupported(): boolean; isInApplicationsFolder(): boolean; isReady(): boolean; isUnityRunning(): boolean; /** - * No confirmation dialog will be presented by default, if you wish to allow the - * user to confirm the operation you may do so using the dialog API. NOTE: This + * No confirmation dialog will be presented by default. If you wish to allow the + * user to confirm the operation, you may do so using the dialog API. NOTE: This * method throws errors if anything other than the user causes the move to fail. - * For instance if the user cancels the authorization dialog this method returns - * false. If we fail to perform the copy then this method will throw an error. The + * For instance if the user cancels the authorization dialog, this method returns + * false. If we fail to perform the copy, then this method will throw an error. The * message in the error should be informative and tell you exactly what went wrong */ moveToApplicationsFolder(): boolean; @@ -903,14 +933,14 @@ declare namespace Electron { */ quit(): void; /** - * Relaunches the app when current instance exits. By default the new instance will - * use the same working directory and command line arguments with current instance. - * When args is specified, the args will be passed as command line arguments - * instead. When execPath is specified, the execPath will be executed for relaunch - * instead of current app. Note that this method does not quit the app when - * executed, you have to call app.quit or app.exit after calling app.relaunch to - * make the app restart. When app.relaunch is called for multiple times, multiple - * instances will be started after current instance exited. An example of + * Relaunches the app when current instance exits. By default, the new instance + * will use the same working directory and command line arguments with current + * instance. When args is specified, the args will be passed as command line + * arguments instead. When execPath is specified, the execPath will be executed for + * relaunch instead of current app. Note that this method does not quit the app + * when executed, you have to call app.quit or app.exit after calling app.relaunch + * to make the app restart. When app.relaunch is called for multiple times, + * multiple instances will be started after current instance exited. An example of * restarting current instance immediately and adding a new command line argument * to the new instance: */ @@ -926,27 +956,25 @@ declare namespace Electron { */ removeAsDefaultProtocolClient(protocol: string, path?: string, args?: string[]): boolean; /** - * This method makes your application a Single Instance Application - instead of - * allowing multiple instances of your app to run, this will ensure that only a - * single instance of your app is running, and other instances signal this instance - * and exit. The return value of this method indicates whether or not this instance - * of your application successfully obtained the lock. If it failed to obtain the - * lock you can assume that another instance of your application is already running - * with the lock and exit immediately. I.e. This method returns true if your - * process is the primary instance of your application and your app should continue - * loading. It returns false if your process should immediately quit as it has - * sent its parameters to another instance that has already acquired the lock. On - * macOS the system enforces single instance automatically when users try to open a - * second instance of your app in Finder, and the open-file and open-url events - * will be emitted for that. However when users start your app in command line the - * system's single instance mechanism will be bypassed and you have to use this + * The return value of this method indicates whether or not this instance of your + * application successfully obtained the lock. If it failed to obtain the lock, + * you can assume that another instance of your application is already running with + * the lock and exit immediately. I.e. This method returns true if your process is + * the primary instance of your application and your app should continue loading. + * It returns false if your process should immediately quit as it has sent its + * parameters to another instance that has already acquired the lock. On macOS, the + * system enforces single instance automatically when users try to open a second + * instance of your app in Finder, and the open-file and open-url events will be + * emitted for that. However when users start your app in command line, the + * system's single instance mechanism will be bypassed, and you have to use this * method to ensure single instance. An example of activating the window of primary * instance when a second instance starts: */ requestSingleInstanceLock(): boolean; /** * Set the about panel options. This will override the values defined in the app's - * .plist file. See the Apple docs for more details. + * .plist file on MacOS. See the Apple docs for more details. On Linux, values must + * be set in order to be shown; there are no defaults. */ setAboutPanelOptions(options: AboutPanelOptionsOptions): void; /** @@ -955,9 +983,17 @@ declare namespace Electron { * accessibility docs for more details. Disabled by default. This API must be * called after the ready event is emitted. Note: Rendering accessibility tree can * significantly affect the performance of your app. It should not be enabled by - * default. + * default. Deprecated Soon */ setAccessibilitySupportEnabled(enabled: boolean): void; + /** + * Sets or creates a directory your app's logs which can then be manipulated with + * app.getPath() or app.setPath(pathName, newPath). Calling app.setAppLogsPath() + * without a path parameter will result in this directory being set to + * /Library/Logs/YourAppName on macOS, and inside the userData directory on Linux + * and Windows. + */ + setAppLogsPath(path?: string): void; /** * Changes the Application User Model ID to id. */ @@ -967,20 +1003,23 @@ declare namespace Electron { * (aka URI scheme). It allows you to integrate your app deeper into the operating * system. Once registered, all links with your-protocol:// will be opened with the * current executable. The whole link, including protocol, will be passed to your - * application as a parameter. On Windows you can provide optional parameters path, - * the path to your executable, and args, an array of arguments to be passed to - * your executable when it launches. Note: On macOS, you can only register + * application as a parameter. On Windows, you can provide optional parameters + * path, the path to your executable, and args, an array of arguments to be passed + * to your executable when it launches. Note: On macOS, you can only register * protocols that have been added to your app's info.plist, which can not be * modified at runtime. You can however change the file with a simple text editor * or script during build time. Please refer to Apple's documentation for details. - * The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme - * internally. + * Note: In a Windows Store environment (when packaged as an appx) this API will + * return true for all calls but the registry key it sets won't be accessible by + * other applications. In order to register your Windows Store application as a + * default protocol handler you must declare the protocol in your manifest. The API + * uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally. */ setAsDefaultProtocolClient(protocol: string, path?: string, args?: string[]): boolean; /** * Sets the counter badge for current app. Setting the count to 0 will hide the - * badge. On macOS it shows on the dock icon. On Linux it only works for Unity - * launcher, Note: Unity launcher requires the existence of a .desktop file to + * badge. On macOS, it shows on the dock icon. On Linux, it only works for Unity + * launcher. Note: Unity launcher requires the existence of a .desktop file to * work, for more information please read Desktop Environment Integration. */ setBadgeCount(count: number): boolean; @@ -1012,12 +1051,12 @@ declare namespace Electron { setName(name: string): void; /** * Overrides the path to a special directory or file associated with name. If the - * path specifies a directory that does not exist, the directory will be created by - * this method. On failure an Error is thrown. You can only override paths of a - * name defined in app.getPath. By default, web pages' cookies and caches will be - * stored under the userData directory. If you want to change this location, you - * have to override the userData path before the ready event of the app module is - * emitted. + * path specifies a directory that does not exist, an Error is thrown. In that + * case, the directory should be created with fs.mkdirSync or similar. You can only + * override paths of a name defined in app.getPath. By default, web pages' cookies + * and caches will be stored under the userData directory. If you want to change + * this location, you have to override the userData path before the ready event of + * the app module is emitted. */ setPath(name: string, path: string): void; /** @@ -1037,10 +1076,14 @@ declare namespace Electron { */ show(): void; /** - * Show the about panel with the values defined in the app's .plist file or with - * the options set via app.setAboutPanelOptions(options). + * Show the app's about panel options. These options can be overridden with + * app.setAboutPanelOptions(options). */ showAboutPanel(): void; + /** + * Show the platform's native emoji picker. + */ + showEmojiPanel(): void; /** * Start accessing a security scoped resource. With this method Electron * applications that are packaged for the Mac App Store may reach outside their @@ -1054,6 +1097,34 @@ declare namespace Electron { */ updateCurrentActivity(type: string, userInfo: any): void; whenReady(): Promise; + /** + * A Boolean property that's true if Chrome's accessibility support is enabled, + * false otherwise. This property will be true if the use of assistive + * technologies, such as screen readers, has been detected. Setting this property + * to true manually enables Chrome's accessibility support, allowing developers to + * expose accessibility switch to users in application settings. See Chromium's + * accessibility docs for more details. Disabled by default. This API must be + * called after the ready event is emitted. Note: Rendering accessibility tree can + * significantly affect the performance of your app. It should not be enabled by + * default. + */ + accessibilitySupportEnabled?: boolean; + /** + * A Boolean which when true disables the overrides that Electron has in place to + * ensure renderer processes are restarted on every navigation. The current + * default value for this property is false. The intention is for these overrides + * to become disabled by default and then at some point in the future this property + * will be removed. This property impacts which native modules you can use in the + * renderer process. For more information on the direction Electron is going with + * renderer process restarts and usage of native modules in the renderer process + * please check out this Tracking Issue. + */ + allowRendererProcessReuse?: boolean; + /** + * A Menu property that return Menu if one has been set and null otherwise. Users + * can pass a Menu to set this property. + */ + applicationMenu?: Menu; commandLine: CommandLine; dock: Dock; /** @@ -1217,7 +1288,8 @@ declare namespace Electron { * media keys or browser commands, as well as the "Back" button built into some * mice on Windows. Commands are lowercased, underscores are replaced with hyphens, * and the APPCOMMAND_ prefix is stripped off. e.g. APPCOMMAND_BROWSER_BACKWARD is - * emitted as browser-backward. + * emitted as browser-backward. The following app commands are explictly supported + * on Linux: */ on(event: 'app-command', listener: (event: Event, command: string) => void): this; @@ -1551,6 +1623,10 @@ declare namespace Electron { * ready event of the app module is emitted. */ static removeExtension(name: string): void; + /** + * Replacement API for setBrowserView supporting work with multi browser views. + */ + addBrowserView(browserView: BrowserView): void; /** * Adds a window as a tab on this window, after the tab for the window instance. */ @@ -1561,11 +1637,22 @@ declare namespace Electron { blur(): void; blurWebView(): void; /** - * Same as webContents.capturePage([rect, ]callback). + * Captures a snapshot of the page within rect. Upon completion callback will be + * called with callback(image). The image is an instance of NativeImage that stores + * data of the snapshot. Omitting rect will capture the whole visible page. + * Deprecated Soon */ capturePage(callback: (image: NativeImage) => void): void; /** - * Same as webContents.capturePage([rect, ]callback). + * Captures a snapshot of the page within rect. Omitting rect will capture the + * whole visible page. + */ + capturePage(rect?: Rectangle): Promise; + /** + * Captures a snapshot of the page within rect. Upon completion callback will be + * called with callback(image). The image is an instance of NativeImage that stores + * data of the snapshot. Omitting rect will capture the whole visible page. + * Deprecated Soon */ capturePage(rect: Rectangle, callback: (image: NativeImage) => void): void; /** @@ -1598,11 +1685,13 @@ declare namespace Electron { focus(): void; focusOnWebView(): void; getBounds(): Rectangle; - /** - * Note: The BrowserView API is currently experimental and may change or be removed - * in future Electron releases. - */ getBrowserView(): (BrowserView) | (null); + /** + * Returns array of BrowserView what was an attached with addBrowserView or + * setBrowserView. Note: The BrowserView API is currently experimental and may + * change or be removed in future Electron releases. + */ + getBrowserViews(): void; getChildWindows(): BrowserWindow[]; getContentBounds(): Rectangle; getContentSize(): number[]; @@ -1626,13 +1715,10 @@ declare namespace Electron { getRepresentedFilename(): string; getSize(): number[]; /** - * Note: The title of web page can be different from the title of the native + * Note: The title of the web page can be different from the title of the native * window. */ getTitle(): string; - /** - * On Windows and Linux always returns true. - */ hasShadow(): boolean; /** * Hides the window. @@ -1684,7 +1770,7 @@ declare namespace Electron { * Same as webContents.loadFile, filePath should be a path to an HTML file relative * to the root of your application. See the webContents docs for more information. */ - loadFile(filePath: string, options?: LoadFileOptions): void; + loadFile(filePath: string, options?: LoadFileOptions): Promise; /** * Same as webContents.loadURL(url[, options]). The url can be a remote address * (e.g. http://) or a path to a local HTML file using the file:// protocol. To @@ -1692,7 +1778,7 @@ declare namespace Electron { * url.format method: You can load a URL using a POST request with URL-encoded data * by doing the following: */ - loadURL(url: string, options?: LoadURLOptions): void; + loadURL(url: string, options?: LoadURLOptions): Promise; /** * Maximizes the window. This will also show (but not focus) the window if it isn't * being displayed already. @@ -1725,6 +1811,11 @@ declare namespace Electron { * Same as webContents.reload. */ reload(): void; + removeBrowserView(browserView: BrowserView): void; + /** + * Remove the window's menu bar. + */ + removeMenu(): void; /** * Restores the window from minimized state to its previous state. */ @@ -1829,7 +1920,7 @@ declare namespace Electron { */ setFullScreenable(fullscreenable: boolean): void; /** - * Sets whether the window should have a shadow. On Windows and Linux does nothing. + * Sets whether the window should have a shadow. */ setHasShadow(hasShadow: boolean): void; /** @@ -1856,8 +1947,7 @@ declare namespace Electron { */ setMaximumSize(width: number, height: number): void; /** - * Sets the menu as the window's menu bar, setting it to null will remove the menu - * bar. + * Sets the menu as the window's menu bar. */ setMenu(menu: (Menu) | (null)): void; /** @@ -2296,12 +2386,12 @@ declare namespace Electron { // Docs: http://electronjs.org/docs/api/clipboard - availableFormats(type?: string): string[]; + availableFormats(type?: 'selection' | 'clipboard'): string[]; /** * Clears the clipboard content. */ - clear(type?: string): void; - has(format: string, type?: string): boolean; + clear(type?: 'selection' | 'clipboard'): void; + has(format: string, type?: 'selection' | 'clipboard'): boolean; read(format: string): string; /** * Returns an Object containing title and url keys representing the bookmark in the @@ -2311,24 +2401,24 @@ declare namespace Electron { readBookmark(): ReadBookmark; readBuffer(format: string): Buffer; readFindText(): string; - readHTML(type?: string): string; - readImage(type?: string): NativeImage; - readRTF(type?: string): string; - readText(type?: string): string; + readHTML(type?: 'selection' | 'clipboard'): string; + readImage(type?: 'selection' | 'clipboard'): NativeImage; + readRTF(type?: 'selection' | 'clipboard'): string; + readText(type?: 'selection' | 'clipboard'): string; /** * Writes data to the clipboard. */ - write(data: Data, type?: string): void; + write(data: Data, type?: 'selection' | 'clipboard'): void; /** * Writes the title and url into the clipboard as a bookmark. Note: Most apps on * Windows don't support pasting bookmarks into them so you can use clipboard.write * to write both a bookmark and fallback text to the clipboard. */ - writeBookmark(title: string, url: string, type?: string): void; + writeBookmark(title: string, url: string, type?: 'selection' | 'clipboard'): void; /** * Writes the buffer into the clipboard as format. */ - writeBuffer(format: string, buffer: Buffer, type?: string): void; + writeBuffer(format: string, buffer: Buffer, type?: 'selection' | 'clipboard'): void; /** * Writes the text into the find pasteboard as plain text. This method uses * synchronous IPC when called from the renderer process. @@ -2337,66 +2427,61 @@ declare namespace Electron { /** * Writes markup to the clipboard. */ - writeHTML(markup: string, type?: string): void; + writeHTML(markup: string, type?: 'selection' | 'clipboard'): void; /** * Writes image to the clipboard. */ - writeImage(image: NativeImage, type?: string): void; + writeImage(image: NativeImage, type?: 'selection' | 'clipboard'): void; /** * Writes the text into the clipboard in RTF. */ - writeRTF(text: string, type?: string): void; + writeRTF(text: string, type?: 'selection' | 'clipboard'): void; /** * Writes the text into the clipboard as plain text. */ - writeText(text: string, type?: string): void; + writeText(text: string, type?: 'selection' | 'clipboard'): void; } interface ContentTracing extends EventEmitter { // Docs: http://electronjs.org/docs/api/content-tracing - /** - * Get the current monitoring traced data. Child processes typically cache trace - * data and only rarely flush and send trace data back to the main process. This is - * because it may be an expensive operation to send the trace data over IPC and we - * would like to avoid unneeded runtime overhead from tracing. So, to end tracing, - * we must asynchronously ask all child processes to flush any pending trace data. - * Once all child processes have acknowledged the captureMonitoringSnapshot request - * the callback will be called with a file that contains the traced data. - */ - captureMonitoringSnapshot(resultFilePath: string, callback: (resultFilePath: string) => void): void; /** * Get a set of category groups. The category groups can change as new code paths * are reached. Once all child processes have acknowledged the getCategories - * request the callback is invoked with an array of category groups. + * request the callback is invoked with an array of category groups. Deprecated + * Soon */ getCategories(callback: (categories: string[]) => void): void; + /** + * Get a set of category groups. The category groups can change as new code paths + * are reached. + */ + getCategories(): Promise; /** * Get the maximum usage across processes of trace buffer as a percentage of the * full state. When the TraceBufferUsage value is determined the callback is - * called. + * called. Deprecated Soon */ - getTraceBufferUsage(callback: (value: number, percentage: number) => void): void; + getTraceBufferUsage(callback: (value: number) => void): void; /** - * Start monitoring on all processes. Monitoring begins immediately locally and - * asynchronously on child processes as soon as they receive the startMonitoring - * request. Once all child processes have acknowledged the startMonitoring request - * the callback will be called. + * Get the maximum usage across processes of trace buffer as a percentage of the + * full state. */ - startMonitoring(options: StartMonitoringOptions, callback: Function): void; + getTraceBufferUsage(): Promise; /** * Start recording on all processes. Recording begins immediately locally and * asynchronously on child processes as soon as they receive the EnableRecording * request. The callback will be called once all child processes have acknowledged - * the startRecording request. + * the startRecording request. Deprecated Soon */ startRecording(options: (TraceCategoriesAndOptions) | (TraceConfig), callback: Function): void; /** - * Stop monitoring on all processes. Once all child processes have acknowledged the - * stopMonitoring request the callback is called. + * Start recording on all processes. Recording begins immediately locally and + * asynchronously on child processes as soon as they receive the EnableRecording + * request. */ - stopMonitoring(callback: Function): void; + startRecording(options: (TraceCategoriesAndOptions) | (TraceConfig)): Promise; /** * Stop recording on all processes. Child processes typically cache trace data and * only rarely flush and send trace data back to the main process. This helps to @@ -2406,9 +2491,18 @@ declare namespace Electron { * acknowledged the stopRecording request, callback will be called with a file that * contains the traced data. Trace data will be written into resultFilePath if it * is not empty or into a temporary file. The actual file path will be passed to - * callback if it's not null. + * callback if it's not null. Deprecated Soon */ stopRecording(resultFilePath: string, callback: (resultFilePath: string) => void): void; + /** + * Stop recording on all processes. Child processes typically cache trace data and + * only rarely flush and send trace data back to the main process. This helps to + * minimize the runtime overhead of tracing since sending trace data over IPC can + * be an expensive operation. So, to end tracing, we must asynchronously ask all + * child processes to flush any pending trace data. Trace data will be written into + * resultFilePath if it is not empty or into a temporary file. + */ + stopRecording(resultFilePath: string): Promise; } interface Cookie { @@ -2520,20 +2614,37 @@ declare namespace Electron { /** * Writes any unwritten cookies data to disk. */ + flushStore(): Promise; + /** + * Writes any unwritten cookies data to disk. Deprecated Soon + */ flushStore(callback: Function): void; + /** + * Sends a request to get all cookies matching filter, and resolves a promise with + * the response. + */ + get(filter: Filter): Promise; /** * Sends a request to get all cookies matching filter, callback will be called with - * callback(error, cookies) on complete. + * callback(error, cookies) on complete. Deprecated Soon */ get(filter: Filter, callback: (error: Error, cookies: Cookie[]) => void): void; + /** + * Removes the cookies matching url and name + */ + remove(url: string, name: string): Promise; /** * Removes the cookies matching url and name, callback will called with callback() - * on complete. + * on complete. Deprecated Soon */ remove(url: string, name: string, callback: Function): void; + /** + * Sets a cookie with details. + */ + set(details: Details): Promise; /** * Sets a cookie with details, callback will be called with callback(error) on - * complete. + * complete. Deprecated Soon */ set(details: Details, callback: (error: Error) => void): void; } @@ -2561,15 +2672,15 @@ declare namespace Electron { id: string; } - interface CrashReporter extends EventEmitter { + interface CrashReporter { // Docs: http://electronjs.org/docs/api/crash-reporter /** * Set an extra parameter to be sent with the crash report. The values specified * here will be sent in addition to any values set via the extra option when start - * was called. This API is only available on macOS, if you need to add/update extra - * parameters on Linux and Windows after your first call to start you can call + * was called. This API is only available on macOS and windows, if you need to + * add/update extra parameters on Linux after your first call to start you can call * start again with the updated extra options. */ addExtraParameter(key: string, value: string): void; @@ -2613,13 +2724,10 @@ declare namespace Electron { * reports from them, use process.crashReporter.start instead. Pass the same * options as above along with an additional one called crashesDirectory that * should point to a directory to store the crash reports temporarily. You can test - * this out by calling process.crash() to crash the child process. Note: To collect - * crash reports from child process in Windows, you need to add this extra code as - * well. This will start the process that will monitor and send the crash reports. - * Replace submitURL, productName and crashesDirectory with appropriate values. - * Note: If you need send additional/updated extra parameters after your first call - * start you can call addExtraParameter on macOS or call start again with the - * new/updated extra parameters on Linux and Windows. Note: On macOS, Electron uses + * this out by calling process.crash() to crash the child process. Note: If you + * need send additional/updated extra parameters after your first call start you + * can call addExtraParameter on macOS or call start again with the new/updated + * extra parameters on Linux and Windows. Note: On macOS and windows, Electron uses * a new crashpad client for crash collection and reporting. If you want to enable * crash reporting, initializing crashpad from the main process using * crashReporter.start is required regardless of which process you want to collect @@ -2631,6 +2739,17 @@ declare namespace Electron { start(options: CrashReporterStartOptions): void; } + interface CustomScheme { + + // Docs: http://electronjs.org/docs/api/structures/custom-scheme + + privileges?: Privileges; + /** + * Custom schemes to be registered with options. + */ + scheme: string; + } + class Debugger extends EventEmitter { // Docs: http://electronjs.org/docs/api/debugger @@ -2712,9 +2831,13 @@ declare namespace Electron { detach(): void; isAttached(): boolean; /** - * Send given command to the debugging target. + * Send given command to the debugging target. Deprecated Soon */ sendCommand(method: string, commandParams?: any, callback?: (error: any, result: any) => void): void; + /** + * Send given command to the debugging target. + */ + sendCommand(method: string, commandParams?: any): Promise; } interface DesktopCapturer extends EventEmitter { @@ -2725,15 +2848,22 @@ declare namespace Electron { * Starts gathering information about all available desktop media sources, and * calls callback(error, sources) when finished. sources is an array of * DesktopCapturerSource objects, each DesktopCapturerSource represents a screen or - * an individual window that can be captured. + * an individual window that can be captured. Deprecated Soon */ getSources(options: SourcesOptions, callback: (error: Error, sources: DesktopCapturerSource[]) => void): void; + getSources(options: SourcesOptions): Promise; } interface DesktopCapturerSource { // Docs: http://electronjs.org/docs/api/structures/desktop-capturer-source + /** + * An icon image of the application that owns the window or null if the source has + * a type screen. The size of the icon is not known in advance and depends on what + * the the application provides. + */ + appIcon: NativeImage; /** * A unique identifier that will correspond to the id of the matching returned by * the . On some platforms, this is equivalent to the XX portion of the id field @@ -2770,7 +2900,7 @@ declare namespace Electron { * information, and gives the user the option of trusting/importing the * certificate. If you provide a browserWindow argument the dialog will be attached * to the parent window, making it modal. On Windows the options are more limited, - * due to the Win32 APIs used: + * due to the Win32 APIs used: Deprecated Soon */ showCertificateTrustDialog(browserWindow: BrowserWindow, options: CertificateTrustDialogOptions, callback: Function): void; /** @@ -2780,6 +2910,14 @@ declare namespace Electron { * to the parent window, making it modal. On Windows the options are more limited, * due to the Win32 APIs used: */ + showCertificateTrustDialog(options: CertificateTrustDialogOptions): Promise; + /** + * On macOS, this displays a modal dialog that shows a message and certificate + * information, and gives the user the option of trusting/importing the + * certificate. If you provide a browserWindow argument the dialog will be attached + * to the parent window, making it modal. On Windows the options are more limited, + * due to the Win32 APIs used: Deprecated Soon + */ showCertificateTrustDialog(options: CertificateTrustDialogOptions, callback: Function): void; /** * On macOS, this displays a modal dialog that shows a message and certificate @@ -2788,6 +2926,14 @@ declare namespace Electron { * to the parent window, making it modal. On Windows the options are more limited, * due to the Win32 APIs used: */ + showCertificateTrustDialog(browserWindow: BrowserWindow, options: CertificateTrustDialogOptions): Promise; + /** + * On macOS, this displays a modal dialog that shows a message and certificate + * information, and gives the user the option of trusting/importing the + * certificate. If you provide a browserWindow argument the dialog will be attached + * to the parent window, making it modal. On Windows the options are more limited, + * due to the Win32 APIs used: Deprecated Soon + */ showCertificateTrustDialog(browserWindow: BrowserWindow, options: CertificateTrustDialogOptions, callback: Function): void; /** * Displays a modal dialog that shows an error message. This API can be called @@ -2798,73 +2944,140 @@ declare namespace Electron { showErrorBox(title: string, content: string): void; /** * Shows a message box, it will block the process until the message box is closed. - * It returns the index of the clicked button. The browserWindow argument allows - * the dialog to attach itself to a parent window, making it modal. If a callback - * is passed, the dialog will not block the process. The API call will be - * asynchronous and the result will be passed via callback(response). + * The browserWindow argument allows the dialog to attach itself to a parent + * window, making it modal. */ - showMessageBox(browserWindow: BrowserWindow, options: MessageBoxOptions, callback?: (response: number, checkboxChecked: boolean) => void): number; + showMessageBox(browserWindow: BrowserWindow, options: MessageBoxOptions): Promise; + /** + * Shows a message box, it will block the process until the message box is closed. + * The browserWindow argument allows the dialog to attach itself to a parent + * window, making it modal. + */ + showMessageBox(options: MessageBoxOptions): Promise; /** * Shows a message box, it will block the process until the message box is closed. * It returns the index of the clicked button. The browserWindow argument allows - * the dialog to attach itself to a parent window, making it modal. If a callback - * is passed, the dialog will not block the process. The API call will be - * asynchronous and the result will be passed via callback(response). + * the dialog to attach itself to a parent window, making it modal. */ - showMessageBox(options: MessageBoxOptions, callback?: (response: number, checkboxChecked: boolean) => void): number; + showMessageBoxSync(browserWindow: BrowserWindow, options: MessageBoxSyncOptions): number; + /** + * Shows a message box, it will block the process until the message box is closed. + * It returns the index of the clicked button. The browserWindow argument allows + * the dialog to attach itself to a parent window, making it modal. + */ + showMessageBoxSync(options: MessageBoxSyncOptions): number; /** * The browserWindow argument allows the dialog to attach itself to a parent * window, making it modal. The filters specifies an array of file types that can * be displayed or selected when you want to limit the user to a specific type. For * example: The extensions array should contain extensions without wildcards or * dots (e.g. 'png' is good but '.png' and '*.png' are bad). To show all files, use - * the '*' wildcard (no other wildcard is supported). If a callback is passed, the - * API call will be asynchronous and the result will be passed via - * callback(filenames). Note: On Windows and Linux an open dialog can not be both a - * file selector and a directory selector, so if you set properties to ['openFile', - * 'openDirectory'] on these platforms, a directory selector will be shown. + * the '*' wildcard (no other wildcard is supported). Note: On Windows and Linux an + * open dialog can not be both a file selector and a directory selector, so if you + * set properties to ['openFile', 'openDirectory'] on these platforms, a directory + * selector will be shown. */ - showOpenDialog(browserWindow: BrowserWindow, options: OpenDialogOptions, callback?: (filePaths: string[], bookmarks: string[]) => void): (string[]) | (undefined); + showOpenDialog(browserWindow: BrowserWindow, options: OpenDialogOptions, callback?: Function): Promise; /** * The browserWindow argument allows the dialog to attach itself to a parent * window, making it modal. The filters specifies an array of file types that can * be displayed or selected when you want to limit the user to a specific type. For * example: The extensions array should contain extensions without wildcards or * dots (e.g. 'png' is good but '.png' and '*.png' are bad). To show all files, use - * the '*' wildcard (no other wildcard is supported). If a callback is passed, the - * API call will be asynchronous and the result will be passed via - * callback(filenames). Note: On Windows and Linux an open dialog can not be both a - * file selector and a directory selector, so if you set properties to ['openFile', - * 'openDirectory'] on these platforms, a directory selector will be shown. + * the '*' wildcard (no other wildcard is supported). Note: On Windows and Linux an + * open dialog can not be both a file selector and a directory selector, so if you + * set properties to ['openFile', 'openDirectory'] on these platforms, a directory + * selector will be shown. */ - showOpenDialog(options: OpenDialogOptions, callback?: (filePaths: string[], bookmarks: string[]) => void): (string[]) | (undefined); + showOpenDialog(options: OpenDialogOptions, callback?: Function): Promise; /** * The browserWindow argument allows the dialog to attach itself to a parent * window, making it modal. The filters specifies an array of file types that can - * be displayed, see dialog.showOpenDialog for an example. If a callback is passed, - * the API call will be asynchronous and the result will be passed via - * callback(filename). + * be displayed or selected when you want to limit the user to a specific type. For + * example: The extensions array should contain extensions without wildcards or + * dots (e.g. 'png' is good but '.png' and '*.png' are bad). To show all files, use + * the '*' wildcard (no other wildcard is supported). Note: On Windows and Linux an + * open dialog can not be both a file selector and a directory selector, so if you + * set properties to ['openFile', 'openDirectory'] on these platforms, a directory + * selector will be shown. */ - showSaveDialog(browserWindow: BrowserWindow, options: SaveDialogOptions, callback?: (filename: string, bookmark: string) => void): (string) | (undefined); + showOpenDialogSync(browserWindow: BrowserWindow, options: OpenDialogSyncOptions): (string[]) | (undefined); /** * The browserWindow argument allows the dialog to attach itself to a parent * window, making it modal. The filters specifies an array of file types that can - * be displayed, see dialog.showOpenDialog for an example. If a callback is passed, - * the API call will be asynchronous and the result will be passed via - * callback(filename). + * be displayed or selected when you want to limit the user to a specific type. For + * example: The extensions array should contain extensions without wildcards or + * dots (e.g. 'png' is good but '.png' and '*.png' are bad). To show all files, use + * the '*' wildcard (no other wildcard is supported). Note: On Windows and Linux an + * open dialog can not be both a file selector and a directory selector, so if you + * set properties to ['openFile', 'openDirectory'] on these platforms, a directory + * selector will be shown. */ - showSaveDialog(options: SaveDialogOptions, callback?: (filename: string, bookmark: string) => void): (string) | (undefined); + showOpenDialogSync(options: OpenDialogSyncOptions): (string[]) | (undefined); + /** + * The browserWindow argument allows the dialog to attach itself to a parent + * window, making it modal. The filters specifies an array of file types that can + * be displayed, see dialog.showOpenDialog for an example. Note: On macOS, using + * the asynchronous version is recommended to avoid issues when expanding and + * collapsing the dialog. + */ + showSaveDialog(options: SaveDialogOptions): Promise; + /** + * The browserWindow argument allows the dialog to attach itself to a parent + * window, making it modal. The filters specifies an array of file types that can + * be displayed, see dialog.showOpenDialog for an example. Note: On macOS, using + * the asynchronous version is recommended to avoid issues when expanding and + * collapsing the dialog. + */ + showSaveDialog(browserWindow: BrowserWindow, options: SaveDialogOptions): Promise; + /** + * The browserWindow argument allows the dialog to attach itself to a parent + * window, making it modal. The filters specifies an array of file types that can + * be displayed, see dialog.showOpenDialog for an example. + */ + showSaveDialogSync(options: SaveDialogSyncOptions): (string) | (undefined); + /** + * The browserWindow argument allows the dialog to attach itself to a parent + * window, making it modal. The filters specifies an array of file types that can + * be displayed, see dialog.showOpenDialog for an example. + */ + showSaveDialogSync(browserWindow: BrowserWindow, options: SaveDialogSyncOptions): (string) | (undefined); } interface Display { // Docs: http://electronjs.org/docs/api/structures/display + /** + * Can be available, unavailable, unknown. + */ + accelerometerSupport: ('available' | 'unavailable' | 'unknown'); bounds: Rectangle; + /** + * The number of bits per pixel. + */ + colorDepth: number; + /** + * represent a color space (three-dimensional object which contains all realizable + * color combinations) for the purpose of color conversions + */ + colorSpace: string; + /** + * The number of bits per color component. + */ + depthPerComponent: number; /** * Unique identifier associated with the display. */ id: number; + /** + * true for an internal display and false for an external display + */ + internal: boolean; + /** + * Whether or not the display is a monochrome display. + */ + monochrome: boolean; /** * Can be 0, 90, 180, 270, represents screen rotation in clock-wise degrees. */ @@ -2951,6 +3164,7 @@ declare namespace Electron { getLastModifiedTime(): string; getMimeType(): string; getReceivedBytes(): number; + getSaveDialogOptions(): SaveDialogOptions; getSavePath(): string; getStartTime(): number; /** @@ -2977,6 +3191,12 @@ declare namespace Electron { * received bytes and restart the download from the beginning. */ resume(): void; + /** + * This API allows the user to set custom options for the save dialog that opens + * for the download item by default. The API is only available in session's + * will-download callback function. + */ + setSaveDialogOptions(options: SaveDialogOptions): void; /** * The API is only available in session's will-download callback function. If user * doesn't set the save path via the API, Electron will use the original routine to @@ -2985,6 +3205,13 @@ declare namespace Electron { setSavePath(path: string): void; } + interface Event extends GlobalEvent { + + // Docs: http://electronjs.org/docs/api/structures/event + + preventDefault: (() => void); + } + interface FileFilter { // Docs: http://electronjs.org/docs/api/structures/file-filter @@ -3012,7 +3239,17 @@ declare namespace Electron { * on macOS 10.14 Mojave unless the app has been authorized as a trusted * accessibility client: */ - register(accelerator: Accelerator, callback: Function): void; + register(accelerator: Accelerator, callback: Function): boolean; + /** + * Registers a global shortcut of all accelerator items in accelerators. The + * callback is called when any of the registered shortcuts are pressed by the user. + * When a given accelerator is already taken by other applications, this call will + * silently fail. This behavior is intended by operating systems, since they don't + * want applications to fight for global shortcuts. The following accelerators will + * not be registered successfully on macOS 10.14 Mojave unless the app has been + * authorized as a trusted accessibility client: + */ + registerAll(accelerators: string[], callback: Function): void; /** * Unregisters the global shortcut of accelerator. */ @@ -3118,15 +3355,24 @@ declare namespace Electron { */ finishTransactionByDate(date: string): void; /** - * Retrieves the product descriptions. + * Retrieves the product descriptions. Deprecated Soon */ getProducts(productIDs: string[], callback: (products: Product[]) => void): void; + /** + * Retrieves the product descriptions. + */ + getProducts(productIDs: string[]): Promise; getReceiptURL(): string; + /** + * You should listen for the transactions-updated event as soon as possible and + * certainly before you call purchaseProduct. Deprecated Soon + */ + purchaseProduct(productID: string, quantity?: number, callback?: (isProductValid: boolean) => void): void; /** * You should listen for the transactions-updated event as soon as possible and * certainly before you call purchaseProduct. */ - purchaseProduct(productID: string, quantity?: number, callback?: (isProductValid: boolean) => void): void; + purchaseProduct(productID: string, quantity?: number): Promise; } class IncomingMessage extends EventEmitter { @@ -3228,12 +3474,12 @@ declare namespace Electron { * Listens to channel, when a new message arrives listener would be called with * listener(event, args...). */ - on(channel: string, listener: Function): this; + on(channel: string, listener: (event: IpcMainEvent, ...args: any[]) => void): this; /** * Adds a one time listener function for the event. This listener is invoked only * the next time a message is sent to channel, after which it is removed. */ - once(channel: string, listener: Function): this; + once(channel: string, listener: (event: IpcMainEvent, ...args: any[]) => void): this; /** * Removes listeners of the specified channel. */ @@ -3245,6 +3491,31 @@ declare namespace Electron { removeListener(channel: string, listener: Function): this; } + interface IpcMainEvent extends Event { + + // Docs: http://electronjs.org/docs/api/structures/ipc-main-event + + /** + * The ID of the renderer frame that sent this message + */ + frameId: number; + /** + * A function that will send an IPC message to the renderer frame that sent the + * original message that you are currently handling. You should use this method to + * "reply" to the sent message in order to guaruntee the reply will go to the + * correct process and frame. + */ + reply: Function; + /** + * Set this to the value to be returned in a syncronous message + */ + returnValue: any; + /** + * Returns the webContents that sent the message + */ + sender: WebContents; + } + interface IpcRenderer extends EventEmitter { // Docs: http://electronjs.org/docs/api/ipc-renderer @@ -3253,12 +3524,12 @@ declare namespace Electron { * Listens to channel, when a new message arrives listener would be called with * listener(event, args...). */ - on(channel: string, listener: Function): this; + on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this; /** * Adds a one time listener function for the event. This listener is invoked only * the next time a message is sent to channel, after which it is removed. */ - once(channel: string, listener: Function): this; + once(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this; /** * Removes all listeners, or those of the specified channel. */ @@ -3295,6 +3566,23 @@ declare namespace Electron { sendToHost(channel: string, ...args: any[]): void; } + interface IpcRendererEvent extends Event { + + // Docs: http://electronjs.org/docs/api/structures/ipc-renderer-event + + /** + * The IpcRenderer instance that emitted the event originally + */ + sender: IpcRenderer; + /** + * The webContents.id that sent the message, you can call + * event.sender.sendTo(event.senderId, ...) to reply to the message, see for more + * information. This only applies to messages sent from a different renderer. + * Messages sent directly from the main process set event.senderId to 0. + */ + senderId: number; + } + interface JumpListCategory { // Docs: http://electronjs.org/docs/api/structures/jump-list-category @@ -3358,6 +3646,37 @@ declare namespace Electron { * One of the following: */ type?: ('task' | 'separator' | 'file'); + /** + * The working directory. Default is empty. + */ + workingDirectory?: string; + } + + interface KeyboardEvent extends Event { + + // Docs: http://electronjs.org/docs/api/structures/keyboard-event + + /** + * whether an Alt key was used in an accelerator to trigger the Event + */ + altKey?: boolean; + /** + * whether the Control key was used in an accelerator to trigger the Event + */ + ctrlKey?: boolean; + /** + * whether a meta key was used in an accelerator to trigger the Event + */ + metaKey?: boolean; + /** + * whether a Shift key was used in an accelerator to trigger the Event + */ + shiftKey?: boolean; + /** + * whether an accelerator was used to trigger the event as opposed to another user + * gesture like mouse click + */ + triggeredByAccelerator?: boolean; } interface MemoryUsageDetails { @@ -3393,7 +3712,7 @@ declare namespace Electron { * usage can be referenced above. You can also attach other fields to the element * of the template and they will become properties of the constructed menu items. */ - static buildFromTemplate(template: MenuItemConstructorOptions[]): Menu; + static buildFromTemplate(template: Array<(MenuItemConstructorOptions) | (MenuItem)>): Menu; /** * Note: The returned Menu instance doesn't support dynamic addition or removal of * menu items. Instance properties can still be dynamically modified. @@ -3408,9 +3727,16 @@ declare namespace Electron { static sendActionToFirstResponder(action: string): void; /** * Sets menu as the application menu on macOS. On Windows and Linux, the menu will - * be set as each window's top menu. Passing null will remove the menu bar on - * Windows and Linux but has no effect on macOS. Note: This API has to be called - * after the ready event of app module. + * be set as each window's top menu. Also on Windows and Linux, you can use a & in + * the top-level item name to indicate which letter should get a generated + * accelerator. For example, using &File for the file menu would result in a + * generated Alt-F accelerator that opens the associated menu. The indicated + * character in the button label gets an underline. The & character is not + * displayed on the button label. Passing null will suppress the default menu. On + * Windows and Linux, this has the additional effect of removing the menu bar from + * the window. Note: The default menu will be created automatically if the app does + * not set one. It contains standard items such as File, Edit, View, Window and + * Help. */ static setApplicationMenu(menu: (Menu) | (null)): void; /** @@ -3438,10 +3764,20 @@ declare namespace Electron { // Docs: http://electronjs.org/docs/api/menu-item constructor(options: MenuItemConstructorOptions); + accelerator: string; checked: boolean; click: Function; + commandId: number; enabled: boolean; + icon: NativeImage; + id: string; label: string; + menu: Menu; + registerAccelerator: boolean; + role: string; + sublabel: string; + submenu: Menu; + type: string; visible: boolean; } @@ -3468,7 +3804,13 @@ declare namespace Electron { */ static createEmpty(): NativeImage; /** - * Creates a new NativeImage instance from buffer. + * Creates a new NativeImage instance from buffer that contains the raw bitmap + * pixel data returned by toBitmap(). The specific format is platform-dependent. + */ + static createFromBitmap(buffer: Buffer, options: CreateFromBitmapOptions): NativeImage; + /** + * Creates a new NativeImage instance from buffer. Tries to decode as PNG or JPEG + * first. */ static createFromBuffer(buffer: Buffer, options?: CreateFromBufferOptions): NativeImage; /** @@ -3556,9 +3898,14 @@ declare namespace Electron { startLogging(path: string): void; /** * Stops recording network events. If not called, net logging will automatically - * end when app quits. + * end when app quits. Deprecated Soon */ stopLogging(callback?: (path: string) => void): void; + /** + * Stops recording network events. If not called, net logging will automatically + * end when app quits. + */ + stopLogging(): Promise; /** * A Boolean property that indicates whether network logs are recorded. */ @@ -3737,6 +4084,15 @@ declare namespace Electron { once(event: 'unlock-screen', listener: Function): this; addListener(event: 'unlock-screen', listener: Function): this; removeListener(event: 'unlock-screen', listener: Function): this; + /** + * Calculate the system idle state. idleThreshold is the amount of time (in + * seconds) before considered idle. locked is available on supported systems only. + */ + getSystemIdleState(idleThreshold: number): ('active' | 'idle' | 'locked' | 'unknown'); + /** + * Calculate system idle time in seconds. + */ + getSystemIdleTime(): number; /** * Calculate the system idle state. idleThreshold is the amount of time (in * seconds) before considered idle. callback will be called synchronously on some @@ -3782,6 +4138,26 @@ declare namespace Electron { status: number; } + interface ProcessMemoryInfo { + + // Docs: http://electronjs.org/docs/api/structures/process-memory-info + + /** + * The amount of memory not shared by other processes, such as JS heap or HTML + * content in Kilobytes. + */ + private: number; + /** + * and The amount of memory currently pinned to actual physical RAM in Kilobytes. + */ + residentSet: number; + /** + * The amount of memory shared between processes, typically memory consumed by the + * Electron code itself in Kilobytes. + */ + shared: number; + } + interface ProcessMetric { // Docs: http://electronjs.org/docs/api/structures/process-metric @@ -3795,9 +4171,9 @@ declare namespace Electron { */ pid: number; /** - * Process type (Browser or Tab or GPU etc). + * Process type. One of the following values: */ - type: string; + type: ('Browser' | 'Tab' | 'Utility' | 'Zygote' | 'GPU' | 'Unknown'); } interface Product { @@ -3812,15 +4188,16 @@ declare namespace Electron { * A string that identifies the version of the content. */ contentVersion: string; - /** - * A Boolean value that indicates whether the App Store has downloadable content - * for this product. - */ - downloadable: boolean; /** * The locale formatted price of the product. */ formattedPrice: string; + /** + * A Boolean value that indicates whether the App Store has downloadable content + * for this product. true if at least one file has been associated with the + * product. + */ + isDownloadable: boolean; /** * A description of the product. */ @@ -3870,9 +4247,10 @@ declare namespace Electron { interceptStringProtocol(scheme: string, handler: (request: InterceptStringProtocolRequest, callback: (data?: string) => void) => void, completion?: (error: Error) => void): void; /** * The callback will be called with a boolean that indicates whether there is - * already a handler for scheme. + * already a handler for scheme. Deprecated Soon */ - isProtocolHandled(scheme: string, callback: (error: Error) => void): void; + isProtocolHandled(scheme: string, callback: (handled: boolean) => void): void; + isProtocolHandled(scheme: string): Promise; /** * Registers a protocol of scheme that will send a Buffer as a response. The usage * is the same with registerFileProtocol, except that the callback should be called @@ -3887,13 +4265,14 @@ declare namespace Electron { * scheme is successfully registered or completion(error) when failed. To handle * the request, the callback should be called with either the file's path or an * object that has a path property, e.g. callback(filePath) or callback({ path: - * filePath }). When callback is called with nothing, a number, or an object that - * has an error property, the request will fail with the error number you - * specified. For the available error numbers you can use, please see the net error - * list. By default the scheme is treated like http:, which is parsed differently - * than protocols that follow the "generic URI syntax" like file:, so you probably - * want to call protocol.registerStandardSchemes to have your scheme treated as a - * standard scheme. + * filePath }). The object may also have a headers property which gives a map of + * headers to values for the response headers, e.g. callback({ path: filePath, + * headers: {"Content-Security-Policy": "default-src 'none'"]}). When callback is + * called with nothing, a number, or an object that has an error property, the + * request will fail with the error number you specified. For the available error + * numbers you can use, please see the net error list. By default the scheme is + * treated like http:, which is parsed differently than protocols that follow the + * "generic URI syntax" like file:. */ registerFileProtocol(scheme: string, handler: (request: RegisterFileProtocolRequest, callback: (filePath?: string) => void) => void, completion?: (error: Error) => void): void; /** @@ -3905,24 +4284,31 @@ declare namespace Electron { * set session to null. For POST requests the uploadData object must be provided. */ registerHttpProtocol(scheme: string, handler: (request: RegisterHttpProtocolRequest, callback: (redirectRequest: RedirectRequest) => void) => void, completion?: (error: Error) => void): void; - registerServiceWorkerSchemes(schemes: string[]): void; /** - * A standard scheme adheres to what RFC 3986 calls generic URI syntax. For example - * http and https are standard schemes, while file is not. Registering a scheme as - * standard, will allow relative and absolute resources to be resolved correctly - * when served. Otherwise the scheme will behave like the file protocol, but - * without the ability to resolve relative URLs. For example when you load - * following page with custom protocol without registering it as standard scheme, - * the image will not be loaded because non-standard schemes can not recognize - * relative URLs: Registering a scheme as standard will allow access to files - * through the FileSystem API. Otherwise the renderer will throw a security error - * for the scheme. By default web storage apis (localStorage, sessionStorage, - * webSQL, indexedDB, cookies) are disabled for non standard schemes. So in general - * if you want to register a custom protocol to replace the http protocol, you have - * to register it as a standard scheme: Note: This method can only be used before - * the ready event of the app module gets emitted. + * Note: This method can only be used before the ready event of the app module gets + * emitted and can be called only once. Registers the scheme as standard, secure, + * bypasses content security policy for resources, allows registering ServiceWorker + * and supports fetch API. Specify a privilege with the value of true to enable the + * capability. An example of registering a privileged scheme, with bypassing + * Content Security Policy: A standard scheme adheres to what RFC 3986 calls + * generic URI syntax. For example http and https are standard schemes, while file + * is not. Registering a scheme as standard, will allow relative and absolute + * resources to be resolved correctly when served. Otherwise the scheme will behave + * like the file protocol, but without the ability to resolve relative URLs. For + * example when you load following page with custom protocol without registering it + * as standard scheme, the image will not be loaded because non-standard schemes + * can not recognize relative URLs: Registering a scheme as standard will allow + * access to files through the FileSystem API. Otherwise the renderer will throw a + * security error for the scheme. By default web storage apis (localStorage, + * sessionStorage, webSQL, indexedDB, cookies) are disabled for non standard + * schemes. So in general if you want to register a custom protocol to replace the + * http protocol, you have to register it as a standard scheme. + * protocol.registerSchemesAsPrivileged can be used to replicate the functionality + * of the previous protocol.registerStandardSchemes, webFrame.registerURLSchemeAs* + * and protocol.registerServiceWorkerSchemes functions that existed prior to + * Electron 5.0.0, for example: before (<= v4.x) after (>= v5.x) */ - registerStandardSchemes(schemes: string[], options?: RegisterStandardSchemesOptions): void; + registerSchemesAsPrivileged(customSchemes: CustomScheme[]): void; /** * Registers a protocol of scheme that will send a Readable as a response. The * usage is similar to the other register{Any}Protocol, except that the callback @@ -4203,22 +4589,33 @@ declare namespace Electron { * authentication. */ allowNTLMCredentialsForDomains(domains: string): void; + clearAuthCache(): Promise; + clearAuthCache(options: (RemovePassword) | (RemoveClientCertificate)): Promise; /** - * Clears the session’s HTTP authentication cache. + * Clears the session’s HTTP authentication cache. Deprecated Soon */ - clearAuthCache(options: (RemovePassword) | (RemoveClientCertificate), callback?: Function): void; + clearAuthCache(options: (RemovePassword) | (RemoveClientCertificate), callback: Function): void; /** * Clears the session’s HTTP cache. */ - clearCache(callback: Function): void; + clearCache(): Promise; + /** + * Clears the session’s HTTP cache. Deprecated Soon + */ + clearCache(callback: (error: number) => void): void; /** * Clears the host resolver cache. */ + clearHostResolverCache(): Promise; + /** + * Clears the host resolver cache. Deprecated Soon + */ clearHostResolverCache(callback?: Function): void; /** - * Clears the data of web storages. + * Clears the storage data for the current session. Deprecated Soon */ clearStorageData(options?: ClearStorageDataOptions, callback?: Function): void; + clearStorageData(options?: ClearStorageDataOptions): Promise; /** * Allows resuming cancelled or interrupted downloads from previous Session. The * API will generate a DownloadItem that can be accessed with the will-download @@ -4240,16 +4637,22 @@ declare namespace Electron { * Writes any unwritten DOMStorage data to disk. */ flushStorageData(): void; - getBlobData(identifier: string, callback: (result: Buffer) => void): void; /** - * Callback is invoked with the session's current cache size. + * Deprecated Soon */ - getCacheSize(callback: (size: number) => void): void; + getBlobData(identifier: string, callback: (result: Buffer) => void): void; + getBlobData(identifier: string): Promise; + getCacheSize(): Promise; + /** + * Callback is invoked with the session's current cache size. Deprecated Soon + */ + getCacheSize(callback: (size: number, error: number) => void): void; getPreloads(): string[]; getUserAgent(): string; + resolveProxy(url: string): Promise; /** * Resolves the proxy information for url. The callback will be called with - * callback(proxy) when the request is performed. + * callback(proxy) when the request is performed. Deprecated Soon */ resolveProxy(url: string, callback: (proxy: string) => void): void; /** @@ -4288,6 +4691,13 @@ declare namespace Electron { * proxyRules has to follow the rules below: For example: The proxyBypassRules is a * comma separated list of rules described below: */ + setProxy(config: Config): Promise; + /** + * Sets the proxy settings. When pacScript and proxyRules are provided together, + * the proxyRules option is ignored and pacScript configuration is applied. The + * proxyRules has to follow the rules below: For example: The proxyBypassRules is a + * comma separated list of rules described below: Deprecated Soon + */ setProxy(config: Config, callback: Function): void; /** * Overrides the userAgent and acceptLanguages for this session. The @@ -4319,7 +4729,12 @@ declare namespace Electron { * Open the given external protocol URL in the desktop's default manner. (For * example, mailto: URLs in the user's default mail agent). */ - openExternal(url: string, options?: OpenExternalOptions, callback?: (error: Error) => void): boolean; + openExternal(url: string, options?: OpenExternalOptions): Promise; + /** + * Open the given external protocol URL in the desktop's default manner. (For + * example, mailto: URLs in the user's default mail agent). Deprecated + */ + openExternalSync(url: string, options?: OpenExternalSyncOptions): boolean; /** * Open the given file in the desktop's default manner. */ @@ -4332,7 +4747,7 @@ declare namespace Electron { /** * Show the given file in a file manager. If possible, select the file. */ - showItemInFolder(fullPath: string): boolean; + showItemInFolder(fullPath: string): void; /** * Creates or updates a shortcut link at shortcutPath. */ @@ -4433,28 +4848,48 @@ declare namespace Electron { once(event: 'color-changed', listener: (event: Event) => void): this; addListener(event: 'color-changed', listener: (event: Event) => void): this; removeListener(event: 'color-changed', listener: (event: Event) => void): this; + on(event: 'high-contrast-color-scheme-changed', listener: (event: Event, + /** + * `true` if a high contrast theme is being used, `false` otherwise. + */ + highContrastColorScheme: boolean) => void): this; + once(event: 'high-contrast-color-scheme-changed', listener: (event: Event, + /** + * `true` if a high contrast theme is being used, `false` otherwise. + */ + highContrastColorScheme: boolean) => void): this; + addListener(event: 'high-contrast-color-scheme-changed', listener: (event: Event, + /** + * `true` if a high contrast theme is being used, `false` otherwise. + */ + highContrastColorScheme: boolean) => void): this; + removeListener(event: 'high-contrast-color-scheme-changed', listener: (event: Event, + /** + * `true` if a high contrast theme is being used, `false` otherwise. + */ + highContrastColorScheme: boolean) => void): this; on(event: 'inverted-color-scheme-changed', listener: (event: Event, /** - * `true` if an inverted color scheme, such as a high contrast theme, is being - * used, `false` otherwise. + * `true` if an inverted color scheme (a high contrast color scheme with light text + * and dark backgrounds) is being used, `false` otherwise. */ invertedColorScheme: boolean) => void): this; once(event: 'inverted-color-scheme-changed', listener: (event: Event, /** - * `true` if an inverted color scheme, such as a high contrast theme, is being - * used, `false` otherwise. + * `true` if an inverted color scheme (a high contrast color scheme with light text + * and dark backgrounds) is being used, `false` otherwise. */ invertedColorScheme: boolean) => void): this; addListener(event: 'inverted-color-scheme-changed', listener: (event: Event, /** - * `true` if an inverted color scheme, such as a high contrast theme, is being - * used, `false` otherwise. + * `true` if an inverted color scheme (a high contrast color scheme with light text + * and dark backgrounds) is being used, `false` otherwise. */ invertedColorScheme: boolean) => void): this; removeListener(event: 'inverted-color-scheme-changed', listener: (event: Event, /** - * `true` if an inverted color scheme, such as a high contrast theme, is being - * used, `false` otherwise. + * `true` if an inverted color scheme (a high contrast color scheme with light text + * and dark backgrounds) is being used, `false` otherwise. */ invertedColorScheme: boolean) => void): this; /** @@ -4467,15 +4902,26 @@ declare namespace Electron { * was not required until macOS 10.14 Mojave, so this method will always return * true if your system is running 10.13 High Sierra or lower. */ - askForMediaAccess(mediaType: 'microphone' | 'camera'): Promise; + askForMediaAccess(mediaType: 'microphone' | 'camera'): Promise; + /** + * NOTE: This API will return false on macOS systems older than Sierra 10.12.2. + */ + canPromptTouchID(): boolean; + /** + * This API is only available on macOS 10.14 Mojave or newer. + */ getAccentColor(): string; + /** + * Returns an object with system animation settings. + */ + getAnimationSettings(): AnimationSettings; /** * Gets the macOS appearance setting that you have declared you want for your * application, maps to NSApplication.appearance. You can use the * setAppLevelAppearance API to set this value. */ getAppLevelAppearance(): ('dark' | 'light' | 'unknown'); - getColor(color: '3d-dark-shadow' | '3d-face' | '3d-highlight' | '3d-light' | '3d-shadow' | 'active-border' | 'active-caption' | 'active-caption-gradient' | 'app-workspace' | 'button-text' | 'caption-text' | 'desktop' | 'disabled-text' | 'highlight' | 'highlight-text' | 'hotlight' | 'inactive-border' | 'inactive-caption' | 'inactive-caption-gradient' | 'inactive-caption-text' | 'info-background' | 'info-text' | 'menu' | 'menu-highlight' | 'menubar' | 'menu-text' | 'scrollbar' | 'window' | 'window-frame' | 'window-text'): string; + getColor(color: '3d-dark-shadow' | '3d-dark-shadow' | '3d-face' | '3d-highlight' | '3d-light' | '3d-shadow' | 'active-border' | 'active-caption' | 'active-caption-gradient' | 'app-workspace' | 'button-text' | 'caption-text' | 'desktop' | 'disabled-text' | 'highlight' | 'highlight-text' | 'hotlight' | 'inactive-border' | 'inactive-caption' | 'inactive-caption-gradient' | 'inactive-caption-text' | 'info-background' | 'info-text' | 'menu' | 'menu-highlight' | 'menubar' | 'menu-text' | 'scrollbar' | 'window' | 'window-frame' | 'window-text' | 'alternate-selected-control-text' | 'alternate-selected-control-text' | 'control-background' | 'control' | 'control-text' | 'disabled-control-text' | 'find-highlight' | 'grid' | 'header-text' | 'highlight' | 'keyboard-focus-indicator' | 'label' | 'link' | 'placeholder-text' | 'quaternary-label' | 'scrubber-textured-background' | 'secondary-label' | 'selected-content-background' | 'selected-control' | 'selected-control-text' | 'selected-menu-item' | 'selected-text-background' | 'selected-text' | 'separator' | 'shadow' | 'tertiary-label' | 'text-background' | 'text' | 'under-page-background' | 'unemphasized-selected-content-background' | 'unemphasized-selected-text-background' | 'unemphasized-selected-text' | 'window-background' | 'window-frame-text'): string; /** * Gets the macOS appearance setting that is currently applied to your application, * maps to NSApplication.effectiveAppearance Please note that until Electron is @@ -4492,6 +4938,12 @@ declare namespace Electron { * always return granted if your system is running 10.13 High Sierra or lower. */ getMediaAccessStatus(mediaType: string): ('not-determined' | 'granted' | 'denied' | 'restricted' | 'unknown'); + /** + * Returns one of several standard system colors that automatically adapt to + * vibrancy and changes in accessibility settings like 'Increase contrast' and + * 'Reduce transparency'. See Apple Documentation for more details. + */ + getSystemColor(color: 'blue' | 'brown' | 'gray' | 'green' | 'orange' | 'pink' | 'purple' | 'red' | 'yellow'): void; /** * Some popular key and types are: */ @@ -4502,6 +4954,7 @@ declare namespace Electron { */ isAeroGlassEnabled(): boolean; isDarkMode(): boolean; + isHighContrastColorScheme(): boolean; isInvertedColorScheme(): boolean; isSwipeTrackingFromScrollEventsEnabled(): boolean; isTrustedAccessibilityClient(prompt: boolean): boolean; @@ -4514,12 +4967,22 @@ declare namespace Electron { * Posts event as native notifications of macOS. The userInfo is an Object that * contains the user information dictionary sent along with the notification. */ - postNotification(event: string, userInfo: any): void; + postNotification(event: string, userInfo: any, deliverImmediately?: boolean): void; /** * Posts event as native notifications of macOS. The userInfo is an Object that * contains the user information dictionary sent along with the notification. */ postWorkspaceNotification(event: string, userInfo: any): void; + /** + * This API itself will not protect your user data; rather, it is a mechanism to + * allow you to do so. Native apps will need to set Access Control Constants like + * kSecAccessControlUserPresence on the their keychain entry so that reading it + * would auto-prompt for Touch ID biometric consent. This could be done with + * node-keytar, such that one would store an encryption key with node-keytar and + * only fetch it if promptTouchID() resolves. NOTE: This API will return a rejected + * Promise on macOS systems older than Sierra 10.12.2. + */ + promptTouchID(reason: string): Promise; /** * Add the specified defaults to your application's NSUserDefaults. */ @@ -4608,6 +5071,10 @@ declare namespace Electron { * The string to be displayed in a JumpList. */ title: string; + /** + * The working directory. Default is empty. + */ + workingDirectory?: string; } interface ThumbarButton { @@ -4829,7 +5296,7 @@ declare namespace Electron { /** * Emitted when the tray icon is clicked. */ - on(event: 'click', listener: (event: Event, + on(event: 'click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ @@ -4838,7 +5305,7 @@ declare namespace Electron { * The position of the event. */ position: Point) => void): this; - once(event: 'click', listener: (event: Event, + once(event: 'click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ @@ -4847,7 +5314,7 @@ declare namespace Electron { * The position of the event. */ position: Point) => void): this; - addListener(event: 'click', listener: (event: Event, + addListener(event: 'click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ @@ -4856,7 +5323,7 @@ declare namespace Electron { * The position of the event. */ position: Point) => void): this; - removeListener(event: 'click', listener: (event: Event, + removeListener(event: 'click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ @@ -4868,22 +5335,22 @@ declare namespace Electron { /** * Emitted when the tray icon is double clicked. */ - on(event: 'double-click', listener: (event: Event, + on(event: 'double-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ bounds: Rectangle) => void): this; - once(event: 'double-click', listener: (event: Event, + once(event: 'double-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ bounds: Rectangle) => void): this; - addListener(event: 'double-click', listener: (event: Event, + addListener(event: 'double-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ bounds: Rectangle) => void): this; - removeListener(event: 'double-click', listener: (event: Event, + removeListener(event: 'double-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ @@ -4965,22 +5432,22 @@ declare namespace Electron { /** * Emitted when the mouse enters the tray icon. */ - on(event: 'mouse-enter', listener: (event: Event, + on(event: 'mouse-enter', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - once(event: 'mouse-enter', listener: (event: Event, + once(event: 'mouse-enter', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - addListener(event: 'mouse-enter', listener: (event: Event, + addListener(event: 'mouse-enter', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - removeListener(event: 'mouse-enter', listener: (event: Event, + removeListener(event: 'mouse-enter', listener: (event: KeyboardEvent, /** * The position of the event. */ @@ -4988,22 +5455,22 @@ declare namespace Electron { /** * Emitted when the mouse exits the tray icon. */ - on(event: 'mouse-leave', listener: (event: Event, + on(event: 'mouse-leave', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - once(event: 'mouse-leave', listener: (event: Event, + once(event: 'mouse-leave', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - addListener(event: 'mouse-leave', listener: (event: Event, + addListener(event: 'mouse-leave', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - removeListener(event: 'mouse-leave', listener: (event: Event, + removeListener(event: 'mouse-leave', listener: (event: KeyboardEvent, /** * The position of the event. */ @@ -5011,22 +5478,22 @@ declare namespace Electron { /** * Emitted when the mouse moves in the tray icon. */ - on(event: 'mouse-move', listener: (event: Event, + on(event: 'mouse-move', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - once(event: 'mouse-move', listener: (event: Event, + once(event: 'mouse-move', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - addListener(event: 'mouse-move', listener: (event: Event, + addListener(event: 'mouse-move', listener: (event: KeyboardEvent, /** * The position of the event. */ position: Point) => void): this; - removeListener(event: 'mouse-move', listener: (event: Event, + removeListener(event: 'mouse-move', listener: (event: KeyboardEvent, /** * The position of the event. */ @@ -5034,22 +5501,22 @@ declare namespace Electron { /** * Emitted when the tray icon is right clicked. */ - on(event: 'right-click', listener: (event: Event, + on(event: 'right-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ bounds: Rectangle) => void): this; - once(event: 'right-click', listener: (event: Event, + once(event: 'right-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ bounds: Rectangle) => void): this; - addListener(event: 'right-click', listener: (event: Event, + addListener(event: 'right-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ bounds: Rectangle) => void): this; - removeListener(event: 'right-click', listener: (event: Event, + removeListener(event: 'right-click', listener: (event: KeyboardEvent, /** * The bounds of tray icon. */ @@ -5068,6 +5535,7 @@ declare namespace Electron { */ getBounds(): Rectangle; getIgnoreDoubleClickEvents(): boolean; + getTitle(title: string): string; isDestroyed(): boolean; /** * Pops up the context menu of the tray icon. When menu is passed, the menu will be @@ -5080,9 +5548,9 @@ declare namespace Electron { */ setContextMenu(menu: (Menu) | (null)): void; /** - * Sets when the tray's icon background becomes highlighted (in blue). Note: You - * can use highlightMode with a BrowserWindow by toggling between 'never' and - * 'always' modes when the window visibility changes. + * Sets when the tray's icon background becomes highlighted (in blue). Deprecated + * Note: You can use highlightMode with a BrowserWindow by toggling between 'never' + * and 'always' modes when the window visibility changes. */ setHighlightMode(mode: 'selection' | 'always' | 'never'): void; /** @@ -5100,7 +5568,7 @@ declare namespace Electron { */ setPressedImage(image: (NativeImage) | (string)): void; /** - * Sets the title displayed aside of the tray icon in the status bar (Support ANSI + * Sets the title displayed next to the tray icon in the status bar (Support ANSI * colors). */ setTitle(title: string): void; @@ -5368,6 +5836,14 @@ declare namespace Electron { * coordinates of the custom cursor's hotspot. */ hotspot?: Point) => void): this; + /** + * Emitted when desktopCapturer.getSources() is called in the renderer process. + * Calling event.preventDefault() will make it return empty sources. + */ + on(event: 'desktop-capturer-get-sources', listener: (event: Event) => void): this; + once(event: 'desktop-capturer-get-sources', listener: (event: Event) => void): this; + addListener(event: 'desktop-capturer-get-sources', listener: (event: Event) => void): this; + removeListener(event: 'desktop-capturer-get-sources', listener: (event: Event) => void): this; /** * Emitted when webContents is destroyed. */ @@ -5716,6 +6192,13 @@ declare namespace Electron { once(event: 'dom-ready', listener: (event: Event) => void): this; addListener(event: 'dom-ready', listener: (event: Event) => void): this; removeListener(event: 'dom-ready', listener: (event: Event) => void): this; + /** + * Emitted when the window enters a full-screen state triggered by HTML API. + */ + on(event: 'enter-html-full-screen', listener: Function): this; + once(event: 'enter-html-full-screen', listener: Function): this; + addListener(event: 'enter-html-full-screen', listener: Function): this; + removeListener(event: 'enter-html-full-screen', listener: Function): this; /** * Emitted when a result is available for [webContents.findInPage] request. */ @@ -5727,6 +6210,45 @@ declare namespace Electron { result: Result) => void): this; removeListener(event: 'found-in-page', listener: (event: Event, result: Result) => void): this; + /** + * Emitted when the renderer process sends an asynchronous message via + * ipcRenderer.send(). + */ + on(event: 'ipc-message', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + once(event: 'ipc-message', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + addListener(event: 'ipc-message', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + removeListener(event: 'ipc-message', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + /** + * Emitted when the renderer process sends a synchronous message via + * ipcRenderer.sendSync(). + */ + on(event: 'ipc-message-sync', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + once(event: 'ipc-message-sync', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + addListener(event: 'ipc-message-sync', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + removeListener(event: 'ipc-message-sync', listener: (event: Event, + channel: string, + ...args: any[]) => void): this; + /** + * Emitted when the window leaves a full-screen state triggered by HTML API. + */ + on(event: 'leave-html-full-screen', listener: Function): this; + once(event: 'leave-html-full-screen', listener: Function): this; + addListener(event: 'leave-html-full-screen', listener: Function): this; + removeListener(event: 'leave-html-full-screen', listener: Function): this; /** * Emitted when webContents wants to do basic auth. The usage is the same with the * login event of app. @@ -5941,6 +6463,21 @@ declare namespace Electron { removeListener(event: 'plugin-crashed', listener: (event: Event, name: string, version: string) => void): this; + /** + * Emitted when the preload script preloadPath throws an unhandled exception error. + */ + on(event: 'preload-error', listener: (event: Event, + preloadPath: string, + error: Error) => void): this; + once(event: 'preload-error', listener: (event: Event, + preloadPath: string, + error: Error) => void): this; + addListener(event: 'preload-error', listener: (event: Event, + preloadPath: string, + error: Error) => void): this; + removeListener(event: 'preload-error', listener: (event: Event, + preloadPath: string, + error: Error) => void): this; /** * Emitted when remote.getBuiltin() is called in the renderer process. Calling * event.preventDefault() will prevent the module from being returned. Custom value @@ -6205,16 +6742,23 @@ declare namespace Electron { canGoBack(): boolean; canGoForward(): boolean; canGoToOffset(offset: number): boolean; + /** + * Captures a snapshot of the page within rect. Omitting rect will capture the + * whole visible page. + */ + capturePage(rect?: Rectangle): Promise; /** * Captures a snapshot of the page within rect. Upon completion callback will be * called with callback(image). The image is an instance of NativeImage that stores * data of the snapshot. Omitting rect will capture the whole visible page. + * Deprecated Soon */ capturePage(rect: Rectangle, callback: (image: NativeImage) => void): void; /** * Captures a snapshot of the page within rect. Upon completion callback will be * called with callback(image). The image is an instance of NativeImage that stores * data of the snapshot. Omitting rect will capture the whole visible page. + * Deprecated Soon */ capturePage(callback: (image: NativeImage) => void): void; /** @@ -6261,12 +6805,15 @@ declare namespace Electron { /** * Evaluates code in page. In the browser window some HTML APIs like * requestFullScreen can only be invoked by a gesture from the user. Setting - * userGesture to true will remove this limitation. If the result of the executed - * code is a promise the callback result will be the resolved value of the promise. - * We recommend that you use the returned Promise to handle code that results in a - * Promise. + * userGesture to true will remove this limitation. Deprecated Soon */ executeJavaScript(code: string, userGesture?: boolean, callback?: (result: any) => void): Promise; + /** + * Evaluates code in page. In the browser window some HTML APIs like + * requestFullScreen can only be invoked by a gesture from the user. Setting + * userGesture to true will remove this limitation. + */ + executeJavaScript(code: string, userGesture?: boolean): Promise; /** * Starts a request to find all matches for the text in the web page. The result of * the request can be obtained by subscribing to found-in-page event. @@ -6288,16 +6835,8 @@ declare namespace Electron { getURL(): string; getUserAgent(): string; getWebRTCIPHandlingPolicy(): string; - /** - * Sends a request to get current zoom factor, the callback will be called with - * callback(zoomFactor). - */ - getZoomFactor(callback: (zoomFactor: number) => void): void; - /** - * Sends a request to get current zoom level, the callback will be called with - * callback(zoomLevel). - */ - getZoomLevel(callback: (zoomLevel: number) => void): void; + getZoomFactor(): number; + getZoomLevel(): number; /** * Makes the browser go back a web page. */ @@ -6314,11 +6853,6 @@ declare namespace Electron { * Navigates to the specified offset from the "current entry". */ goToOffset(offset: number): void; - /** - * Checks if any ServiceWorker is registered and returns a boolean as response to - * callback. - */ - hasServiceWorker(callback: (hasWorker: boolean) => void): void; /** * Injects CSS into the current web page. */ @@ -6335,6 +6869,10 @@ declare namespace Electron { * Opens the developer tools for the service worker context. */ inspectServiceWorker(): void; + /** + * Opens the developer tools for the shared worker context. + */ + inspectSharedWorker(): void; /** * Schedules a full repaint of the window this web contents is in. If offscreen * rendering is enabled invalidates the frame and generates a new one through the @@ -6358,13 +6896,13 @@ declare namespace Electron { * relative to the root of your application. For instance an app structure like * this: Would require code like this */ - loadFile(filePath: string, options?: LoadFileOptions): void; + loadFile(filePath: string, options?: LoadFileOptions): Promise; /** * Loads the url in the window. The url must contain the protocol prefix, e.g. the * http:// or file://. If the load should bypass http cache then use the pragma * header to achieve it. */ - loadURL(url: string, options?: LoadURLOptions): void; + loadURL(url: string, options?: LoadURLOptions): Promise; /** * Opens the devtools. When contents is a tag, the mode would be detach * by default, explicitly passing an empty mode can force using last used dock @@ -6387,13 +6925,18 @@ declare namespace Electron { * Use page-break-before: always; CSS style to force to print to a new page. */ print(options?: PrintOptions, callback?: (success: boolean) => void): void; + /** + * Prints window's web page as PDF with Chromium's preview printing custom + * settings. The landscape will be ignored if @page CSS at-rule is used in the web + * page. By default, an empty options will be regarded as: Use page-break-before: + * always; CSS style to force to print to a new page. An example of + * webContents.printToPDF: + */ + printToPDF(options: PrintToPDFOptions): Promise; /** * Prints window's web page as PDF with Chromium's preview printing custom * settings. The callback will be called with callback(error, data) on completion. - * The data is a Buffer that contains the generated PDF data. The landscape will be - * ignored if @page CSS at-rule is used in the web page. By default, an empty - * options will be regarded as: Use page-break-before: always; CSS style to force - * to print to a new page. An example of webContents.printToPDF: + * The data is a Buffer that contains the generated PDF data. Deprecated Soon */ printToPDF(options: PrintToPDFOptions, callback: (error: Error, data: Buffer) => void): void; /** @@ -6420,7 +6963,7 @@ declare namespace Electron { * Executes the editing command replaceMisspelling in web page. */ replaceMisspelling(text: string): void; - savePage(fullPath: string, saveType: 'HTMLOnly' | 'HTMLComplete' | 'MHTML', callback: (error: Error) => void): boolean; + savePage(fullPath: string, saveType: 'HTMLOnly' | 'HTMLComplete' | 'MHTML'): Promise; /** * Executes the editing command selectAll in web page. */ @@ -6441,6 +6984,16 @@ declare namespace Electron { * object also have following properties: */ sendInputEvent(event: Event): void; + /** + * Send an asynchronous message to a specific frame in a renderer process via + * channel. Arguments will be serialized as JSON internally and as such no + * functions or prototype chains will be included. The renderer process can handle + * the message by listening to channel with the ipcRenderer module. If you want to + * get the frameId of a given renderer context you should use the + * webFrame.routingId value. E.g. You can also read frameId from all incoming IPC + * messages in the main process. + */ + sendToFrame(frameId: number, channel: string, ...args: any[]): void; /** * Mute the audio on the current web page. */ @@ -6539,12 +7092,6 @@ declare namespace Electron { * Executes the editing command undo in web page. */ undo(): void; - /** - * Unregisters any ServiceWorker if present and returns a boolean as response to - * callback when the JS promise is fulfilled or false when the JS promise is - * rejected. - */ - unregisterServiceWorker(callback: (success: boolean) => void): void; /** * Executes the editing command unselect in web page. */ @@ -6574,11 +7121,22 @@ declare namespace Electron { * requestFullScreen can only be invoked by a gesture from the user. Setting * userGesture to true will remove this limitation. */ + executeJavaScript(code: string, userGesture?: boolean): Promise; + /** + * Evaluates code in page. In the browser window some HTML APIs like + * requestFullScreen can only be invoked by a gesture from the user. Setting + * userGesture to true will remove this limitation. Deprecated Soon + */ executeJavaScript(code: string, userGesture?: boolean, callback?: (result: any) => void): Promise; /** - * Work like executeJavaScript but evaluates scripts in an isolated context. + * Works like executeJavaScript but evaluates scripts in an isolated context. + * Deprecated Soon */ - executeJavaScriptInIsolatedWorld(worldId: number, scripts: WebSource[], userGesture?: boolean, callback?: (result: any) => void): void; + executeJavaScriptInIsolatedWorld(worldId: number, scripts: WebSource[], userGesture?: boolean, callback?: (result: any) => void): Promise; + /** + * Works like executeJavaScript but evaluates scripts in an isolated context. + */ + executeJavaScriptInIsolatedWorld(worldId: number, scripts: WebSource[], userGesture?: boolean): Promise; findFrameByName(name: string): WebFrame; findFrameByRoutingId(routingId: number): WebFrame; getFrameForSelector(selector: string): WebFrame; @@ -6589,22 +7147,14 @@ declare namespace Electron { getResourceUsage(): ResourceUsage; getZoomFactor(): number; getZoomLevel(): number; + /** + * Inserts css as a style sheet in the document. + */ + insertCSS(css: string): void; /** * Inserts text to the focused element. */ insertText(text: string): void; - /** - * Resources will be loaded from this scheme regardless of the current page's - * Content Security Policy. - */ - registerURLSchemeAsBypassingCSP(scheme: string): void; - /** - * Registers the scheme as secure, bypasses content security policy for resources, - * allows registering ServiceWorker and supports fetch API. Specify an option with - * the value of false to omit it from the registration. An example of registering a - * privileged scheme, without bypassing Content Security Policy: - */ - registerURLSchemeAsPrivileged(scheme: string, options?: RegisterURLSchemeAsPrivilegedOptions): void; /** * Set the content security policy of the isolated world. */ @@ -6613,6 +7163,11 @@ declare namespace Electron { * Set the name of the isolated world. Useful in devtools. */ setIsolatedWorldHumanReadableName(worldId: number, name: string): void; + /** + * Set the security origin, content security policy and name of the isolated world. + * Note: If the csp is specified, then the securityOrigin also has to be specified. + */ + setIsolatedWorldInfo(worldId: number, info: Info): void; /** * Set the security origin of the isolated world. */ @@ -6623,10 +7178,12 @@ declare namespace Electron { setLayoutZoomLevelLimits(minimumLevel: number, maximumLevel: number): void; /** * Sets a provider for spell checking in input fields and text areas. The provider - * must be an object that has a spellCheck method that returns whether the word - * passed is correctly spelled. An example of using node-spellchecker as provider: + * must be an object that has a spellCheck method that accepts an array of + * individual words for spellchecking. The spellCheck function runs asynchronously + * and calls the callback function with an array of misspelt words when complete. + * An example of using node-spellchecker as provider: */ - setSpellCheckProvider(language: string, autoCorrectWord: boolean, provider: Provider): void; + setSpellCheckProvider(language: string, provider: Provider): void; /** * Sets the maximum and minimum pinch-to-zoom level. */ @@ -6685,90 +7242,90 @@ declare namespace Electron { * The listener will be called with listener(details) when a server initiated * redirect is about to occur. */ - onBeforeRedirect(listener: (details: OnBeforeRedirectDetails) => void): void; + onBeforeRedirect(listener: ((details: OnBeforeRedirectDetails) => void) | (null)): void; /** * The listener will be called with listener(details) when a server initiated * redirect is about to occur. */ - onBeforeRedirect(filter: OnBeforeRedirectFilter, listener: (details: OnBeforeRedirectDetails) => void): void; + onBeforeRedirect(filter: OnBeforeRedirectFilter, listener: ((details: OnBeforeRedirectDetails) => void) | (null)): void; /** * The listener will be called with listener(details, callback) when a request is * about to occur. The uploadData is an array of UploadData objects. The callback - * has to be called with an response object. + * has to be called with an response object. Some examples of valid urls: */ - onBeforeRequest(listener: (details: OnBeforeRequestDetails, callback: (response: Response) => void) => void): void; + onBeforeRequest(listener: ((details: OnBeforeRequestDetails, callback: (response: Response) => void) => void) | (null)): void; /** * The listener will be called with listener(details, callback) when a request is * about to occur. The uploadData is an array of UploadData objects. The callback - * has to be called with an response object. + * has to be called with an response object. Some examples of valid urls: */ - onBeforeRequest(filter: OnBeforeRequestFilter, listener: (details: OnBeforeRequestDetails, callback: (response: Response) => void) => void): void; + onBeforeRequest(filter: OnBeforeRequestFilter, listener: ((details: OnBeforeRequestDetails, callback: (response: Response) => void) => void) | (null)): void; /** * The listener will be called with listener(details, callback) before sending an * HTTP request, once the request headers are available. This may occur after a TCP * connection is made to the server, but before any http data is sent. The callback * has to be called with an response object. */ - onBeforeSendHeaders(filter: OnBeforeSendHeadersFilter, listener: (details: OnBeforeSendHeadersDetails, callback: (response: OnBeforeSendHeadersResponse) => void) => void): void; + onBeforeSendHeaders(filter: OnBeforeSendHeadersFilter, listener: ((details: OnBeforeSendHeadersDetails, callback: (response: OnBeforeSendHeadersResponse) => void) => void) | (null)): void; /** * The listener will be called with listener(details, callback) before sending an * HTTP request, once the request headers are available. This may occur after a TCP * connection is made to the server, but before any http data is sent. The callback * has to be called with an response object. */ - onBeforeSendHeaders(listener: (details: OnBeforeSendHeadersDetails, callback: (response: OnBeforeSendHeadersResponse) => void) => void): void; + onBeforeSendHeaders(listener: ((details: OnBeforeSendHeadersDetails, callback: (response: OnBeforeSendHeadersResponse) => void) => void) | (null)): void; /** * The listener will be called with listener(details) when a request is completed. */ - onCompleted(filter: OnCompletedFilter, listener: (details: OnCompletedDetails) => void): void; + onCompleted(filter: OnCompletedFilter, listener: ((details: OnCompletedDetails) => void) | (null)): void; /** * The listener will be called with listener(details) when a request is completed. */ - onCompleted(listener: (details: OnCompletedDetails) => void): void; + onCompleted(listener: ((details: OnCompletedDetails) => void) | (null)): void; /** * The listener will be called with listener(details) when an error occurs. */ - onErrorOccurred(listener: (details: OnErrorOccurredDetails) => void): void; + onErrorOccurred(listener: ((details: OnErrorOccurredDetails) => void) | (null)): void; /** * The listener will be called with listener(details) when an error occurs. */ - onErrorOccurred(filter: OnErrorOccurredFilter, listener: (details: OnErrorOccurredDetails) => void): void; + onErrorOccurred(filter: OnErrorOccurredFilter, listener: ((details: OnErrorOccurredDetails) => void) | (null)): void; /** * The listener will be called with listener(details, callback) when HTTP response * headers of a request have been received. The callback has to be called with an * response object. */ - onHeadersReceived(filter: OnHeadersReceivedFilter, listener: (details: OnHeadersReceivedDetails, callback: (response: OnHeadersReceivedResponse) => void) => void): void; + onHeadersReceived(filter: OnHeadersReceivedFilter, listener: ((details: OnHeadersReceivedDetails, callback: (response: OnHeadersReceivedResponse) => void) => void) | (null)): void; /** * The listener will be called with listener(details, callback) when HTTP response * headers of a request have been received. The callback has to be called with an * response object. */ - onHeadersReceived(listener: (details: OnHeadersReceivedDetails, callback: (response: OnHeadersReceivedResponse) => void) => void): void; + onHeadersReceived(listener: ((details: OnHeadersReceivedDetails, callback: (response: OnHeadersReceivedResponse) => void) => void) | (null)): void; /** * The listener will be called with listener(details) when first byte of the * response body is received. For HTTP requests, this means that the status line * and response headers are available. */ - onResponseStarted(listener: (details: OnResponseStartedDetails) => void): void; + onResponseStarted(listener: ((details: OnResponseStartedDetails) => void) | (null)): void; /** * The listener will be called with listener(details) when first byte of the * response body is received. For HTTP requests, this means that the status line * and response headers are available. */ - onResponseStarted(filter: OnResponseStartedFilter, listener: (details: OnResponseStartedDetails) => void): void; + onResponseStarted(filter: OnResponseStartedFilter, listener: ((details: OnResponseStartedDetails) => void) | (null)): void; /** * The listener will be called with listener(details) just before a request is * going to be sent to the server, modifications of previous onBeforeSendHeaders * response are visible by the time this listener is fired. */ - onSendHeaders(filter: OnSendHeadersFilter, listener: (details: OnSendHeadersDetails) => void): void; + onSendHeaders(filter: OnSendHeadersFilter, listener: ((details: OnSendHeadersDetails) => void) | (null)): void; /** * The listener will be called with listener(details) just before a request is * going to be sent to the server, modifications of previous onBeforeSendHeaders * response are visible by the time this listener is fired. */ - onSendHeaders(listener: (details: OnSendHeadersDetails) => void): void; + onSendHeaders(listener: ((details: OnSendHeadersDetails) => void) | (null)): void; } interface WebSource { @@ -6963,15 +7520,24 @@ declare namespace Electron { canGoForward(): boolean; canGoToOffset(offset: number): boolean; /** - * Captures a snapshot of the webview's page. Same as - * webContents.capturePage([rect, ]callback). + * Captures a snapshot of the page within rect. Upon completion callback will be + * called with callback(image). The image is an instance of NativeImage that stores + * data of the snapshot. Omitting rect will capture the whole visible page. + * Deprecated Soon */ capturePage(callback: (image: NativeImage) => void): void; /** - * Captures a snapshot of the webview's page. Same as - * webContents.capturePage([rect, ]callback). + * Captures a snapshot of the page within rect. Upon completion callback will be + * called with callback(image). The image is an instance of NativeImage that stores + * data of the snapshot. Omitting rect will capture the whole visible page. + * Deprecated Soon */ capturePage(rect: Rectangle, callback: (image: NativeImage) => void): void; + /** + * Captures a snapshot of the page within rect. Omitting rect will capture the + * whole visible page. + */ + capturePage(rect?: Rectangle): Promise; /** * Clears the navigation history. */ @@ -6996,12 +7562,18 @@ declare namespace Electron { * Initiates a download of the resource at url without navigating. */ downloadURL(url: string): void; + /** + * Evaluates code in page. If userGesture is set, it will create the user gesture + * context in the page. HTML APIs like requestFullScreen, which require user + * action, can take advantage of this option for automation. Deprecated Soon + */ + executeJavaScript(code: string, userGesture?: boolean, callback?: (result: any) => void): Promise; /** * Evaluates code in page. If userGesture is set, it will create the user gesture * context in the page. HTML APIs like requestFullScreen, which require user * action, can take advantage of this option for automation. */ - executeJavaScript(code: string, userGesture?: boolean, callback?: (result: any) => void): void; + executeJavaScript(code: string, userGesture?: boolean): Promise; /** * Starts a request to find all matches for the text in the web page. The result of * the request can be obtained by subscribing to found-in-page event. @@ -7015,16 +7587,9 @@ declare namespace Electron { * is disabled. */ getWebContents(): WebContents; - /** - * Sends a request to get current zoom factor, the callback will be called with - * callback(zoomFactor). - */ - getZoomFactor(callback: (zoomFactor: number) => void): void; - /** - * Sends a request to get current zoom level, the callback will be called with - * callback(zoomLevel). - */ - getZoomLevel(callback: (zoomLevel: number) => void): void; + getWebContentsId(): number; + getZoomFactor(): number; + getZoomLevel(): number; /** * Makes the guest page go back. */ @@ -7057,6 +7622,10 @@ declare namespace Electron { * Opens the DevTools for the service worker context present in the guest page. */ inspectServiceWorker(): void; + /** + * Opens the DevTools for the shared worker context present in the guest page. + */ + inspectSharedWorker(): void; isAudioMuted(): boolean; isCrashed(): boolean; isCurrentlyAudible(): boolean; @@ -7069,7 +7638,7 @@ declare namespace Electron { * Loads the url in the webview, the url must contain the protocol prefix, e.g. the * http:// or file://. */ - loadURL(url: string, options?: LoadURLOptions): void; + loadURL(url: string, options?: LoadURLOptions): Promise; /** * Opens a DevTools window for guest page. */ @@ -7088,9 +7657,13 @@ declare namespace Electron { print(options?: PrintOptions): void; /** * Prints webview's web page as PDF, Same as webContents.printToPDF(options, - * callback). + * callback). Deprecated Soon */ printToPDF(options: PrintToPDFOptions, callback: (error: Error, data: Buffer) => void): void; + /** + * Prints webview's web page as PDF, Same as webContents.printToPDF(options). + */ + printToPDF(options: PrintToPDFOptions): Promise; /** * Executes editing command redo in page. */ @@ -7180,14 +7753,6 @@ declare namespace Electron { * windows. Popups are disabled by default. */ // allowpopups?: string; ### VSCODE CHANGE (https://github.com/electron/electron/blob/master/docs/tutorial/security.md) ### - /** - * When this attribute is present the webview container will automatically resize - * within the bounds specified by the attributes minwidth, minheight, maxwidth, and - * maxheight. These constraints do not impact the webview unless autosize is - * enabled. When autosize is enabled, the webview container size cannot be less - * than the minimum values or greater than the maximum. - */ - autosize?: string; /** * A list of strings which specifies the blink features to be disabled separated by * ,. The full list of supported feature strings can be found in the @@ -7207,7 +7772,7 @@ declare namespace Electron { enableblinkfeatures?: string; /** * When this attribute is false the guest page in webview will not have access to - * the remote module. The remote module is avaiable by default. + * the remote module. The remote module is available by default. */ enableremotemodule?: string; /** @@ -7220,6 +7785,13 @@ declare namespace Electron { * system resources. Node integration is disabled by default in the guest page. */ nodeintegration?: string; + /** + * Experimental option for enabling NodeJS support in sub-frames such as iframes + * inside the webview. All your preloads will load for every iframe, you can use + * process.isMainFrame to determine if you are in the main frame or not. This + * option is disabled by default in the guest page. + */ + nodeintegrationinsubframes?: string; /** * Sets the session used by the page. If partition starts with persist:, the page * will use a persistent session available to all pages in the app with the same @@ -7281,14 +7853,23 @@ declare namespace Electron { * Copyright information. */ copyright?: string; + /** + * The app's build version number. + */ + version?: string; /** * Credit information. */ credits?: string; /** - * The app's build version number. + * The app's website. */ - version?: string; + website?: string; + /** + * Path to the app's icon. Will be shown as 64x64 pixels while retaining aspect + * ratio. + */ + iconPath?: string; } interface AddRepresentationOptions { @@ -7314,6 +7895,24 @@ declare namespace Electron { dataURL?: string; } + interface AnimationSettings { + /** + * Returns true if rich animations should be rendered. Looks at session type (e.g. + * remote desktop) and accessibility settings to give guidance for heavy + * animations. + */ + shouldRenderRichAnimation: boolean; + /** + * Determines on a per-platform basis whether scroll animations (e.g. produced by + * home/end key) should be enabled. + */ + scrollAnimationsEnabledBySystem: boolean; + /** + * Determines whether the user desires reduced motion based on platform APIs. + */ + prefersReducedMotion: boolean; + } + interface AppDetailsOptions { /** * Window's . It has to be set, otherwise the other options will have no effect. @@ -7357,6 +7956,16 @@ declare namespace Electron { * by default. */ height: boolean; + /** + * If true, the view's x position and width will grow and shrink proportionly with + * the window. false by default. + */ + horizontal: boolean; + /** + * If true, the view's y position and height will grow and shrink proportinaly with + * the window. false by default. + */ + vertical: boolean; } interface BitmapOptions { @@ -7475,7 +8084,9 @@ declare namespace Electron { */ kiosk?: boolean; /** - * Default window title. Default is "Electron". + * Default window title. Default is "Electron". If the HTML tag is defined + * in the HTML file loaded by loadURL(), this property will be + * ignored. */ title?: string; /** @@ -7520,8 +8131,8 @@ declare namespace Electron { enableLargerThanScreen?: boolean; /** * Window's background color as a hexadecimal value, like #66CD00 or #FFF or - * #80FFFFFF (alpha is supported if transparent is set to true). Default is #FFF - * (white). + * #80FFFFFF (alpha in #AARRGGBB format is supported if transparent is set to + * true). Default is #FFF (white). */ backgroundColor?: string; /** @@ -7633,15 +8244,24 @@ declare namespace Electron { interface CommandLine { /** * Append a switch (with optional value) to Chromium's command line. Note: This - * will not affect process.argv, and is mainly used by developers to control some - * low-level Chromium behaviors. + * will not affect process.argv. The intended usage of this function is to control + * Chromium's behavior. */ appendSwitch: (the_switch: string, value?: string) => void; /** * Append an argument to Chromium's command line. The argument will be quoted - * correctly. Note: This will not affect process.argv. + * correctly. Switches will precede arguments regardless of appending order. If + * you're appending an argument like --switch=value, consider using + * appendSwitch('switch', 'value') instead. Note: This will not affect + * process.argv. The intended usage of this function is to control Chromium's + * behavior. */ appendArgument: (value: string) => void; + hasSwitch: (the_switch: string) => boolean; + /** + * Note: When the switch is not present or has no value, it returns empty string. + */ + getSwitchValue: (the_switch: string) => string; } interface Config { @@ -7778,6 +8398,15 @@ declare namespace Electron { crashesDirectory?: string; } + interface CreateFromBitmapOptions { + width: number; + height: number; + /** + * Defaults to 1.0. + */ + scaleFactor?: number; + } + interface CreateFromBufferOptions { /** * Required for bitmap buffers. @@ -7850,8 +8479,7 @@ declare namespace Electron { */ value?: string; /** - * The domain of the cookie; this will be normalized with a preceding dot so that - * it's also valid for subdomains. Empty by default if omitted. + * The domain of the cookie. Empty by default if omitted. */ domain?: string; /** @@ -7935,15 +8563,13 @@ declare namespace Electron { * Hides the dock icon. */ hide: () => void; - /** - * Shows the dock icon. - */ - show: () => void; + show: () => Promise; isVisible: () => boolean; /** * Sets the application's dock menu. */ setMenu: (menu: Menu) => void; + getMenu: () => (Menu) | (null); /** * Sets the image associated with this dock icon. */ @@ -8094,6 +8720,21 @@ declare namespace Electron { password: string; } + interface Info { + /** + * Security origin for the isolated world. + */ + securityOrigin?: string; + /** + * Content Security Policy for the isolated world. + */ + csp?: string; + /** + * Name for isolated world. Useful in devtools. + */ + name?: string; + } + interface Input { /** * Either keyUp or keyDown. @@ -8288,12 +8929,18 @@ declare namespace Electron { * Will be called with click(menuItem, browserWindow, event) when the menu item is * clicked. */ - click?: (menuItem: MenuItem, browserWindow: BrowserWindow, event: Event) => void; + click?: (menuItem: MenuItem, browserWindow: BrowserWindow, event: KeyboardEvent) => void; /** - * Define the action of the menu item, when specified the click property will be - * ignored. See . + * Can be undo, redo, cut, copy, paste, pasteAndMatchStyle, delete, selectAll, + * reload, forceReload, toggleDevTools, resetZoom, zoomIn, zoomOut, + * togglefullscreen, window, minimize, close, help, about, services, hide, + * hideOthers, unhide, quit, startSpeaking, stopSpeaking, close, minimize, zoom, + * front, appMenu, fileMenu, editMenu, viewMenu, recentDocuments, toggleTabBar, + * selectNextTab, selectPreviousTab, mergeAllWindows, clearRecentDocuments, + * moveTabToNewWindow or windowMenu Define the action of the menu item, when + * specified the click property will be ignored. See . */ - role?: string; + role?: ('undo' | 'redo' | 'cut' | 'copy' | 'paste' | 'pasteAndMatchStyle' | 'delete' | 'selectAll' | 'reload' | 'forceReload' | 'toggleDevTools' | 'resetZoom' | 'zoomIn' | 'zoomOut' | 'togglefullscreen' | 'window' | 'minimize' | 'close' | 'help' | 'about' | 'services' | 'hide' | 'hideOthers' | 'unhide' | 'quit' | 'startSpeaking' | 'stopSpeaking' | 'close' | 'minimize' | 'zoom' | 'front' | 'appMenu' | 'fileMenu' | 'editMenu' | 'viewMenu' | 'recentDocuments' | 'toggleTabBar' | 'selectNextTab' | 'selectPreviousTab' | 'mergeAllWindows' | 'clearRecentDocuments' | 'moveTabToNewWindow' | 'windowMenu'); /** * Can be normal, separator, submenu, checkbox or radio. */ @@ -8306,6 +8953,11 @@ declare namespace Electron { * If false, the menu item will be greyed out and unclickable. */ enabled?: boolean; + /** + * default is true, and when false will prevent the accelerator from triggering the + * item if the item is not visible`. + */ + acceleratorWorksWhenHidden?: boolean; /** * If false, the menu item will be entirely hidden. */ @@ -8421,6 +9073,82 @@ declare namespace Electron { normalizeAccessKeys?: boolean; } + interface MessageBoxReturnValue { + /** + * The index of the clicked button. + */ + response: number; + /** + * The checked state of the checkbox if checkboxLabel was set. Otherwise false. + */ + checkboxChecked: boolean; + } + + interface MessageBoxSyncOptions { + /** + * Can be "none", "info", "error", "question" or "warning". On Windows, "question" + * displays the same icon as "info", unless you set an icon using the "icon" + * option. On macOS, both "warning" and "error" display the same warning icon. + */ + type?: string; + /** + * Array of texts for buttons. On Windows, an empty array will result in one button + * labeled "OK". + */ + buttons?: string[]; + /** + * Index of the button in the buttons array which will be selected by default when + * the message box opens. + */ + defaultId?: number; + /** + * Title of the message box, some platforms will not show it. + */ + title?: string; + /** + * Content of the message box. + */ + message: string; + /** + * Extra information of the message. + */ + detail?: string; + /** + * If provided, the message box will include a checkbox with the given label. The + * checkbox state can be inspected only when using callback. + */ + checkboxLabel?: string; + /** + * Initial checked state of the checkbox. false by default. + */ + checkboxChecked?: boolean; + icon?: (NativeImage) | (string); + /** + * The index of the button to be used to cancel the dialog, via the Esc key. By + * default this is assigned to the first button with "cancel" or "no" as the label. + * If no such labeled buttons exist and this option is not set, 0 will be used as + * the return value or callback response. + */ + cancelId?: number; + /** + * On Windows Electron will try to figure out which one of the buttons are common + * buttons (like "Cancel" or "Yes"), and show the others as command links in the + * dialog. This can make the dialog appear in the style of modern Windows apps. If + * you don't like this behavior, you can set noLink to true. + */ + noLink?: boolean; + /** + * Normalize the keyboard access keys across platforms. Default is false. Enabling + * this assumes & is used in the button labels for the placement of the keyboard + * shortcut access key and labels will be converted so they work correctly on each + * platform, & characters are removed on macOS, converted to _ on Linux, and left + * untouched on Windows. For example, a button label of Vie&w will be converted to + * Vie_w on Linux and View on macOS and can be selected via Alt-W on Windows and + * Linux. + */ + normalizeAccessKeys?: boolean; + } + interface NewWindowEvent extends Event { url: string; frameName: string; @@ -8488,6 +9216,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; redirectURL: string; statusCode: number; @@ -8513,6 +9242,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; uploadData: UploadData[]; } @@ -8531,6 +9261,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; requestHeaders: RequestHeaders; } @@ -8579,6 +9310,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; fromCache: boolean; /** @@ -8601,6 +9333,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; statusLine: string; statusCode: number; @@ -8634,6 +9367,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; responseHeaders: ResponseHeaders; /** @@ -8658,6 +9392,7 @@ declare namespace Electron { method: string; webContentsId?: number; resourceType: string; + referrer: string; timestamp: number; requestHeaders: RequestHeaders; } @@ -8677,6 +9412,11 @@ declare namespace Electron { * back. In detach mode it's not. */ mode: ('right' | 'bottom' | 'undocked' | 'detach'); + /** + * Whether to bring the opened devtools window to the foreground. The default is + * true. + */ + activate?: boolean; } interface OpenDialogOptions { @@ -8703,6 +9443,44 @@ declare namespace Electron { securityScopedBookmarks?: boolean; } + interface OpenDialogReturnValue { + /** + * An array of file paths chosen by the user. If the dialog is cancelled this will + * be an empty array. + */ + filePaths?: string[]; + /** + * An array matching the filePaths array of base64 encoded strings which contains + * security scoped bookmark data. securityScopedBookmarks must be enabled for this + * to be populated. + */ + bookmarks?: string[]; + } + + interface OpenDialogSyncOptions { + title?: string; + defaultPath?: string; + /** + * Custom label for the confirmation button, when left empty the default label will + * be used. + */ + buttonLabel?: string; + filters?: FileFilter[]; + /** + * Contains which features the dialog should use. The following values are + * supported: + */ + properties?: Array<'openFile' | 'openDirectory' | 'multiSelections' | 'showHiddenFiles' | 'createDirectory' | 'promptToCreate' | 'noResolveAliases' | 'treatPackageAsDirectory'>; + /** + * Message to display above input boxes. + */ + message?: string; + /** + * Create when packaged for the Mac App Store. + */ + securityScopedBookmarks?: boolean; + } + interface OpenExternalOptions { /** * true to bring the opened application to the foreground. The default is true. @@ -8714,6 +9492,17 @@ declare namespace Electron { workingDirectory?: string; } + interface OpenExternalSyncOptions { + /** + * true to bring the opened application to the foreground. The default is true. + */ + activate?: boolean; + /** + * The working directory. + */ + workingDirectory?: string; + } + interface PageFaviconUpdatedEvent extends Event { /** * Array of URLs. @@ -8874,21 +9663,31 @@ declare namespace Electron { landscape?: boolean; } - interface ProcessMemoryInfo { + interface Privileges { /** - * and The amount of memory currently pinned to actual physical RAM in Kilobytes. + * Default false. */ - residentSet: number; + standard?: boolean; /** - * The amount of memory not shared by other processes, such as JS heap or HTML - * content in Kilobytes. + * Default false. */ - private: number; + secure?: boolean; /** - * The amount of memory shared between processes, typically memory consumed by the - * Electron code itself in Kilobytes. + * Default false. */ - shared: number; + bypassCSP?: boolean; + /** + * Default false. + */ + allowServiceWorkers?: boolean; + /** + * Default false. + */ + supportFetchAPI?: boolean; + /** + * Default false. + */ + corsEnabled?: boolean; } interface ProgressBarOptions { @@ -8900,9 +9699,9 @@ declare namespace Electron { interface Provider { /** - * Returns Boolean. + * . */ - spellCheck: (text: string) => void; + spellCheck: (words: string[], callback: (misspeltWords: string[]) => void) => void; } interface ReadBookmark { @@ -8939,13 +9738,6 @@ declare namespace Electron { uploadData: UploadData[]; } - interface RegisterStandardSchemesOptions { - /** - * true to register the scheme as secure. Default false. - */ - secure?: boolean; - } - interface RegisterStreamProtocolRequest { url: string; headers: Headers; @@ -8961,29 +9753,6 @@ declare namespace Electron { uploadData: UploadData[]; } - interface RegisterURLSchemeAsPrivilegedOptions { - /** - * Default true. - */ - secure?: boolean; - /** - * Default true. - */ - bypassCSP?: boolean; - /** - * Default true. - */ - allowServiceWorkers?: boolean; - /** - * Default true. - */ - supportFetchAPI?: boolean; - /** - * Default true. - */ - corsEnabled?: boolean; - } - interface RelaunchOptions { args?: string[]; execPath?: string; @@ -9080,6 +9849,53 @@ declare namespace Electron { securityScopedBookmarks?: boolean; } + interface SaveDialogReturnValue { + /** + * whether or not the dialog was canceled. + */ + canceled: boolean; + /** + * If the dialog is canceled this will be undefined. + */ + filePath?: string; + /** + * Base64 encoded string which contains the security scoped bookmark data for the + * saved file. securityScopedBookmarks must be enabled for this to be present. + */ + bookmark?: string; + } + + interface SaveDialogSyncOptions { + title?: string; + /** + * Absolute directory path, absolute file path, or file name to use by default. + */ + defaultPath?: string; + /** + * Custom label for the confirmation button, when left empty the default label will + * be used. + */ + buttonLabel?: string; + filters?: FileFilter[]; + /** + * Message to display above text fields. + */ + message?: string; + /** + * Custom label for the text displayed in front of the filename text field. + */ + nameFieldLabel?: string; + /** + * Show the tags input box, defaults to true. + */ + showsTagField?: boolean; + /** + * Create a when packaged for the Mac App Store. If this option is enabled and the + * file doesn't already exist a blank file will be created at the chosen path. + */ + securityScopedBookmarks?: boolean; + } + interface Settings { /** * true to open the app at login, false to remove the app as a login item. Defaults @@ -9112,14 +9928,17 @@ declare namespace Electron { types: string[]; /** * The size that the media source thumbnail should be scaled to. Default is 150 x - * 150. + * 150. Set width or height to 0 when you do not need the thumbnails. This will + * save the processing time required for capturing the content of each window and + * screen. */ thumbnailSize?: Size; - } - - interface StartMonitoringOptions { - categoryFilter: string; - traceOptions: string; + /** + * Set to true to enable fetching window icons. The default value is false. When + * false the appIcon property of the sources return null. Same if a source has the + * type screen. + */ + fetchWindowIcons?: boolean; } interface SystemMemoryInfo { @@ -9487,7 +10306,7 @@ declare namespace Electron { */ devTools?: boolean; /** - * Whether node integration is enabled. Default is true. + * Whether node integration is enabled. Default is false. */ nodeIntegration?: boolean; /** @@ -9495,6 +10314,12 @@ declare namespace Electron { * this can be found in . */ nodeIntegrationInWorker?: boolean; + /** + * Experimental option for enabling Node.js support in sub-frames such as iframes + * and child windows. All your preloads will load for every iframe, you can use + * process.isMainFrame to determine if you are in the main frame or not. + */ + nodeIntegrationInSubFrames?: boolean; /** * Specifies a script that will be loaded before other scripts run in the page. * This script will always have access to node APIs no matter whether node @@ -9571,10 +10396,6 @@ declare namespace Electron { * Enables WebGL support. Default is true. */ webgl?: boolean; - /** - * Enables WebAudio support. Default is true. - */ - webaudio?: boolean; /** * Whether plugins should be enabled. Default is false. */ @@ -9642,19 +10463,17 @@ declare namespace Electron { */ contextIsolation?: boolean; /** - * Whether to use native window.open(). If set to true, the webPreferences of child - * window will always be the same with parent window, regardless of the parameters - * passed to window.open(). Defaults to false. This option is currently - * experimental. + * Whether to use native window.open(). Defaults to false. Child windows will + * always have node integration disabled unless nodeIntegrationInSubFrames is true. + * This option is currently experimental. */ nativeWindowOpen?: boolean; /** - * Whether to enable the . Defaults to the value of the nodeIntegration option. The - * preload script configured for the will have node integration enabled when it is - * executed so you should ensure remote/untrusted content is not able to create a - * tag with a possibly malicious preload script. You can use the - * will-attach-webview event on to strip away the preload script and to validate or - * alter the 's initial settings. + * Whether to enable the . Defaults to false. The preload script configured for the + * will have node integration enabled when it is executed so you should ensure + * remote/untrusted content is not able to create a tag with a possibly malicious + * preload script. You can use the will-attach-webview event on to strip away the + * preload script and to validate or alter the 's initial settings. */ webviewTag?: boolean; /** @@ -9678,6 +10497,17 @@ declare namespace Electron { * Default is false. */ navigateOnDragDrop?: boolean; + /** + * Autoplay policy to apply to content in the window, can be + * no-user-gesture-required, user-gesture-required, + * document-user-activation-required. Defaults to no-user-gesture-required. + */ + autoplayPolicy?: ('no-user-gesture-required' | 'user-gesture-required' | 'document-user-activation-required'); + /** + * Whether to prevent the window from resizing when entering HTML Fullscreen. + * Default is false. + */ + disableHtmlFullscreenWindowResize?: boolean; } interface DefaultFontFamily { @@ -9750,7 +10580,6 @@ declare namespace NodeJS { // addListener(event: 'loaded', listener: Function): this; // removeListener(event: 'loaded', listener: Function): this; // ### END VSCODE MODIFICATION ### - /** * Causes the main thread of the current process crash. */ @@ -9777,12 +10606,17 @@ declare namespace NodeJS { * private memory is more representative of the actual pre-compression memory usage * of the process on macOS. */ - getProcessMemoryInfo(): Electron.ProcessMemoryInfo; + getProcessMemoryInfo(): Promise; /** * Returns an object giving memory usage statistics about the entire system. Note * that all statistics are reported in Kilobytes. */ getSystemMemoryInfo(): Electron.SystemMemoryInfo; + /** + * Examples: Note: It returns the actual operating system version instead of kernel + * version on macOS unlike os.release(). + */ + getSystemVersion(): string; /** * Causes the main thread of the current process hang. */ @@ -9801,6 +10635,17 @@ declare namespace NodeJS { * this property is true in the main process, otherwise it is undefined. */ defaultApp?: boolean; + /** + * A Boolean that controls whether or not deprecation warnings are printed to + * stderr when formerly callback-based APIs converted to Promises are invoked using + * callbacks. Setting this to true will enable deprecation warnings. + */ + enablePromiseAPIs?: boolean; + /** + * A Boolean, true when the current renderer context is the "main" renderer frame. + * If you want the ID of the current frame you should use webFrame.routingId. + */ + isMainFrame?: boolean; /** * A Boolean. For Mac App Store build, this property is true, for other builds it * is undefined. @@ -9848,7 +10693,7 @@ declare namespace NodeJS { traceProcessWarnings?: boolean; /** * A String representing the current process's type, can be "browser" (i.e. main - * process) or "renderer". + * process), "renderer", or "worker" (i.e. web worker). */ type?: string; /** diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 27d56d5263..b17d05c9b2 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -203,16 +203,16 @@ export const toggleClass: (node: HTMLElement | SVGElement, className: string, sh class DomListener implements IDisposable { private _handler: (e: any) => void; - private _node: Element | Window | Document; + private _node: EventTarget; private readonly _type: string; - private readonly _useCapture: boolean; + private readonly _options: boolean | AddEventListenerOptions; - constructor(node: Element | Window | Document, type: string, handler: (e: any) => void, useCapture?: boolean) { + constructor(node: EventTarget, type: string, handler: (e: any) => void, options?: boolean | AddEventListenerOptions) { this._node = node; this._type = type; this._handler = handler; - this._useCapture = (useCapture || false); - this._node.addEventListener(this._type, this._handler, this._useCapture); + this._options = (options || false); + this._node.addEventListener(this._type, this._handler, this._options); } public dispose(): void { @@ -221,7 +221,7 @@ class DomListener implements IDisposable { return; } - this._node.removeEventListener(this._type, this._handler, this._useCapture); + this._node.removeEventListener(this._type, this._handler, this._options); // Prevent leakers from holding on to the dom or handler func this._node = null!; @@ -229,9 +229,10 @@ class DomListener implements IDisposable { } } -export function addDisposableListener(node: Element | Window | Document, type: K, handler: (event: GlobalEventHandlersEventMap[K]) => void, useCapture?: boolean): IDisposable; -export function addDisposableListener(node: Element | Window | Document, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable; -export function addDisposableListener(node: Element | Window | Document, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable { +export function addDisposableListener(node: EventTarget, type: K, handler: (event: GlobalEventHandlersEventMap[K]) => void, useCapture?: boolean): IDisposable; +export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable; +export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCapture: AddEventListenerOptions): IDisposable; +export function addDisposableListener(node: EventTarget, type: string, handler: (event: any) => void, useCapture?: boolean | AddEventListenerOptions): IDisposable { return new DomListener(node, type, handler, useCapture); } @@ -559,13 +560,11 @@ class SizeUtils { // Position & Dimension export class Dimension { - public width: number; - public height: number; - constructor(width: number, height: number) { - this.width = width; - this.height = height; - } + constructor( + public readonly width: number, + public readonly height: number, + ) { } static equals(a: Dimension | undefined, b: Dimension | undefined): boolean { if (a === b) { diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index 3dc9389da6..70979638c9 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -40,7 +40,8 @@ transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */ } -.monaco-action-bar .action-item .icon { +.monaco-action-bar .action-item .icon, +.monaco-action-bar .action-item .codicon { display: inline-block; } @@ -95,4 +96,4 @@ display: flex; align-items: center; justify-content: center; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 9e29b0ed7b..e7274468a8 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -311,14 +311,14 @@ export class ActionViewItem extends BaseActionViewItem { if (this.options.icon) { this.cssClass = this.getAction().class; - DOM.addClass(this.label, 'icon'); + DOM.addClass(this.label, 'codicon'); if (this.cssClass) { DOM.addClasses(this.label, this.cssClass); } this.updateEnabled(); } else { - DOM.removeClass(this.label, 'icon'); + DOM.removeClass(this.label, 'codicon'); } } diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index 0f609f880f..b044557e91 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -99,7 +99,7 @@ export class Checkbox extends Widget { this.domNode = document.createElement('div'); this.domNode.title = this._opts.title; - this.domNode.className = 'monaco-custom-checkbox ' + (this._opts.actionClassName || '') + ' ' + (this._checked ? 'checked' : 'unchecked'); + this.domNode.className = 'monaco-custom-checkbox codicon ' + (this._opts.actionClassName || '') + ' ' + (this._checked ? 'checked' : 'unchecked'); this.domNode.tabIndex = 0; this.domNode.setAttribute('role', 'checkbox'); this.domNode.setAttribute('aria-checked', String(this._checked)); diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css new file mode 100644 index 0000000000..b568eed82d --- /dev/null +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css @@ -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. + *--------------------------------------------------------------------------------------------*/ + +@keyframes codicon-spin { + 100% { + transform:rotate(360deg); + } +} + +.codicon-animation-spin { + animation: octicon-spin 1.5s linear infinite; +} diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css new file mode 100644 index 0000000000..a2c2be8f4a --- /dev/null +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -0,0 +1,354 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +@font-face { + font-family: "codicon"; + src: url("./codicon.ttf?e042d2dda15ef7b36b910e3edf539f26") format("truetype"); +} + +.codicon[class*='codicon-'] { + font: normal normal normal 16px/1 codicon; + display: inline-block; + text-decoration: none; + text-rendering: auto; + text-align: center; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + + +.codicon-add:before { content: "\ea60" } +.codicon-plus:before { content: "\ea60" } +.codicon-gist-new:before { content: "\ea60" } +.codicon-repo-create:before { content: "\ea60" } +.codicon-lightbulb:before { content: "\ea61" } +.codicon-light-bulb:before { content: "\ea61" } +.codicon-repo:before { content: "\ea62" } +.codicon-repo-delete:before { content: "\ea62" } +.codicon-gist-fork:before { content: "\ea63" } +.codicon-repo-forked:before { content: "\ea63" } +.codicon-git-pull-request:before { content: "\ea64" } +.codicon-git-pull-request-abandoned:before { content: "\ea64" } +.codicon-record-keys:before { content: "\ea65" } +.codicon-keyboard:before { content: "\ea65" } +.codicon-tag:before { content: "\ea66" } +.codicon-tag-add:before { content: "\ea66" } +.codicon-tag-remove:before { content: "\ea66" } +.codicon-person:before { content: "\ea67" } +.codicon-person-add:before { content: "\ea67" } +.codicon-person-follow:before { content: "\ea67" } +.codicon-person-outline:before { content: "\ea67" } +.codicon-person-filled:before { content: "\ea67" } +.codicon-git-branch:before { content: "\ea68" } +.codicon-git-branch-create:before { content: "\ea68" } +.codicon-git-branch-delete:before { content: "\ea68" } +.codicon-source-control:before { content: "\ea68" } +.codicon-mirror:before { content: "\ea69" } +.codicon-mirror-public:before { content: "\ea69" } +.codicon-star:before { content: "\ea6a" } +.codicon-star-add:before { content: "\ea6a" } +.codicon-star-delete:before { content: "\ea6a" } +.codicon-comment:before { content: "\ea6b" } +.codicon-comment-add:before { content: "\ea6b" } +.codicon-alert:before { content: "\ea6c" } +.codicon-warning:before { content: "\ea6c" } +.codicon-search:before { content: "\ea6d" } +.codicon-search-save:before { content: "\ea6d" } +.codicon-log-out:before { content: "\ea6e" } +.codicon-sign-out:before { content: "\ea6e" } +.codicon-log-in:before { content: "\ea6f" } +.codicon-sign-in:before { content: "\ea6f" } +.codicon-eye:before { content: "\ea70" } +.codicon-eye-unwatch:before { content: "\ea70" } +.codicon-eye-watch:before { content: "\ea70" } +.codicon-circle-filled:before { content: "\ea71" } +.codicon-primitive-dot:before { content: "\ea71" } +.codicon-stop:before { content: "\ea72" } +.codicon-primitive-square:before { content: "\ea72" } +.codicon-edit:before { content: "\ea73" } +.codicon-pencil:before { content: "\ea73" } +.codicon-info:before { content: "\ea74" } +.codicon-issue-opened:before { content: "\ea74" } +.codicon-gist-private:before { content: "\ea75" } +.codicon-git-fork-private:before { content: "\ea75" } +.codicon-lock:before { content: "\ea75" } +.codicon-mirror-private:before { content: "\ea75" } +.codicon-close:before { content: "\ea76" } +.codicon-remove-close:before { content: "\ea76" } +.codicon-x:before { content: "\ea76" } +.codicon-repo-sync:before { content: "\ea77" } +.codicon-sync:before { content: "\ea77" } +.codicon-clone:before { content: "\ea78" } +.codicon-desktop-download:before { content: "\ea78" } +.codicon-beaker:before { content: "\ea79" } +.codicon-microscope:before { content: "\ea79" } +.codicon-vm:before { content: "\ea7a" } +.codicon-device-desktop:before { content: "\ea7a" } +.codicon-file:before { content: "\ea7b" } +.codicon-file-text:before { content: "\ea7b" } +.codicon-more:before { content: "\ea7c" } +.codicon-kebab-horizontal:before { content: "\ea7c" } +.codicon-mail-reply:before { content: "\ea7d" } +.codicon-reply:before { content: "\ea7d" } +.codicon-organization:before { content: "\ea7e" } +.codicon-organization-filled:before { content: "\ea7e" } +.codicon-organization-outline:before { content: "\ea7e" } +.codicon-new-file:before { content: "\ea7f" } +.codicon-file-add:before { content: "\ea7f" } +.codicon-new-folder:before { content: "\ea80" } +.codicon-file-directory-create:before { content: "\ea80" } +.codicon-Vector:before { content: "\f101" } +.codicon-activate-breakpoints:before { content: "\f102" } +.codicon-archive:before { content: "\f103" } +.codicon-array:before { content: "\f104" } +.codicon-arrow-both:before { content: "\f105" } +.codicon-arrow-down:before { content: "\f106" } +.codicon-arrow-left:before { content: "\f107" } +.codicon-arrow-right:before { content: "\f108" } +.codicon-arrow-small-down:before { content: "\f109" } +.codicon-arrow-small-left:before { content: "\f10a" } +.codicon-arrow-small-right:before { content: "\f10b" } +.codicon-arrow-small-up:before { content: "\f10c" } +.codicon-arrow-up:before { content: "\f10d" } +.codicon-bell:before { content: "\f10e" } +.codicon-bold:before { content: "\f10f" } +.codicon-book:before { content: "\f110" } +.codicon-bookmark:before { content: "\f111" } +.codicon-boolean:before { content: "\f112" } +.codicon-breakpoint-conditional-unverified:before { content: "\f113" } +.codicon-breakpoint-conditional:before { content: "\f114" } +.codicon-breakpoint-data-unverified:before { content: "\f115" } +.codicon-breakpoint-data:before { content: "\f116" } +.codicon-breakpoint-log-unverified:before { content: "\f117" } +.codicon-breakpoint-log:before { content: "\f118" } +.codicon-briefcase:before { content: "\f119" } +.codicon-broadcast:before { content: "\f11a" } +.codicon-browser:before { content: "\f11b" } +.codicon-bug:before { content: "\f11c" } +.codicon-calendar:before { content: "\f11d" } +.codicon-case-sensitive:before { content: "\f11e" } +.codicon-check:before { content: "\f11f" } +.codicon-checklist:before { content: "\f120" } +.codicon-chevron-down:before { content: "\f121" } +.codicon-chevron-left:before { content: "\f122" } +.codicon-chevron-right:before { content: "\f123" } +.codicon-chevron-up:before { content: "\f124" } +.codicon-circle-outline:before { content: "\f125" } +.codicon-circle-slash:before { content: "\f126" } +.codicon-circuit-board:before { content: "\f127" } +.codicon-class:before { content: "\f128" } +.codicon-clear-all:before { content: "\f129" } +.codicon-clippy:before { content: "\f12a" } +.codicon-close-all:before { content: "\f12b" } +.codicon-cloud-download:before { content: "\f12c" } +.codicon-cloud-upload:before { content: "\f12d" } +.codicon-code:before { content: "\f12e" } +.codicon-collapse-all:before { content: "\f12f" } +.codicon-color-mode:before { content: "\f130" } +.codicon-color:before { content: "\f131" } +.codicon-comment-discussion:before { content: "\f132" } +.codicon-compare-changes:before { content: "\f133" } +.codicon-console:before { content: "\f134" } +.codicon-constant:before { content: "\f135" } +.codicon-continue:before { content: "\f136" } +.codicon-credit-card:before { content: "\f137" } +.codicon-current-and-breakpoint:before { content: "\f138" } +.codicon-current:before { content: "\f139" } +.codicon-dash:before { content: "\f13a" } +.codicon-dashboard:before { content: "\f13b" } +.codicon-database:before { content: "\f13c" } +.codicon-debug:before { content: "\f13d" } +.codicon-device-camera-video:before { content: "\f13e" } +.codicon-device-camera:before { content: "\f13f" } +.codicon-device-mobile:before { content: "\f140" } +.codicon-diff-added:before { content: "\f141" } +.codicon-diff-ignored:before { content: "\f142" } +.codicon-diff-modified:before { content: "\f143" } +.codicon-diff-removed:before { content: "\f144" } +.codicon-diff-renamed:before { content: "\f145" } +.codicon-diff:before { content: "\f146" } +.codicon-discard:before { content: "\f147" } +.codicon-disconnect-:before { content: "\f148" } +.codicon-editor-layout:before { content: "\f149" } +.codicon-ellipsis:before { content: "\f14a" } +.codicon-empty-window:before { content: "\f14b" } +.codicon-enumerator-member:before { content: "\f14c" } +.codicon-enumerator:before { content: "\f14d" } +.codicon-error:before { content: "\f14e" } +.codicon-event:before { content: "\f14f" } +.codicon-exclude:before { content: "\f150" } +.codicon-extensions:before { content: "\f151" } +.codicon-eye-closed:before { content: "\f152" } +.codicon-field:before { content: "\f153" } +.codicon-file-binary:before { content: "\f154" } +.codicon-file-code:before { content: "\f155" } +.codicon-file-media:before { content: "\f156" } +.codicon-file-pdf:before { content: "\f157" } +.codicon-file-submodule:before { content: "\f158" } +.codicon-file-symlink-directory:before { content: "\f159" } +.codicon-file-symlink-file:before { content: "\f15a" } +.codicon-file-zip:before { content: "\f15b" } +.codicon-files:before { content: "\f15c" } +.codicon-filter:before { content: "\f15d" } +.codicon-flame:before { content: "\f15e" } +.codicon-fold-down:before { content: "\f15f" } +.codicon-fold-up:before { content: "\f160" } +.codicon-fold:before { content: "\f161" } +.codicon-folder-active:before { content: "\f162" } +.codicon-folder-opened:before { content: "\f163" } +.codicon-folder:before { content: "\f164" } +.codicon-gift:before { content: "\f165" } +.codicon-gist-secret:before { content: "\f166" } +.codicon-gist:before { content: "\f167" } +.codicon-git-commit:before { content: "\f168" } +.codicon-git-compare:before { content: "\f169" } +.codicon-git-merge:before { content: "\f16a" } +.codicon-github-action:before { content: "\f16b" } +.codicon-github-alt:before { content: "\f16c" } +.codicon-github:before { content: "\f16d" } +.codicon-globe:before { content: "\f16e" } +.codicon-go-to-file:before { content: "\f16f" } +.codicon-grabber:before { content: "\f170" } +.codicon-graph:before { content: "\f171" } +.codicon-gripper:before { content: "\f172" } +.codicon-heart:before { content: "\f173" } +.codicon-history:before { content: "\f174" } +.codicon-home:before { content: "\f175" } +.codicon-horizontal-rule:before { content: "\f176" } +.codicon-hubot:before { content: "\f177" } +.codicon-inbox:before { content: "\f178" } +.codicon-interface:before { content: "\f179" } +.codicon-issue-closed:before { content: "\f17a" } +.codicon-issue-reopened:before { content: "\f17b" } +.codicon-issues:before { content: "\f17c" } +.codicon-italic:before { content: "\f17d" } +.codicon-jersey:before { content: "\f17e" } +.codicon-json:before { content: "\f17f" } +.codicon-kebab-vertical:before { content: "\f180" } +.codicon-key:before { content: "\f181" } +.codicon-keyword:before { content: "\f182" } +.codicon-law:before { content: "\f183" } +.codicon-lightbulb-autofix:before { content: "\f184" } +.codicon-link-external:before { content: "\f185" } +.codicon-link:before { content: "\f186" } +.codicon-list-ordered:before { content: "\f187" } +.codicon-list-unordered:before { content: "\f188" } +.codicon-live-share:before { content: "\f189" } +.codicon-loading:before { content: "\f18a" } +.codicon-location:before { content: "\f18b" } +.codicon-mail-read:before { content: "\f18c" } +.codicon-mail:before { content: "\f18d" } +.codicon-markdown:before { content: "\f18e" } +.codicon-megaphone:before { content: "\f18f" } +.codicon-mention:before { content: "\f190" } +.codicon-method:before { content: "\f191" } +.codicon-milestone:before { content: "\f192" } +.codicon-misc:before { content: "\f193" } +.codicon-mortar-board:before { content: "\f194" } +.codicon-move:before { content: "\f195" } +.codicon-multiple-windows:before { content: "\f196" } +.codicon-mute:before { content: "\f197" } +.codicon-namespace:before { content: "\f198" } +.codicon-no-newline:before { content: "\f199" } +.codicon-note:before { content: "\f19a" } +.codicon-numeric:before { content: "\f19b" } +.codicon-octoface:before { content: "\f19c" } +.codicon-open-preview:before { content: "\f19d" } +.codicon-operator:before { content: "\f19e" } +.codicon-package:before { content: "\f19f" } +.codicon-paintcan:before { content: "\f1a0" } +.codicon-parameter:before { content: "\f1a1" } +.codicon-pause:before { content: "\f1a2" } +.codicon-pin:before { content: "\f1a3" } +.codicon-play:before { content: "\f1a4" } +.codicon-plug:before { content: "\f1a5" } +.codicon-preserve-case:before { content: "\f1a6" } +.codicon-preview:before { content: "\f1a7" } +.codicon-project:before { content: "\f1a8" } +.codicon-property:before { content: "\f1a9" } +.codicon-pulse:before { content: "\f1aa" } +.codicon-question:before { content: "\f1ab" } +.codicon-quote:before { content: "\f1ac" } +.codicon-radio-tower:before { content: "\f1ad" } +.codicon-reactions:before { content: "\f1ae" } +.codicon-references:before { content: "\f1af" } +.codicon-refresh:before { content: "\f1b0" } +.codicon-regex:before { content: "\f1b1" } +.codicon-remote:before { content: "\f1b2" } +.codicon-remove:before { content: "\f1b3" } +.codicon-replace-all:before { content: "\f1b4" } +.codicon-replace:before { content: "\f1b5" } +.codicon-repo-clone:before { content: "\f1b6" } +.codicon-repo-force-push:before { content: "\f1b7" } +.codicon-repo-pull:before { content: "\f1b8" } +.codicon-repo-push:before { content: "\f1b9" } +.codicon-report:before { content: "\f1ba" } +.codicon-request-changes:before { content: "\f1bb" } +.codicon-restart:before { content: "\f1bc" } +.codicon-rocket:before { content: "\f1bd" } +.codicon-root-folder-opened:before { content: "\f1be" } +.codicon-root-folder:before { content: "\f1bf" } +.codicon-rss:before { content: "\f1c0" } +.codicon-ruby:before { content: "\f1c1" } +.codicon-ruler:before { content: "\f1c2" } +.codicon-save-all:before { content: "\f1c3" } +.codicon-save-as:before { content: "\f1c4" } +.codicon-save:before { content: "\f1c5" } +.codicon-screen-full:before { content: "\f1c6" } +.codicon-screen-normal:before { content: "\f1c7" } +.codicon-search-stop:before { content: "\f1c8" } +.codicon-selection:before { content: "\f1c9" } +.codicon-server:before { content: "\f1ca" } +.codicon-settings:before { content: "\f1cb" } +.codicon-shield:before { content: "\f1cc" } +.codicon-smiley:before { content: "\f1cd" } +.codicon-snippet:before { content: "\f1ce" } +.codicon-sort-precedence:before { content: "\f1cf" } +.codicon-split-horizontal:before { content: "\f1d0" } +.codicon-split-vertical:before { content: "\f1d1" } +.codicon-squirrel:before { content: "\f1d2" } +.codicon-star-empty:before { content: "\f1d3" } +.codicon-star-full:before { content: "\f1d4" } +.codicon-star-half:before { content: "\f1d5" } +.codicon-start:before { content: "\f1d6" } +.codicon-step-into:before { content: "\f1d7" } +.codicon-step-out:before { content: "\f1d8" } +.codicon-step-over:before { content: "\f1d9" } +.codicon-string:before { content: "\f1da" } +.codicon-structure:before { content: "\f1db" } +.codicon-tasklist:before { content: "\f1dc" } +.codicon-telescope:before { content: "\f1dd" } +.codicon-text-size:before { content: "\f1de" } +.codicon-three-bars:before { content: "\f1df" } +.codicon-thumbsdown:before { content: "\f1e0" } +.codicon-thumbsup:before { content: "\f1e1" } +.codicon-tools:before { content: "\f1e2" } +.codicon-trash:before { content: "\f1e3" } +.codicon-triangle-down:before { content: "\f1e4" } +.codicon-triangle-left:before { content: "\f1e5" } +.codicon-triangle-right:before { content: "\f1e6" } +.codicon-triangle-up:before { content: "\f1e7" } +.codicon-twitter:before { content: "\f1e8" } +.codicon-unfold:before { content: "\f1e9" } +.codicon-unlock:before { content: "\f1ea" } +.codicon-unmute:before { content: "\f1eb" } +.codicon-unverified:before { content: "\f1ec" } +.codicon-variable:before { content: "\f1ed" } +.codicon-verified:before { content: "\f1ee" } +.codicon-versions:before { content: "\f1ef" } +.codicon-vm-active:before { content: "\f1f0" } +.codicon-vm-outline:before { content: "\f1f1" } +.codicon-vm-running:before { content: "\f1f2" } +.codicon-watch:before { content: "\f1f3" } +.codicon-whitespace:before { content: "\f1f4" } +.codicon-whole-word:before { content: "\f1f5" } +.codicon-window:before { content: "\f1f6" } +.codicon-word-wrap:before { content: "\f1f7" } +.codicon-zoom-in:before { content: "\f1f8" } +.codicon-zoom-out:before { content: "\f1f9" } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf new file mode 100644 index 0000000000..688b9ffd0b Binary files /dev/null and b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf differ diff --git a/src/vs/base/browser/ui/codiconLabel/codiconLabel.mock.ts b/src/vs/base/browser/ui/codiconLabel/codiconLabel.mock.ts new file mode 100644 index 0000000000..ef04a20569 --- /dev/null +++ b/src/vs/base/browser/ui/codiconLabel/codiconLabel.mock.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { escape } from 'vs/base/common/strings'; + +export function renderCodicons(text: string): string { + return escape(text); +} + +export class CodiconLabel { + + private _container: HTMLElement; + + constructor(container: HTMLElement) { + this._container = container; + } + + set text(text: string) { + this._container.innerHTML = renderCodicons(text || ''); + } + +} diff --git a/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts b/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts new file mode 100644 index 0000000000..f97348a3a9 --- /dev/null +++ b/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./codicon/codicon'; +import 'vs/css!./codicon/codicon-animations'; +import { escape } from 'vs/base/common/strings'; + +function expand(text: string): string { + return text.replace(/\$\(((.+?)(~(.*?))?)\)/g, (_match, _g1, name, _g3, animation) => { + return ``; + }); +} + +export function renderCodicons(label: string): string { + return expand(escape(label)); +} + +export class CodiconLabel { + + constructor( + private readonly _container: HTMLElement + ) { } + + set text(text: string) { + this._container.innerHTML = renderCodicons(text || ''); + } + + set title(title: string) { + this._container.title = title; + } +} diff --git a/src/vs/base/browser/ui/findinput/case-sensitive-dark.svg b/src/vs/base/browser/ui/findinput/case-sensitive-dark.svg deleted file mode 100644 index 40c9ed3262..0000000000 --- a/src/vs/base/browser/ui/findinput/case-sensitive-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/findinput/case-sensitive-hc.svg b/src/vs/base/browser/ui/findinput/case-sensitive-hc.svg deleted file mode 100644 index 82501418da..0000000000 --- a/src/vs/base/browser/ui/findinput/case-sensitive-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/findinput/case-sensitive-light.svg b/src/vs/base/browser/ui/findinput/case-sensitive-light.svg deleted file mode 100644 index aa1dbd353e..0000000000 --- a/src/vs/base/browser/ui/findinput/case-sensitive-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/findinput/findInputCheckboxes.css b/src/vs/base/browser/ui/findinput/findInputCheckboxes.css deleted file mode 100644 index 0ce7e7d27e..0000000000 --- a/src/vs/base/browser/ui/findinput/findInputCheckboxes.css +++ /dev/null @@ -1,56 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.vs .monaco-custom-checkbox.monaco-case-sensitive { - background: url('case-sensitive-light.svg') center center no-repeat; -} - -.vs-dark .monaco-custom-checkbox.monaco-case-sensitive { - background: url('case-sensitive-dark.svg') center center no-repeat; -} - -.hc-black .monaco-custom-checkbox.monaco-case-sensitive, -.hc-black .monaco-custom-checkbox.monaco-case-sensitive:hover { - background: url('case-sensitive-hc.svg') center center no-repeat; -} - -.vs .monaco-custom-checkbox.monaco-preserve-case { - background: url('preserve-case-light.svg') center center no-repeat; -} - -.vs-dark .monaco-custom-checkbox.monaco-preserve-case { - background: url('preserve-case-dark.svg') center center no-repeat; -} - -.hc-black .monaco-custom-checkbox.monaco-preserve-case, -.hc-black .monaco-custom-checkbox.monaco-preserve-case:hover { - background: url('preserve-case-hc.svg') center center no-repeat; -} - -.vs .monaco-custom-checkbox.monaco-whole-word { - background: url('whole-word-light.svg') center center no-repeat; -} - -.vs-dark .monaco-custom-checkbox.monaco-whole-word { - background: url('whole-word-dark.svg') center center no-repeat; -} - -.hc-black .monaco-custom-checkbox.monaco-whole-word, -.hc-black .monaco-custom-checkbox.monaco-whole-word:hover { - background: url('whole-word-hc.svg') center center no-repeat; -} - -.vs .monaco-custom-checkbox.monaco-regex { - background: url('regex-light.svg') center center no-repeat; -} - -.vs-dark .monaco-custom-checkbox.monaco-regex { - background: url('regex-dark.svg') center center no-repeat; -} - -.hc-black .monaco-custom-checkbox.monaco-regex, -.hc-black .monaco-custom-checkbox.monaco-regex:hover { - background: url('regex-hc.svg') center center no-repeat; -} diff --git a/src/vs/base/browser/ui/findinput/findInputCheckboxes.ts b/src/vs/base/browser/ui/findinput/findInputCheckboxes.ts index 46dd9d67aa..5d8a17b036 100644 --- a/src/vs/base/browser/ui/findinput/findInputCheckboxes.ts +++ b/src/vs/base/browser/ui/findinput/findInputCheckboxes.ts @@ -5,7 +5,6 @@ import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { Color } from 'vs/base/common/color'; -import 'vs/css!./findInputCheckboxes'; import * as nls from 'vs/nls'; export interface IFindInputCheckboxOpts { @@ -22,7 +21,7 @@ const NLS_REGEX_CHECKBOX_LABEL = nls.localize('regexDescription', "Use Regular E export class CaseSensitiveCheckbox extends Checkbox { constructor(opts: IFindInputCheckboxOpts) { super({ - actionClassName: 'monaco-case-sensitive', + actionClassName: 'codicon-case-sensitive', title: NLS_CASE_SENSITIVE_CHECKBOX_LABEL + opts.appendTitle, isChecked: opts.isChecked, inputActiveOptionBorder: opts.inputActiveOptionBorder, @@ -34,7 +33,7 @@ export class CaseSensitiveCheckbox extends Checkbox { export class WholeWordsCheckbox extends Checkbox { constructor(opts: IFindInputCheckboxOpts) { super({ - actionClassName: 'monaco-whole-word', + actionClassName: 'codicon-whole-word', title: NLS_WHOLE_WORD_CHECKBOX_LABEL + opts.appendTitle, isChecked: opts.isChecked, inputActiveOptionBorder: opts.inputActiveOptionBorder, @@ -46,7 +45,7 @@ export class WholeWordsCheckbox extends Checkbox { export class RegexCheckbox extends Checkbox { constructor(opts: IFindInputCheckboxOpts) { super({ - actionClassName: 'monaco-regex', + actionClassName: 'codicon-regex', title: NLS_REGEX_CHECKBOX_LABEL + opts.appendTitle, isChecked: opts.isChecked, inputActiveOptionBorder: opts.inputActiveOptionBorder, diff --git a/src/vs/base/browser/ui/findinput/preserve-case-dark.svg b/src/vs/base/browser/ui/findinput/preserve-case-dark.svg deleted file mode 100644 index 1c87e34716..0000000000 --- a/src/vs/base/browser/ui/findinput/preserve-case-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/base/browser/ui/findinput/preserve-case-hc.svg b/src/vs/base/browser/ui/findinput/preserve-case-hc.svg deleted file mode 100644 index f316948bb9..0000000000 --- a/src/vs/base/browser/ui/findinput/preserve-case-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/base/browser/ui/findinput/preserve-case-light.svg b/src/vs/base/browser/ui/findinput/preserve-case-light.svg deleted file mode 100644 index 83954b6652..0000000000 --- a/src/vs/base/browser/ui/findinput/preserve-case-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/base/browser/ui/findinput/regex-dark.svg b/src/vs/base/browser/ui/findinput/regex-dark.svg deleted file mode 100644 index cf43e9d952..0000000000 --- a/src/vs/base/browser/ui/findinput/regex-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/findinput/regex-hc.svg b/src/vs/base/browser/ui/findinput/regex-hc.svg deleted file mode 100644 index 116c547d15..0000000000 --- a/src/vs/base/browser/ui/findinput/regex-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/findinput/regex-light.svg b/src/vs/base/browser/ui/findinput/regex-light.svg deleted file mode 100644 index b329c104fa..0000000000 --- a/src/vs/base/browser/ui/findinput/regex-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index 141934c785..9dda103ae8 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -41,7 +41,7 @@ export class PreserveCaseCheckbox extends Checkbox { constructor(opts: IFindInputCheckboxOpts) { super({ // TODO: does this need its own icon? - actionClassName: 'monaco-case-sensitive', + actionClassName: 'codicon-preserve-case', title: NLS_PRESERVE_CASE_LABEL + opts.appendTitle, isChecked: opts.isChecked, inputActiveOptionBorder: opts.inputActiveOptionBorder diff --git a/src/vs/base/browser/ui/findinput/whole-word-dark.svg b/src/vs/base/browser/ui/findinput/whole-word-dark.svg deleted file mode 100644 index 6c38eb215d..0000000000 --- a/src/vs/base/browser/ui/findinput/whole-word-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/findinput/whole-word-hc.svg b/src/vs/base/browser/ui/findinput/whole-word-hc.svg deleted file mode 100644 index fc1ff43268..0000000000 --- a/src/vs/base/browser/ui/findinput/whole-word-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/findinput/whole-word-light.svg b/src/vs/base/browser/ui/findinput/whole-word-light.svg deleted file mode 100644 index 345e65b2a5..0000000000 --- a/src/vs/base/browser/ui/findinput/whole-word-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 950001d013..9732e00fa3 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as objects from 'vs/base/common/objects'; -import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; +import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import { escape } from 'vs/base/common/strings'; export interface IHighlight { @@ -65,13 +65,13 @@ export class HighlightedLabel { if (pos < highlight.start) { htmlContent += ''; const substring = this.text.substring(pos, highlight.start); - htmlContent += this.supportOcticons ? renderOcticons(substring) : escape(substring); + htmlContent += this.supportOcticons ? renderCodicons(substring) : escape(substring); htmlContent += ''; pos = highlight.end; } htmlContent += ''; const substring = this.text.substring(highlight.start, highlight.end); - htmlContent += this.supportOcticons ? renderOcticons(substring) : escape(substring); + htmlContent += this.supportOcticons ? renderCodicons(substring) : escape(substring); htmlContent += ''; pos = highlight.end; } @@ -79,7 +79,7 @@ export class HighlightedLabel { if (pos < this.text.length) { htmlContent += ''; const substring = this.text.substring(pos); - htmlContent += this.supportOcticons ? renderOcticons(substring) : escape(substring); + htmlContent += this.supportOcticons ? renderCodicons(substring) : escape(substring); htmlContent += ''; } diff --git a/src/vs/base/browser/ui/inputbox/inputBox.css b/src/vs/base/browser/ui/inputbox/inputBox.css index 9415d36a1b..a56bf03fbe 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.css +++ b/src/vs/base/browser/ui/inputbox/inputBox.css @@ -123,7 +123,7 @@ margin-left: 2px; } -.monaco-inputbox .monaco-action-bar .action-item .icon { +.monaco-inputbox .monaco-action-bar .action-item .codicon { background-repeat: no-repeat; width: 16px; height: 16px; diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.css b/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.css index 7e86f3a74c..69e7dac4e6 100644 --- a/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.css +++ b/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.css @@ -1,6 +1,6 @@ @font-face { font-family: "octicons2"; - src: url("./octicons2.ttf?90586c9ac0aa804395e9c9c0d15d1094") format("truetype"); + src: url("./octicons2.ttf?aa78025ff36faa0aebb5b864685c6dc4") format("truetype"); } .octicon, .mega-octicon { diff --git a/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.ttf b/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.ttf index 793aba95d5..19f3d6e11c 100644 Binary files a/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.ttf and b/src/vs/base/browser/ui/octiconLabel/octicons/octicons2.ttf differ diff --git a/src/vs/base/browser/ui/splitview/panelview.css b/src/vs/base/browser/ui/splitview/panelview.css index 7fd452238f..dfcdf00476 100644 --- a/src/vs/base/browser/ui/splitview/panelview.css +++ b/src/vs/base/browser/ui/splitview/panelview.css @@ -68,7 +68,8 @@ } /* TODO: actions should be part of the panel, but they aren't yet */ -.monaco-panel-view .panel > .panel-header > .actions .action-label.icon { +.monaco-panel-view .panel > .panel-header > .actions .action-label.icon, +.monaco-panel-view .panel > .panel-header > .actions .action-label.codicon { width: 28px; height: 22px; background-size: 16px; diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index 6a579f0bbf..26bd2964d6 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import * as platform from 'vs/base/common/platform'; export namespace Schemas { @@ -60,7 +60,7 @@ class RemoteAuthoritiesImpl { private readonly _ports: { [authority: string]: number; }; private readonly _connectionTokens: { [authority: string]: string; }; private _preferredWebSchema: 'http' | 'https'; - private _delegate: ((uri: URI) => UriComponents) | null; + private _delegate: ((uri: URI) => URI) | null; constructor() { this._hosts = Object.create(null); @@ -74,7 +74,7 @@ class RemoteAuthoritiesImpl { this._preferredWebSchema = schema; } - public setDelegate(delegate: (uri: URI) => UriComponents): void { + public setDelegate(delegate: (uri: URI) => URI): void { this._delegate = delegate; } @@ -89,8 +89,7 @@ class RemoteAuthoritiesImpl { public rewrite(uri: URI): URI { if (this._delegate) { - const result = this._delegate(uri); - return URI.revive(result); + return this._delegate(uri); } const authority = uri.authority; const host = this._hosts[authority]; diff --git a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts index af464fa42b..388185df18 100644 --- a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts +++ b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts @@ -3,11 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Menu, MenuItem, BrowserWindow, Event, ipcMain } from 'electron'; +import { Menu, MenuItem, BrowserWindow, ipcMain } from 'electron'; import { ISerializableContextMenuItem, CONTEXT_MENU_CLOSE_CHANNEL, CONTEXT_MENU_CHANNEL, IPopupOptions } from 'vs/base/parts/contextmenu/common/contextmenu'; export function registerContextMenuListener(): void { - ipcMain.on(CONTEXT_MENU_CHANNEL, (event: Event, contextMenuId: number, items: ISerializableContextMenuItem[], onClickChannel: string, options?: IPopupOptions) => { + ipcMain.on(CONTEXT_MENU_CHANNEL, (event: Electron.IpcMainEvent, contextMenuId: number, items: ISerializableContextMenuItem[], onClickChannel: string, options?: IPopupOptions) => { const menu = createMenu(event, onClickChannel, items); menu.popup({ @@ -27,7 +27,7 @@ export function registerContextMenuListener(): void { }); } -function createMenu(event: Event, onClickChannel: string, items: ISerializableContextMenuItem[]): Menu { +function createMenu(event: Electron.IpcMainEvent, onClickChannel: string, items: ISerializableContextMenuItem[]): Menu { const menu = new Menu(); items.forEach(item => { diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index f9708d4e15..79e367f347 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -32,7 +32,6 @@ export interface IServerChannel { listen(ctx: TContext, event: string, arg?: any): Event; } - export const enum RequestType { Promise = 100, PromiseCancel = 101, diff --git a/src/vs/base/parts/tree/browser/collapse-all-dark.svg b/src/vs/base/parts/tree/browser/collapse-all-dark.svg deleted file mode 100644 index 4862c55dbe..0000000000 --- a/src/vs/base/parts/tree/browser/collapse-all-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/base/parts/tree/browser/collapse-all-hc.svg b/src/vs/base/parts/tree/browser/collapse-all-hc.svg deleted file mode 100644 index 05f920b29b..0000000000 --- a/src/vs/base/parts/tree/browser/collapse-all-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/base/parts/tree/browser/collapse-all-light.svg b/src/vs/base/parts/tree/browser/collapse-all-light.svg deleted file mode 100644 index 6359b42e62..0000000000 --- a/src/vs/base/parts/tree/browser/collapse-all-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/base/parts/tree/browser/tree.css b/src/vs/base/parts/tree/browser/tree.css index a74ae446e0..c0289b6fcb 100644 --- a/src/vs/base/parts/tree/browser/tree.css +++ b/src/vs/base/parts/tree/browser/tree.css @@ -120,15 +120,3 @@ .hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children.loading > .content:before { background-image: url('loading-hc.svg'); } - -.monaco-tree-action.collapse-all { - background: url('collapse-all-light.svg') center center no-repeat; -} - -.vs-dark .monaco-tree-action.collapse-all { - background: url('collapse-all-dark.svg') center center no-repeat; -} - -.hc-black .monaco-tree-action.collapse-all { - background: url('collapse-all-hc.svg') center center no-repeat; -} diff --git a/src/vs/code/browser/workbench/workbench-dev.html b/src/vs/code/browser/workbench/workbench-dev.html index 9fb0fab5a1..910666edf0 100644 --- a/src/vs/code/browser/workbench/workbench-dev.html +++ b/src/vs/code/browser/workbench/workbench-dev.html @@ -14,7 +14,7 @@ default-src 'self'; img-src 'self' https: data: blob:; media-src 'none'; - script-src 'self' https://az416426.vo.msecnd.net 'unsafe-eval' https: 'sha256-4DqvCTjCHj2KW4QxC/Yt6uBwMRyYiEg7kOoykSEkonQ=' 'sha256-meDZW3XhN5JmdjFUrWGhTouRKBiWYtXHltaKnqn/WMo='; + script-src 'self' https://az416426.vo.msecnd.net 'unsafe-eval' https: 'sha256-AMRGFXNZ7mBnD/6F4lTV00XAjE5CBSM7ZeIv3DIp5YM=' 'sha256-meDZW3XhN5JmdjFUrWGhTouRKBiWYtXHltaKnqn/WMo='; child-src 'self'; frame-src 'self' https://*.vscode-webview-test.com; worker-src 'self'; @@ -44,13 +44,13 @@ self.require = { baseUrl: `${window.location.origin}/static/out`, paths: { - 'vscode-textmate': `${window.location.origin}/static/node_modules/vscode-textmate/release/main`, - 'onigasm-umd': `${window.location.origin}/static/node_modules/onigasm-umd/release/main`, - 'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`, - 'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, - 'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`, - 'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`, - '@microsoft/applicationinsights-web': `${window.location.origin}/static/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`, + 'vscode-textmate': `${window.location.origin}/static/remote/web/node_modules/vscode-textmate/release/main`, + 'onigasm-umd': `${window.location.origin}/static/remote/web/node_modules/onigasm-umd/release/main`, + 'xterm': `${window.location.origin}/static/remote/web/node_modules/xterm/lib/xterm.js`, + 'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`, + 'xterm-addon-web-links': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`, + 'semver-umd': `${window.location.origin}/static/remote/web/node_modules/semver-umd/lib/semver-umd.js`, + '@microsoft/applicationinsights-web': `${window.location.origin}/static/remote/web/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`, } }; diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 41fc2d055c..3593c528ae 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -3,16 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchConstructionOptions, create } from 'vs/workbench/workbench.web.api'; -import { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService'; -import { Event, Emitter } from 'vs/base/common/event'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { IWorkbenchConstructionOptions, create, URI, Event, Emitter, UriComponents, ICredentialsProvider, IURLCallbackProvider } from 'vs/workbench/workbench.web.api'; import { generateUuid } from 'vs/base/common/uuid'; import { CancellationToken } from 'vs/base/common/cancellation'; import { streamToBuffer } from 'vs/base/common/buffer'; import { Disposable } from 'vs/base/common/lifecycle'; import { request } from 'vs/base/parts/request/browser/request'; -import { ICredentialsProvider } from 'vs/workbench/services/credentials/browser/credentialsService'; interface ICredential { service: string; @@ -24,7 +20,7 @@ class LocalStorageCredentialsProvider implements ICredentialsProvider { static readonly CREDENTIALS_OPENED_KEY = 'credentials.provider'; - private _credentials: ICredential[]; + private _credentials!: ICredential[]; private get credentials(): ICredential[] { if (!this._credentials) { try { @@ -205,4 +201,12 @@ const options: IWorkbenchConstructionOptions = JSON.parse(document.getElementByI options.urlCallbackProvider = new PollingURLCallbackProvider(); options.credentialsProvider = new LocalStorageCredentialsProvider(); +if (options.folderUri) { + options.folderUri = URI.revive(options.folderUri); +} + +if (options.workspaceUri) { + options.workspaceUri = URI.revive(options.workspaceUri); +} + create(document.body, options); diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index 1f9b21d0aa..4637310668 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -10,8 +10,7 @@ import { $ } from 'vs/base/browser/dom'; import * as collections from 'vs/base/common/collections'; import * as browser from 'vs/base/browser/browser'; import { escape } from 'vs/base/common/strings'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/common/product'; import * as os from 'os'; import { debounce } from 'vs/base/common/decorators'; import * as platform from 'vs/base/common/platform'; @@ -33,7 +32,7 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-browser/issue/issueReporterModel'; import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/node/issue'; import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; -import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { ILogService, getLogLevel } from 'vs/platform/log/common/log'; import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil'; @@ -85,7 +84,7 @@ export class IssueReporter extends Disposable { this.issueReporterModel = new IssueReporterModel({ issueType: configuration.data.issueType || IssueType.Bug, versionInfo: { - vscodeVersion: `${pkg.name} ${pkg.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`, + vscodeVersion: `${product.nameShort} ${product.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`, os: `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}` }, extensionsDisabled: !!this.environmentService.disableExtensions, @@ -301,8 +300,8 @@ export class IssueReporter extends Disposable { this.environmentService = new EnvironmentService(configuration, configuration.execPath); const logService = new SpdLogService(`issuereporter${configuration.windowId}`, this.environmentService.logsPath, getLogLevel(this.environmentService)); - const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel')); - this.logService = new FollowerLogService(logLevelClient, logService); + const loggerClient = new LoggerChannelClient(mainProcessService.getChannel('logger')); + this.logService = new FollowerLogService(loggerClient, logService); const sharedProcess = (serviceCollection.get(IWindowsService)).whenSharedProcessReady() .then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${configuration.windowId}`)); @@ -311,7 +310,7 @@ export class IssueReporter extends Disposable { if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) { const channel = getDelayedChannel(sharedProcess.then(c => c.getChannel('telemetryAppender'))); const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)); - const commonProperties = resolveCommonProperties(product.commit || 'Commit unknown', pkg.version, configuration.machineId, product.msftInternalDomains, this.environmentService.installSourcePath); + const commonProperties = resolveCommonProperties(product.commit || 'Commit unknown', product.version, configuration.machineId, product.msftInternalDomains, this.environmentService.installSourcePath); const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot]; const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths }; diff --git a/src/vs/code/electron-browser/issue/media/issueReporter.css b/src/vs/code/electron-browser/issue/media/issueReporter.css index 433cc91fdc..b256e0359a 100644 --- a/src/vs/code/electron-browser/issue/media/issueReporter.css +++ b/src/vs/code/electron-browser/issue/media/issueReporter.css @@ -109,7 +109,7 @@ html:lang(zh-Hant) { } html:lang(ja) { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Meiryo", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif; } html:lang(ko) { diff --git a/src/vs/code/electron-browser/processExplorer/media/processExplorer.css b/src/vs/code/electron-browser/processExplorer/media/processExplorer.css index b9b703bdef..3e3acaae00 100644 --- a/src/vs/code/electron-browser/processExplorer/media/processExplorer.css +++ b/src/vs/code/electron-browser/processExplorer/media/processExplorer.css @@ -17,7 +17,7 @@ html:lang(zh-Hant) { } html:lang(ja) { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Meiryo", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Noto Sans", "Yu Gothic UI", "Meiryo UI", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif; } html:lang(ko) { @@ -101,4 +101,4 @@ tbody > tr:hover { img { width: 16px; margin-right: 4px; -} \ No newline at end of file +} diff --git a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts index a2ac7f6875..d69a40090c 100644 --- a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/processExplorer'; import { webFrame, ipcRenderer, clipboard } from 'electron'; import { repeat } from 'vs/base/common/strings'; import { totalmem } from 'os'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { localize } from 'vs/nls'; import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/node/issue'; import * as browser from 'vs/base/browser/browser'; diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts index 5c59f89bf5..c4d8930fc6 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts @@ -7,7 +7,7 @@ import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { IStringDictionary } from 'vs/base/common/collections'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts index 87488502d8..91ebed4dfd 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts @@ -8,7 +8,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { readdir, rimraf, stat } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; export class NodeCachedDataCleaner { diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts index db03b8f82c..5e856bff2a 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts @@ -8,7 +8,7 @@ import { join } from 'vs/base/common/path'; import { readdir, readFile, rimraf } from 'vs/base/node/pfs'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IBackupWorkspacesFormat } from 'vs/platform/backup/common/backup'; +import { IBackupWorkspacesFormat } from 'vs/platform/backup/node/backup'; export class StorageDataCleaner extends Disposable { diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 001458bd16..bc32d6a98c 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -5,8 +5,7 @@ import * as fs from 'fs'; import * as platform from 'vs/base/common/platform'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/common/product'; import { serve, Server, connect } from 'vs/base/parts/ipc/node/ipc.net'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -27,11 +26,11 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProper import { TelemetryAppenderChannel } from 'vs/platform/telemetry/node/telemetryIpc'; import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowsService, ActiveWindowManager } from 'vs/platform/windows/common/windows'; import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; import { ipcRenderer } from 'electron'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; -import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { LocalizationsChannel } from 'vs/platform/localizations/node/localizationsIpc'; @@ -51,7 +50,12 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncService, UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService'; +import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; +import { SettingsMergeChannelClient } from 'vs/platform/userDataSync/common/settingsSyncIpc'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -97,8 +101,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const environmentService = new EnvironmentService(initData.args, process.execPath); const mainRouter = new StaticRouter(ctx => ctx === 'main'); - const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', mainRouter)); - const logService = new FollowerLogService(logLevelClient, new SpdLogService('sharedprocess', environmentService.logsPath, initData.logLevel)); + const loggerClient = new LoggerChannelClient(server.getChannel('logger', mainRouter)); + const logService = new FollowerLogService(loggerClient, new SpdLogService('sharedprocess', environmentService.logsPath, initData.logLevel)); disposables.add(logService); logService.info('main', JSON.stringify(configuration)); @@ -118,6 +122,11 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const windowsService = new WindowsService(mainProcessService); services.set(IWindowsService, windowsService); + const activeWindowManager = new ActiveWindowManager(windowsService); + const activeWindowRouter = new StaticRouter(ctx => activeWindowManager.getActiveClientId().then(id => ctx === id)); + const settingsMergeChannel = server.getChannel('settingsMerge', activeWindowRouter); + services.set(ISettingsMergeService, new SettingsMergeChannelClient(settingsMergeChannel)); + // Files const fileService = new FileService(logService); services.set(IFileService, fileService); @@ -136,7 +145,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const services = new ServiceCollection(); const environmentService = accessor.get(IEnvironmentService); const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService; - const telemetryLogService = new FollowerLogService(logLevelClient, new SpdLogService('telemetry', environmentService.logsPath, initData.logLevel)); + const telemetryLogService = new FollowerLogService(loggerClient, new SpdLogService('telemetry', environmentService.logsPath, initData.logLevel)); telemetryLogService.info('The below are logs for every telemetry event sent from VS Code once the log level is set to trace.'); telemetryLogService.info('==========================================================='); @@ -148,7 +157,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat } const config: ITelemetryServiceConfig = { appender: combinedAppender(appInsightsAppender, new LogAppender(logService)), - commonProperties: resolveCommonProperties(product.commit, pkg.version, configuration.machineId, product.msftInternalDomains, installSourcePath), + commonProperties: resolveCommonProperties(product.commit, product.version, configuration.machineId, product.msftInternalDomains, installSourcePath), piiPaths: extensionsPath ? [appRoot, extensionsPath] : [appRoot] }; @@ -164,6 +173,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); + services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); + services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); const instantiationService2 = instantiationService.createChild(services); @@ -181,6 +192,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const diagnosticsChannel = new DiagnosticsChannel(diagnosticsService); server.registerChannel('diagnostics', diagnosticsChannel); + const userDataSyncService = accessor.get(IUserDataSyncService); + const userDataSyncChannel = new UserDataSyncChannel(userDataSyncService); + server.registerChannel('userDataSync', userDataSyncChannel); + // clean up deprecated extensions (extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions(); // update localizations cache @@ -190,7 +205,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat instantiationService2.createInstance(NodeCachedDataCleaner), instantiationService2.createInstance(LanguagePackCachedDataCleaner), instantiationService2.createInstance(StorageDataCleaner), - instantiationService2.createInstance(LogsDataCleaner) + instantiationService2.createInstance(LogsDataCleaner), + instantiationService2.createInstance(UserDataAutoSync) )); disposables.add(extensionManagementService as ExtensionManagementService); }); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 7ae330744c..514d837a06 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -9,7 +9,7 @@ import { WindowsManager } from 'vs/code/electron-main/windows'; import { IWindowsService, OpenContext, ActiveWindowManager, IURIToOpen } from 'vs/platform/windows/common/windows'; import { WindowsChannel } from 'vs/platform/windows/common/windowsIpc'; import { WindowsService } from 'vs/platform/windows/electron-main/windowsService'; -import { ILifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { getShellEnvironment } from 'vs/code/node/shellEnv'; import { IUpdateService } from 'vs/platform/update/common/update'; import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc'; @@ -17,7 +17,7 @@ import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { Server, connect } from 'vs/base/parts/ipc/node/ipc.net'; import { SharedProcess } from 'vs/code/electron-main/sharedProcess'; -import { LaunchService, LaunchChannel, ILaunchService } from 'vs/platform/launch/electron-main/launchService'; +import { LaunchMainService, LaunchChannel, ILaunchMainService } from 'vs/platform/launch/electron-main/launchService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -33,28 +33,26 @@ import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; +import { SimpleServiceProxyChannel } from 'vs/platform/ipc/node/simpleIpcProxy'; +import product from 'vs/platform/product/common/product'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; -import { IHistoryMainService } from 'vs/platform/history/common/history'; import { URI } from 'vs/base/common/uri'; -import { WorkspacesChannel } from 'vs/platform/workspaces/node/workspacesIpc'; -import { IWorkspacesMainService, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; +import { WorkspacesChannel } from 'vs/platform/workspaces/electron-main/workspacesIpc'; +import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { getMachineId } from 'vs/base/node/id'; import { Win32UpdateService } from 'vs/platform/update/electron-main/updateService.win32'; import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux'; import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin'; import { IIssueService } from 'vs/platform/issue/node/issue'; -import { IssueChannel } from 'vs/platform/issue/node/issueIpc'; -import { IssueService } from 'vs/platform/issue/electron-main/issueService'; -import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; +import { IssueMainService } from 'vs/platform/issue/electron-main/issueMainService'; +import { LoggerChannel } from 'vs/platform/log/common/logIpc'; import { setUnexpectedErrorHandler, onUnexpectedError } from 'vs/base/common/errors'; import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener'; import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver'; import { IMenubarService } from 'vs/platform/menubar/node/menubar'; -import { MenubarService } from 'vs/platform/menubar/electron-main/menubarService'; +import { MenubarMainService } from 'vs/platform/menubar/electron-main/menubarMainService'; import { MenubarChannel } from 'vs/platform/menubar/node/menubarIpc'; import { RunOnceScheduler } from 'vs/base/common/async'; import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu'; @@ -67,10 +65,10 @@ import { IStorageMainService, StorageMainService } from 'vs/platform/storage/nod import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc'; import { startsWith } from 'vs/base/common/strings'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; -import { IBackupMainService } from 'vs/platform/backup/common/backup'; -import { HistoryMainService } from 'vs/platform/history/electron-main/historyMainService'; +import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; +import { HistoryMainService, IHistoryMainService } from 'vs/platform/history/electron-main/historyMainService'; import { URLService } from 'vs/platform/url/node/urlService'; -import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { WorkspacesMainService, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { statSync } from 'fs'; import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsIpc'; import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; @@ -78,6 +76,8 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; export class CodeApplication extends Disposable { @@ -92,7 +92,7 @@ export class CodeApplication extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @IStateService private readonly stateService: IStateService ) { @@ -109,7 +109,7 @@ export class CodeApplication extends Disposable { process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason)); // Dispose on shutdown - this.lifecycleService.onWillShutdown(() => this.dispose()); + this.lifecycleMainService.onWillShutdown(() => this.dispose()); // Contextmenu via IPC support registerContextMenuListener(); @@ -255,10 +255,10 @@ export class CodeApplication extends Disposable { this.logService.trace('IPC#vscode:exit', code); this.dispose(); - this.lifecycleService.kill(code); + this.lifecycleMainService.kill(code); }); - ipc.on('vscode:fetchShellEnv', async (event: Event) => { + ipc.on('vscode:fetchShellEnv', async (event: Electron.IpcMainEvent) => { const webContents = event.sender; try { @@ -275,14 +275,14 @@ export class CodeApplication extends Disposable { } }); - ipc.on('vscode:toggleDevTools', (event: Event) => event.sender.toggleDevTools()); - ipc.on('vscode:openDevTools', (event: Event) => event.sender.openDevTools()); + ipc.on('vscode:toggleDevTools', (event: Electron.IpcMainEvent) => event.sender.toggleDevTools()); + ipc.on('vscode:openDevTools', (event: Electron.IpcMainEvent) => event.sender.openDevTools()); - ipc.on('vscode:reloadWindow', (event: Event) => event.sender.reload()); + ipc.on('vscode:reloadWindow', (event: Electron.IpcMainEvent) => event.sender.reload()); // Some listeners after window opened (async () => { - await this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen); + await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen); // After waking up from sleep (after window opened) powerMonitor.on('resume', () => { @@ -361,7 +361,7 @@ export class CodeApplication extends Disposable { // Spawn shared process after the first window has opened and 3s have passed const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); const sharedProcessClient = sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main')); - this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { + this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { this._register(new RunOnceScheduler(async () => { const userEnv = await getShellEnvironment(this.logService, this.environmentService); @@ -424,7 +424,6 @@ export class CodeApplication extends Disposable { private async createServices(machineId: string, trueMachineId: string | undefined, sharedProcess: SharedProcess, sharedProcessClient: Promise>): Promise { const services = new ServiceCollection(); - // Files const fileService = this._register(new FileService(this.logService)); services.set(IFileService, fileService); @@ -451,17 +450,18 @@ export class CodeApplication extends Disposable { services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId, this.userEnv])); services.set(IWindowsService, new SyncDescriptor(WindowsService, [sharedProcess])); - services.set(ILaunchService, new SyncDescriptor(LaunchService)); + services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService)); const diagnosticsChannel = getDelayedChannel(sharedProcessClient.then(client => client.getChannel('diagnostics'))); services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService, [diagnosticsChannel])); - services.set(IIssueService, new SyncDescriptor(IssueService, [machineId, this.userEnv])); - services.set(IMenubarService, new SyncDescriptor(MenubarService)); + services.set(IIssueService, new SyncDescriptor(IssueMainService, [machineId, this.userEnv])); + services.set(IElectronService, new SyncDescriptor(ElectronMainService)); + services.set(IMenubarService, new SyncDescriptor(MenubarMainService)); const storageMainService = new StorageMainService(this.logService, this.environmentService); services.set(IStorageMainService, storageMainService); - this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close())); + this.lifecycleMainService.onWillShutdown(e => e.join(storageMainService.close())); const backupMainService = new BackupMainService(this.environmentService, this.configurationService, this.logService); services.set(IBackupMainService, backupMainService); @@ -474,7 +474,7 @@ export class CodeApplication extends Disposable { if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) { const channel = getDelayedChannel(sharedProcessClient.then(client => client.getChannel('telemetryAppender'))); const appender = combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService)); - const commonProperties = resolveCommonProperties(product.commit, pkg.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath); + const commonProperties = resolveCommonProperties(product.commit, product.version, machineId, product.msftInternalDomains, this.environmentService.installSourcePath); const piiPaths = this.environmentService.extensionsPath ? [this.environmentService.appRoot, this.environmentService.extensionsPath] : [this.environmentService.appRoot]; const config: ITelemetryServiceConfig = { appender, commonProperties, piiPaths, trueMachineId }; @@ -529,8 +529,8 @@ export class CodeApplication extends Disposable { private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise>): ICodeWindow[] { // Register more Main IPC services - const launchService = accessor.get(ILaunchService); - const launchChannel = new LaunchChannel(launchService); + const launchMainService = accessor.get(ILaunchMainService); + const launchChannel = new LaunchChannel(launchMainService); this.mainIpcServer.registerChannel('launch', launchChannel); // Register more Electron IPC services @@ -539,11 +539,15 @@ export class CodeApplication extends Disposable { electronIpcServer.registerChannel('update', updateChannel); const issueService = accessor.get(IIssueService); - const issueChannel = new IssueChannel(issueService); + const issueChannel = new SimpleServiceProxyChannel(issueService); electronIpcServer.registerChannel('issue', issueChannel); - const workspacesService = accessor.get(IWorkspacesMainService); - const workspacesChannel = new WorkspacesChannel(workspacesService); + const electronService = accessor.get(IElectronService); + const electronChannel = new SimpleServiceProxyChannel(electronService); + electronIpcServer.registerChannel('electron', electronChannel); + + const workspacesMainService = accessor.get(IWorkspacesMainService); + const workspacesChannel = new WorkspacesChannel(workspacesMainService); electronIpcServer.registerChannel('workspaces', workspacesChannel); const windowsService = accessor.get(IWindowsService); @@ -563,16 +567,15 @@ export class CodeApplication extends Disposable { const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService)); electronIpcServer.registerChannel('storage', storageChannel); - // Log level management - const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService)); - electronIpcServer.registerChannel('loglevel', logLevelChannel); - sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel)); + const loggerChannel = new LoggerChannel(accessor.get(ILogService)); + electronIpcServer.registerChannel('logger', loggerChannel); + sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); // ExtensionHost Debug broadcast service electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel()); // Signal phase: ready (services set) - this.lifecycleService.phase = LifecycleMainPhase.Ready; + this.lifecycleMainService.phase = LifecycleMainPhase.Ready; // Propagate to clients const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); @@ -682,7 +685,7 @@ export class CodeApplication extends Disposable { private afterWindowOpen(): void { // Signal phase: after window open - this.lifecycleService.phase = LifecycleMainPhase.AfterWindowOpen; + this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; // Remote Authorities this.handleRemoteAuthorities(); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 06549d2ce1..d8f8a2b985 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -7,12 +7,12 @@ import 'vs/platform/update/common/update.config.contribution'; import { app, dialog } from 'electron'; import { assign } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { parseMainProcessArgv } from 'vs/platform/environment/node/argvHelper'; import { addArg, createWaitMarkerFile } from 'vs/platform/environment/node/argv'; import { mkdirp } from 'vs/base/node/pfs'; import { validatePaths } from 'vs/code/node/paths'; -import { LifecycleService, ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { LifecycleMainService, ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net'; import { LaunchChannelClient } from 'vs/platform/launch/electron-main/launchService'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -27,7 +27,7 @@ import { EnvironmentService, xdgRuntimeDir } from 'vs/platform/environment/node/ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; import { IRequestService } from 'vs/platform/request/common/request'; -import { RequestService } from 'vs/platform/request/electron-main/requestService'; +import { RequestMainService } from 'vs/platform/request/electron-main/requestMainService'; import * as fs from 'fs'; import { CodeApplication } from 'vs/code/electron-main/app'; import { localize } from 'vs/nls'; @@ -116,13 +116,13 @@ class CodeMain { await instantiationService.invokeFunction(async accessor => { const environmentService = accessor.get(IEnvironmentService); const logService = accessor.get(ILogService); - const lifecycleService = accessor.get(ILifecycleService); + const lifecycleMainService = accessor.get(ILifecycleMainService); const configurationService = accessor.get(IConfigurationService); - const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleService, instantiationService, true); + const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleMainService, instantiationService, true); bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel()); - once(lifecycleService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose()); + once(lifecycleMainService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose()); return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup(); }); @@ -143,9 +143,9 @@ class CodeMain { services.set(ILogService, logService); services.set(IConfigurationService, new ConfigurationService(environmentService.settingsResource)); - services.set(ILifecycleService, new SyncDescriptor(LifecycleService)); + services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService)); services.set(IStateService, new SyncDescriptor(StateService)); - services.set(IRequestService, new SyncDescriptor(RequestService)); + services.set(IRequestService, new SyncDescriptor(RequestMainService)); services.set(IThemeMainService, new SyncDescriptor(ThemeMainService)); services.set(ISignService, new SyncDescriptor(SignService)); @@ -191,7 +191,7 @@ class CodeMain { return instanceEnvironment; } - private async doStartup(logService: ILogService, environmentService: IEnvironmentService, lifecycleService: ILifecycleService, instantiationService: IInstantiationService, retry: boolean): Promise { + private async doStartup(logService: ILogService, environmentService: IEnvironmentService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { // Try to setup a server for running. If that succeeds it means // we are the first instance to startup. Otherwise it is likely @@ -199,7 +199,7 @@ class CodeMain { let server: Server; try { server = await serve(environmentService.mainIPCHandle); - once(lifecycleService.onWillShutdown)(() => server.dispose()); + once(lifecycleMainService.onWillShutdown)(() => server.dispose()); } catch (error) { // Handle unexpected errors (the only expected error is EADDRINUSE that @@ -247,7 +247,7 @@ class CodeMain { throw error; } - return this.doStartup(logService, environmentService, lifecycleService, instantiationService, false); + return this.doStartup(logService, environmentService, lifecycleMainService, instantiationService, false); } // Tests from CLI require to be the only instance currently @@ -376,7 +376,7 @@ class CodeMain { private quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void { const logService = accessor.get(ILogService); - const lifecycleService = accessor.get(ILifecycleService); + const lifecycleMainService = accessor.get(ILifecycleMainService); let exitCode = 0; @@ -396,7 +396,7 @@ class CodeMain { } } - lifecycleService.kill(exitCode); + lifecycleMainService.kill(exitCode); } } diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index a47fce312d..a3d0a42705 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -10,7 +10,7 @@ import { BrowserWindow, ipcMain } from 'electron'; import { ISharedProcess } from 'vs/platform/windows/electron-main/windows'; import { Barrier } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; -import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -24,7 +24,7 @@ export class SharedProcess implements ISharedProcess { private readonly machineId: string, private userEnv: NodeJS.ProcessEnv, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, @IThemeMainService private readonly themeMainService: IThemeMainService ) { } @@ -36,9 +36,8 @@ export class SharedProcess implements ISharedProcess { backgroundColor: this.themeMainService.getBackgroundColor(), webPreferences: { images: false, - webaudio: false, - webgl: false, nodeIntegration: true, + webgl: false, disableBlinkFeatures: 'Auxclick' // do NOT change, allows us to identify this window as shared-process in the process explorer } }); @@ -69,7 +68,7 @@ export class SharedProcess implements ISharedProcess { const disposables = new DisposableStore(); - this.lifecycleService.onWillShutdown(() => { + this.lifecycleMainService.onWillShutdown(() => { disposables.dispose(); // Shut the shared process down when we are quitting diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 67cfad1759..9a8993a6dc 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -12,13 +12,14 @@ import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/ import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { ICodeWindow, IWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; -import { IWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; -import { IBackupMainService } from 'vs/platform/backup/common/backup'; +import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import * as perf from 'vs/base/common/performance'; import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/common/extensionGalleryService'; @@ -26,7 +27,6 @@ import { IThemeMainService } from 'vs/platform/theme/electron-main/themeMainServ import { endsWith } from 'vs/base/common/strings'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IFileService } from 'vs/platform/files/common/files'; -import pkg from 'vs/platform/product/node/package'; const RUN_TEXTMATE_IN_WORKER = false; @@ -310,7 +310,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { private handleMarketplaceRequests(): void { // Resolve marketplace headers - this.marketplaceHeadersPromise = resolveMarketplaceHeaders(pkg.version, this.environmentService, this.fileService); + this.marketplaceHeadersPromise = resolveMarketplaceHeaders(product.version, this.environmentService, this.fileService); // Inject headers when requests are incoming const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; @@ -561,7 +561,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { autoDetectHighContrast = false; } windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme(); - windowConfiguration.accessibilitySupport = app.isAccessibilitySupportEnabled(); + windowConfiguration.accessibilitySupport = app.accessibilitySupportEnabled; // Title style related windowConfiguration.maximized = this._win.isMaximized(); diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 637ced15d8..e720e9c70e 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -8,24 +8,26 @@ import { basename, normalize, join, dirname } from 'vs/base/common/path'; import { localize } from 'vs/nls'; import * as arrays from 'vs/base/common/arrays'; import { assign, mixin } from 'vs/base/common/objects'; -import { IBackupMainService, IEmptyWindowBackupInfo } from 'vs/platform/backup/common/backup'; +import { IBackupMainService } from 'vs/platform/backup/electron-main/backup'; +import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { IStateService } from 'vs/platform/state/common/state'; import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, FileFilter } from 'electron'; import { parseLineAndColumnAware } from 'vs/code/node/paths'; -import { ILifecycleService, UnloadReason, LifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen, isFileToOpen, isWorkspaceToOpen, isFolderToOpen } from 'vs/platform/windows/common/windows'; import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/code/node/windowsFinder'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; -import { IHistoryMainService, IRecent } from 'vs/platform/history/common/history'; +import { IRecent } from 'vs/platform/history/common/history'; +import { IHistoryMainService } from 'vs/platform/history/electron-main/historyMainService'; import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; @@ -36,7 +38,7 @@ import { exists, dirExists } from 'vs/base/node/pfs'; import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename, originalFSPath, hasTrailingPathSeparator, removeTrailingPathSeparator } from 'vs/base/common/resources'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage'; -import { getWorkspaceIdentifier } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { getWorkspaceIdentifier, IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { once } from 'vs/base/common/functional'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -186,7 +188,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { @ILogService private readonly logService: ILogService, @IStateService private readonly stateService: IStateService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IBackupMainService private readonly backupMainService: IBackupMainService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -205,8 +207,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService { this.dialogs = new Dialogs(stateService, this); this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, this); - this.lifecycleService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners()); - this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.installWindowsMutex()); + this.lifecycleMainService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners()); + this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.installWindowsMutex()); } private installWindowsMutex(): void { @@ -215,7 +217,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { try { const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex; const mutex = new WindowsMutex(win32MutexName); - once(this.lifecycleService.onWillShutdown)(() => mutex.release()); + once(this.lifecycleMainService.onWillShutdown)(() => mutex.release()); } catch (e) { this.logService.error(e); } @@ -249,8 +251,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } // Handle various lifecycle events around windows - this.lifecycleService.onBeforeWindowClose(window => this.onBeforeWindowClose(window)); - this.lifecycleService.onBeforeShutdown(() => this.onBeforeShutdown()); + this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window)); + this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown()); this.onWindowsCountChanged(e => { if (e.newCount - e.oldCount > 0) { // clear last closed window state when a new window opens. this helps on macOS where @@ -338,7 +340,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // See note on #onBeforeShutdown() for details how these events are flowing private onBeforeWindowClose(win: ICodeWindow): void { - if (this.lifecycleService.quitRequested) { + if (this.lifecycleMainService.quitRequested) { return; // during quit, many windows close in parallel so let it be handled in the before-quit handler } @@ -983,7 +985,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { private getRestoreWindowsSetting(): RestoreWindowsSetting { let restoreWindows: RestoreWindowsSetting; - if (this.lifecycleService.wasRestarted) { + if (this.lifecycleMainService.wasRestarted) { restoreWindows = 'all'; // always reopen all windows when an update was applied } else { const windowConfig = this.configurationService.getValue('window'); @@ -1326,7 +1328,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore else { - allowFullscreen = this.lifecycleService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen); + allowFullscreen = this.lifecycleMainService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen); } if (state.mode === WindowMode.Fullscreen && !allowFullscreen) { @@ -1362,7 +1364,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { window.win.on('closed', () => this.onWindowClosed(window!)); // Lifecycle - (this.lifecycleService as LifecycleService).registerWindow(window); + (this.lifecycleMainService as LifecycleMainService).registerWindow(window); } // Existing window @@ -1385,7 +1387,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // first and only load the new configuration if that was // not vetoed if (window.isReady) { - this.lifecycleService.unload(window, UnloadReason.LOAD).then(veto => { + this.lifecycleMainService.unload(window, UnloadReason.LOAD).then(veto => { if (!veto) { this.doOpenInBrowserWindow(window!, configuration, options); } @@ -1552,7 +1554,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { async reload(win: ICodeWindow, cli?: ParsedArgs): Promise { // Only reload when the window has not vetoed this - const veto = await this.lifecycleService.unload(win, UnloadReason.RELOAD); + const veto = await this.lifecycleMainService.unload(win, UnloadReason.RELOAD); if (!veto) { win.reload(undefined, cli); } @@ -1864,7 +1866,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // Otherwise: normal quit else { setTimeout(() => { - this.lifecycleService.quit(); + this.lifecycleMainService.quit(); }, 10 /* delay to unwind callback stack (IPC) */); } } @@ -1955,24 +1957,21 @@ class Dialogs { } showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): Promise { - return this.getDialogQueue(window).queue(() => { - return new Promise(resolve => { - const callback = (response: number, checkboxChecked: boolean) => { - resolve({ button: response, checkboxChecked }); - }; + return this.getDialogQueue(window).queue(async () => { + let result: Electron.MessageBoxReturnValue; + if (window) { + result = await dialog.showMessageBox(window.win, options); + } else { + result = await dialog.showMessageBox(options); + } - if (window) { - dialog.showMessageBox(window.win, options, callback); - } else { - dialog.showMessageBox(options, callback); - } - }); + return { button: result.response, checkboxChecked: result.checkboxChecked }; }); } showSaveDialog(options: Electron.SaveDialogOptions, window?: ICodeWindow): Promise { - function normalizePath(path: string): string { + function normalizePath(path: string | undefined): string | undefined { if (path && isMacintosh) { path = normalizeNFC(path); // normalize paths returned from the OS } @@ -1980,24 +1979,21 @@ class Dialogs { return path; } - return this.getDialogQueue(window).queue(() => { - return new Promise(resolve => { - const callback = (path: string) => { - resolve(normalizePath(path)); - }; + return this.getDialogQueue(window).queue(async () => { + let result: Electron.SaveDialogReturnValue; + if (window) { + result = await dialog.showSaveDialog(window.win, options); + } else { + result = await dialog.showSaveDialog(options); + } - if (window) { - dialog.showSaveDialog(window.win, options, callback); - } else { - dialog.showSaveDialog(options, callback); - } - }); + return normalizePath(result.filePath); }); } showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): Promise { - function normalizePaths(paths: string[]): string[] { + function normalizePaths(paths: string[] | undefined): string[] | undefined { if (paths && paths.length > 0 && isMacintosh) { paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS } @@ -2005,32 +2001,25 @@ class Dialogs { return paths; } - return this.getDialogQueue(window).queue(() => { - return new Promise(resolve => { + return this.getDialogQueue(window).queue(async () => { - // Ensure the path exists (if provided) - let validatePathPromise: Promise = Promise.resolve(); - if (options.defaultPath) { - validatePathPromise = exists(options.defaultPath).then(exists => { - if (!exists) { - options.defaultPath = undefined; - } - }); + // Ensure the path exists (if provided) + if (options.defaultPath) { + const pathExists = await exists(options.defaultPath); + if (!pathExists) { + options.defaultPath = undefined; } + } - // Show dialog and wrap as promise - validatePathPromise.then(() => { - const callback = (paths: string[]) => { - resolve(normalizePaths(paths)); - }; + // Show dialog + let result: Electron.OpenDialogReturnValue; + if (window) { + result = await dialog.showOpenDialog(window.win, options); + } else { + result = await dialog.showOpenDialog(options); + } - if (window) { - dialog.showOpenDialog(window.win, options, callback); - } else { - dialog.showOpenDialog(options, callback); - } - }); - }); + return normalizePaths(result.filePaths); }); } } diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 20a1ad5063..a46e1cb7c7 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -7,8 +7,7 @@ import { spawn, ChildProcess, SpawnOptions } from 'child_process'; import { buildHelpMessage, buildVersionMessage, addArg, createWaitMarkerFile, OPTIONS } from 'vs/platform/environment/node/argv'; import { parseCLIProcessArgv } from 'vs/platform/environment/node/argvHelper'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/common/product'; import * as paths from 'vs/base/common/path'; import * as os from 'os'; import * as fs from 'fs'; @@ -16,7 +15,7 @@ import { whenDeleted, writeFileSync } from 'vs/base/node/pfs'; import { findFreePort, randomPort } from 'vs/base/node/ports'; import { resolveTerminalEncoding } from 'vs/base/node/encoding'; import * as iconv from 'iconv-lite'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, isLinux } from 'vs/base/common/platform'; import { ProfilingSession, Target } from 'v8-inspect-profiler'; import { isString } from 'vs/base/common/types'; @@ -46,12 +45,12 @@ export async function main(argv: string[]): Promise { // Help if (args.help) { const executable = `${product.applicationName}${os.platform() === 'win32' ? '.exe' : ''}`; - console.log(buildHelpMessage(product.nameLong, executable, pkg.version, OPTIONS)); + console.log(buildHelpMessage(product.nameLong, executable, product.version, OPTIONS)); } // Version Info else if (args.version) { - console.log(buildVersionMessage(pkg.version, product.commit)); + console.log(buildVersionMessage(product.version, product.commit)); } // Extensions Management @@ -361,6 +360,10 @@ export async function main(argv: string[]): Promise { options['stdio'] = 'ignore'; } + if (isLinux) { + addArg(argv, '--no-sandbox'); // Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox + } + const child = spawn(process.execPath, argv.slice(2), options); if (args.wait && waitMarkerFilePath) { diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 43ecdb988f..1512375fa5 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/common/product'; import * as path from 'vs/base/common/path'; import * as semver from 'semver-umd'; @@ -46,7 +45,7 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id); @@ -350,7 +349,7 @@ export async function main(argv: ParsedArgs): Promise { const config: ITelemetryServiceConfig = { appender: combinedAppender(...appenders), - commonProperties: resolveCommonProperties(product.commit, pkg.version, stateService.getItem('telemetry.machineId'), product.msftInternalDomains, installSourcePath), + commonProperties: resolveCommonProperties(product.commit, product.version, stateService.getItem('telemetry.machineId'), product.msftInternalDomains, installSourcePath), piiPaths: extensionsPath ? [appRoot, extensionsPath] : [appRoot] }; diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index df7b412c69..f71d0a604a 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -13,7 +13,7 @@ import { equalsIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { IOpener, IOpenerService, IValidator } from 'vs/platform/opener/common/opener'; +import { IOpener, IOpenerService, IValidator, IExternalUriResolver } from 'vs/platform/opener/common/opener'; export class OpenerService extends Disposable implements IOpenerService { @@ -21,6 +21,7 @@ export class OpenerService extends Disposable implements IOpenerService { private readonly _openers = new LinkedList(); private readonly _validators = new LinkedList(); + private readonly _resolvers = new LinkedList(); constructor( @ICodeEditorService private readonly _editorService: ICodeEditorService, @@ -39,6 +40,11 @@ export class OpenerService extends Disposable implements IOpenerService { return { dispose: remove }; } + registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable { + const remove = this._resolvers.push(resolver); + return { dispose: remove }; + } + async open(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { // no scheme ?!? if (!resource.scheme) { @@ -118,7 +124,11 @@ export class OpenerService extends Disposable implements IOpenerService { } } - private _doOpenExternal(resource: URI): Promise { + private async _doOpenExternal(resource: URI): Promise { + for (const resolver of this._resolvers.toArray()) { + resource = await resolver.resolveExternalUri(resource); + } + dom.windowOpenNoOpener(encodeURI(resource.toString(true))); return Promise.resolve(true); diff --git a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts index b3dd48afd5..bd46aff4f2 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts +++ b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts @@ -101,15 +101,6 @@ export class LineNumbersOverlay extends DynamicViewOverlay { } const modelLineNumber = modelPosition.lineNumber; - if (!this._renderFinalNewline) { - const lineCount = this._context.model.getLineCount(); - const lineContent = this._context.model.getLineContent(modelLineNumber); - - if (modelLineNumber === lineCount && lineContent === '') { - return ''; - } - } - if (this._renderCustomLineNumbers) { return this._renderCustomLineNumbers(modelLineNumber); } @@ -146,10 +137,19 @@ export class LineNumbersOverlay extends DynamicViewOverlay { const visibleEndLineNumber = ctx.visibleRange.endLineNumber; const common = '
'; + const lineCount = this._context.model.getLineCount(); const output: string[] = []; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { const lineIndex = lineNumber - visibleStartLineNumber; + if (!this._renderFinalNewline) { + if (lineNumber === lineCount && this._context.model.getLineLength(lineNumber) === 0) { + // Do not render last (empty) line + output[lineIndex] = ''; + continue; + } + } + const renderLineNumber = this._getLineRenderLineNumber(lineNumber); if (renderLineNumber) { diff --git a/src/vs/editor/common/commands/shiftCommand.ts b/src/vs/editor/common/commands/shiftCommand.ts index 6589c265a3..da4e75c846 100644 --- a/src/vs/editor/common/commands/shiftCommand.ts +++ b/src/vs/editor/common/commands/shiftCommand.ts @@ -179,7 +179,7 @@ export class ShiftCommand implements ICommand { } this._addEditOperation(builder, new Range(lineNumber, 1, lineNumber, indentationEndIndex + 1), desiredIndent); - if (lineNumber === startLine) { + if (lineNumber === startLine && !this._selection.isEmpty()) { // Force the startColumn to stay put because we're inserting after it this._selectionStartColumnStaysPut = (this._selection.startColumn <= indentationEndIndex + 1); } @@ -226,7 +226,7 @@ export class ShiftCommand implements ICommand { this._addEditOperation(builder, new Range(lineNumber, 1, lineNumber, indentationEndIndex + 1), ''); } else { this._addEditOperation(builder, new Range(lineNumber, 1, lineNumber, 1), oneIndent); - if (lineNumber === startLine) { + if (lineNumber === startLine && !this._selection.isEmpty()) { // Force the startColumn to stay put because we're inserting after it this._selectionStartColumnStaysPut = (this._selection.startColumn === 1); } diff --git a/src/vs/editor/contrib/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index a576b6fde3..d19194e0e1 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -5,7 +5,7 @@ /* Checkbox */ -.monaco-checkbox .label { +.monaco-checkbox .codicon-selection { width: 12px; height: 12px; border: 1px solid black; @@ -24,7 +24,7 @@ border: 0; } -.monaco-checkbox .checkbox:checked + .label { +.monaco-checkbox .checkbox:checked + .codicon-selection { background-color: black; } @@ -166,6 +166,9 @@ background-position: center center; background-repeat: no-repeat; cursor: pointer; + display: flex; + align-items: center; + justify-content: center; } .monaco-editor .find-widget .button:not(.disabled):hover { @@ -200,14 +203,6 @@ display: none; } -.monaco-editor .find-widget .previous { - background-image: url('images/chevron-previous-light.svg'); -} - -.monaco-editor .find-widget .next { - background-image: url('images/chevron-next-light.svg'); -} - .monaco-editor .find-widget .disabled { opacity: 0.3; cursor: default; @@ -221,54 +216,32 @@ margin-left: 3px; } -.monaco-editor .find-widget .monaco-checkbox .label { - content: ''; - display: inline-block; - background-repeat: no-repeat; - background-position: center; - background-image: url('images/find-selection-light.svg'); +.monaco-editor .find-widget .monaco-checkbox .codicon-selection { + display: flex; + align-items: center; + justify-content: center; width: 20px; height: 20px; border: none; } -.monaco-editor .find-widget .monaco-checkbox .checkbox:disabled + .label { +.monaco-editor .find-widget .monaco-checkbox .checkbox:disabled + .codicon-selection { opacity: 0.3; cursor: default; } -.monaco-editor .find-widget .monaco-checkbox .checkbox:not(:disabled) + .label { +.monaco-editor .find-widget .monaco-checkbox .checkbox:not(:disabled) + .codicon-selection { cursor: pointer; } -.monaco-editor .find-widget .monaco-checkbox .checkbox:not(:disabled):hover:before + .label { +.monaco-editor .find-widget .monaco-checkbox .checkbox:not(:disabled):hover:before + .codicon-selection { background-color: #DDD; } -.monaco-editor .find-widget .monaco-checkbox .checkbox:checked + .label { +.monaco-editor .find-widget .monaco-checkbox .checkbox:checked + .codicon-selection { background-color: rgba(100, 100, 100, 0.2); } -.monaco-editor .find-widget .close-fw { - background-image: url('images/close-light.svg'); -} - -.monaco-editor .find-widget .expand { - background-image: url('images/tree-expanded-light.svg'); -} - -.monaco-editor .find-widget .collapse { - background-image: url('images/tree-collapsed-light.svg'); -} - -.monaco-editor .find-widget .replace { - background-image: url('images/replace-light.svg'); -} - -.monaco-editor .find-widget .replace-all { - background-image: url('images/replace-all-light.svg'); -} - .monaco-editor .find-widget > .replace-part { display: none; } @@ -329,52 +302,13 @@ margin-left: -4px; } -.monaco-editor.hc-black .find-widget .previous, -.monaco-editor.vs-dark .find-widget .previous { - background-image: url('images/chevron-previous-dark.svg'); -} - -.monaco-editor.hc-black .find-widget .next, -.monaco-editor.vs-dark .find-widget .next { - background-image: url('images/chevron-next-dark.svg'); -} - -.monaco-editor.hc-black .find-widget .monaco-checkbox .label, -.monaco-editor.vs-dark .find-widget .monaco-checkbox .label { - background-image: url('images/find-selection-dark.svg'); -} - -.monaco-editor.vs-dark .find-widget .monaco-checkbox .checkbox:not(:disabled):hover:before + .label { +.monaco-editor.vs-dark .find-widget .monaco-checkbox .checkbox:not(:disabled):hover:before + .codicon-selection { background-color: rgba(255, 255, 255, 0.1); } -.monaco-editor.hc-black .find-widget .close-fw, -.monaco-editor.vs-dark .find-widget .close-fw { - background-image: url('images/close-dark.svg'); -} - -.monaco-editor.hc-black .find-widget .replace, -.monaco-editor.vs-dark .find-widget .replace { - background-image: url('images/replace-dark.svg'); -} - -.monaco-editor.hc-black .find-widget .replace-all, -.monaco-editor.vs-dark .find-widget .replace-all { - background-image: url('images/replace-all-dark.svg'); -} - -.monaco-editor.hc-black .find-widget .expand, -.monaco-editor.vs-dark .find-widget .expand { - background-image: url('images/tree-expanded-dark.svg'); -} - -.monaco-editor.hc-black .find-widget .collapse, -.monaco-editor.vs-dark .find-widget .collapse { - background-image: url('images/tree-collapsed-dark.svg'); -} - .monaco-editor.hc-black .find-widget .button:not(.disabled):hover, -.monaco-editor.vs-dark .find-widget .button:not(.disabled):hover { +.monaco-editor.vs-dark .find-widget .button:not(.disabled):hover, +.monaco-editor.vs-dark .find-widget .monaco-checkbox:not(.disabled):hover { background-color: rgba(255, 255, 255, 0.1); } @@ -384,6 +318,6 @@ left: 2px; } -.monaco-editor.hc-black .find-widget .monaco-checkbox .checkbox:checked + .label { +.monaco-editor.hc-black .find-widget .monaco-checkbox .checkbox:checked + .codicon-selection { background-color: rgba(255, 255, 255, 0.1); } diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index a6b76b13d6..d3a5fe6758 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -453,8 +453,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._replaceAllBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty); dom.toggleClass(this._domNode, 'replaceToggled', this._isReplaceVisible); - this._toggleReplaceBtn.toggleClass('collapse', !this._isReplaceVisible); - this._toggleReplaceBtn.toggleClass('expand', this._isReplaceVisible); + this._toggleReplaceBtn.toggleClass('codicon-chevron-right', !this._isReplaceVisible); + this._toggleReplaceBtn.toggleClass('codicon-chevron-down', this._isReplaceVisible); this._toggleReplaceBtn.setExpanded(this._isReplaceVisible); let canReplace = !this._codeEditor.getOption(EditorOption.readOnly); @@ -642,6 +642,10 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas if (!this._isVisible) { return; } + if (!dom.isInDOM(this._domNode)) { + // the widget is not in the DOM + return; + } const layoutInfo = this._codeEditor.getLayoutInfo(); const editorContentWidth = layoutInfo.contentWidth; @@ -951,7 +955,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas // Previous button this._prevBtn = this._register(new SimpleButton({ label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction), - className: 'previous', + className: 'codicon codicon-arrow-up', onTrigger: () => { this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().then(undefined, onUnexpectedError); } @@ -960,7 +964,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas // Next button this._nextBtn = this._register(new SimpleButton({ label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction), - className: 'next', + className: 'codicon codicon-arrow-down', onTrigger: () => { this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().then(undefined, onUnexpectedError); } @@ -1000,7 +1004,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas // Close button this._closeBtn = this._register(new SimpleButton({ label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand), - className: 'close-fw', + className: 'codicon codicon-close', onTrigger: () => { this._state.change({ isRevealed: false, searchScope: null }, false); }, @@ -1063,7 +1067,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas // Replace one button this._replaceBtn = this._register(new SimpleButton({ label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction), - className: 'replace', + className: 'codicon codicon-replace', onTrigger: () => { this._controller.replace(); }, @@ -1078,7 +1082,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas // Replace all button this._replaceAllBtn = this._register(new SimpleButton({ label: NLS_REPLACE_ALL_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceAllAction), - className: 'replace-all', + className: 'codicon codicon-replace-all', onTrigger: () => { this._controller.replaceAll(); } @@ -1098,7 +1102,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas // Toggle replace button this._toggleReplaceBtn = this._register(new SimpleButton({ label: NLS_TOGGLE_REPLACE_MODE_BTN_LABEL, - className: 'toggle left', + className: 'codicon toggle left', onTrigger: () => { this._state.change({ isReplaceRevealed: !this._isReplaceVisible }, false); if (this._isReplaceVisible) { @@ -1108,8 +1112,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._showViewZone(); } })); - this._toggleReplaceBtn.toggleClass('expand', this._isReplaceVisible); - this._toggleReplaceBtn.toggleClass('collapse', !this._isReplaceVisible); + this._toggleReplaceBtn.toggleClass('codicon-chevron-down', this._isReplaceVisible); + this._toggleReplaceBtn.toggleClass('codicon-chevron-right', !this._isReplaceVisible); this._toggleReplaceBtn.setExpanded(this._isReplaceVisible); // Widget @@ -1227,7 +1231,7 @@ class SimpleCheckbox extends Widget { this._checkbox.tabIndex = -1; this._label = document.createElement('label'); - this._label.className = 'label'; + this._label.className = 'codicon codicon-selection'; // Connect the label and the checkbox. Checkbox will get checked when the label receives a click. this._label.htmlFor = this._checkbox.id; this._label.tabIndex = -1; diff --git a/src/vs/editor/contrib/find/images/tree-collapsed-dark.svg b/src/vs/editor/contrib/find/images/tree-collapsed-dark.svg deleted file mode 100644 index 17de497f33..0000000000 --- a/src/vs/editor/contrib/find/images/tree-collapsed-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/tree-collapsed-light.svg b/src/vs/editor/contrib/find/images/tree-collapsed-light.svg deleted file mode 100644 index 296499b8e5..0000000000 --- a/src/vs/editor/contrib/find/images/tree-collapsed-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/tree-expanded-dark.svg b/src/vs/editor/contrib/find/images/tree-expanded-dark.svg deleted file mode 100644 index a1df6a8d44..0000000000 --- a/src/vs/editor/contrib/find/images/tree-expanded-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/find/images/tree-expanded-light.svg b/src/vs/editor/contrib/find/images/tree-expanded-light.svg deleted file mode 100644 index e60e357f57..0000000000 --- a/src/vs/editor/contrib/find/images/tree-expanded-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/hover/hover.css b/src/vs/editor/contrib/hover/hover.css index 26f1531a19..5b63d89b20 100644 --- a/src/vs/editor/contrib/hover/hover.css +++ b/src/vs/editor/contrib/hover/hover.css @@ -37,6 +37,10 @@ margin: 8px 0; } +.monaco-editor-hover p > code { + font-family: var(--monaco-monospace-font); +} + .monaco-editor-hover hr { margin-top: 4px; margin-bottom: -6px; diff --git a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts index 036e51d296..30904395a7 100644 --- a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts @@ -928,6 +928,28 @@ suite('Editor Contrib - Line Operations', () => { model.dispose(); }); + test('issue #80736: Indenting while the cursor is at the start of a line of text causes the added spaces or tab to be selected', () => { + const model = createTextModel( + [ + 'Some text' + ].join('\n'), + { + insertSpaces: false, + } + ); + + withTestCodeEditor(null, { model: model }, (editor) => { + const indentLinesAction = new IndentLinesAction(); + editor.setPosition(new Position(1, 1)); + + indentLinesAction.run(null!, editor); + assert.equal(model.getLineContent(1), '\tSome text'); + assert.deepEqual(editor.getSelection(), new Selection(1, 2, 1, 2)); + }); + + model.dispose(); + }); + test('issue #62112: Delete line does not work properly when multiple cursors are on line', () => { const TEXT = [ 'a', diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index f0a4d60dc7..15c44fad54 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -332,7 +332,12 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget, private updateMaxHeight(): void { const height = Math.max(this.editor.getLayoutInfo().height / 4, 250); - this.element.style.maxHeight = `${height}px`; + const maxHeight = `${height}px`; + this.element.style.maxHeight = maxHeight; + const wrapper = this.element.getElementsByClassName('wrapper') as HTMLCollectionOf; + if (wrapper.length) { + wrapper[0].style.maxHeight = maxHeight; + } } } diff --git a/src/vs/editor/contrib/referenceSearch/media/close-dark.svg b/src/vs/editor/contrib/referenceSearch/media/close-dark.svg deleted file mode 100644 index bffa4e9dab..0000000000 --- a/src/vs/editor/contrib/referenceSearch/media/close-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/referenceSearch/media/close-light.svg b/src/vs/editor/contrib/referenceSearch/media/close-light.svg deleted file mode 100644 index b44dee661a..0000000000 --- a/src/vs/editor/contrib/referenceSearch/media/close-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css b/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css index cdc264c32e..ab8bce2bf9 100644 --- a/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css +++ b/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css @@ -56,10 +56,6 @@ margin: 0; } -.monaco-editor .peekview-widget .head .peekview-actions .action-label.icon.close-peekview-action { - background: url('close-light.svg') center center no-repeat; -} - .monaco-editor .peekview-widget > .body { border-top: 1px solid; position: relative; @@ -68,25 +64,20 @@ /* Dark Theme */ /* High Contrast Theme */ -.monaco-editor.hc-black .peekview-widget .head .peekview-actions .action-label.icon.close-peekview-action, -.monaco-editor.vs-dark .peekview-widget .head .peekview-actions .action-label.icon.close-peekview-action { - background: url('close-dark.svg') center center no-repeat; -} - -.monaco-editor .peekview-widget .peekview-actions .icon.chevron-up { +.monaco-editor .peekview-widget .peekview-actions .codicon.chevron-up { background: url('chevron-previous-light.svg') center center no-repeat; } -.vs-dark .monaco-editor .peekview-widget .peekview-actions .icon.chevron-up, -.hc-black .monaco-editor .peekview-widget .peekview-actions .icon.chevron-up { +.vs-dark .monaco-editor .peekview-widget .peekview-actions .codicon.chevron-up, +.hc-black .monaco-editor .peekview-widget .peekview-actions .codicon.chevron-up { background: url('chevron-previous-dark.svg') center center no-repeat; } -.monaco-editor .peekview-widget .peekview-actions .icon.chevron-down { +.monaco-editor .peekview-widget .peekview-actions .codicon.chevron-down { background: url('chevron-next-light.svg') center center no-repeat; } -.vs-dark .monaco-editor .peekview-widget .peekview-actions .icon.chevron-down, -.hc-black .monaco-editor .peekview-widget .peekview-actions .icon.chevron-down { +.vs-dark .monaco-editor .peekview-widget .peekview-actions .codicon.chevron-down, +.hc-black .monaco-editor .peekview-widget .peekview-actions .codicon.chevron-down { background: url('chevron-next-dark.svg') center center no-repeat; -} \ No newline at end of file +} diff --git a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts index 61a506b25b..56790e31b9 100644 --- a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts @@ -168,7 +168,7 @@ export abstract class PeekViewWidget extends ZoneWidget { this._actionbarWidget = new ActionBar(actionsContainer, actionBarOptions); this._disposables.add(this._actionbarWidget); - this._actionbarWidget.push(new Action('peekview.close', nls.localize('label.close', "Close"), 'close-peekview-action', true, () => { + this._actionbarWidget.push(new Action('peekview.close', nls.localize('label.close', "Close"), 'codicon-close', true, () => { this.dispose(); return Promise.resolve(); }), { label: false, icon: true }); diff --git a/src/vs/editor/contrib/suggest/media/suggest.css b/src/vs/editor/contrib/suggest/media/suggest.css index 663a8baebe..3ad81340d5 100644 --- a/src/vs/editor/contrib/suggest/media/suggest.css +++ b/src/vs/editor/contrib/suggest/media/suggest.css @@ -53,6 +53,10 @@ padding-left: 20px; } +.monaco-editor .suggest-widget > .details p > code { + font-family: var(--monaco-monospace-font); +} + /* Styles for Message element for when widget is loading or is empty */ .monaco-editor .suggest-widget > .message { padding-left: 22px; diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index a3489b4e3c..340215e251 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -186,6 +186,10 @@ export class SimpleDialogService implements IDialogService { public show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { return Promise.resolve({ choice: 0 }); } + + public about(): Promise { + return Promise.resolve(undefined); + } } export class SimpleNotificationService implements INotificationService { diff --git a/src/vs/editor/test/browser/commands/shiftCommand.test.ts b/src/vs/editor/test/browser/commands/shiftCommand.test.ts index 909db815f2..265786c631 100644 --- a/src/vs/editor/test/browser/commands/shiftCommand.test.ts +++ b/src/vs/editor/test/browser/commands/shiftCommand.test.ts @@ -92,7 +92,7 @@ suite('Editor Commands - ShiftCommand', () => { '', '123' ], - new Selection(1, 1, 1, 2) + new Selection(1, 2, 1, 2) ); }); diff --git a/src/vs/nls.d.ts b/src/vs/nls.d.ts index 14b2baed54..9b5dbc8f98 100644 --- a/src/vs/nls.d.ts +++ b/src/vs/nls.d.ts @@ -9,11 +9,17 @@ export interface ILocalizeInfo { } /** - * Localize a message. `message` can contain `{n}` notation where it is replaced by the nth value in `...args`. + * Localize a message. + * + * `message` can contain `{n}` notation where it is replaced by the nth value in `...args` + * For example, `localize('hello {0}', name)` */ export declare function localize(info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): string; /** - * Localize a message. `message` can contain `{n}` notation where it is replaced by the nth value in `...args`. + * Localize a message. + * + * `message` can contain `{n}` notation where it is replaced by the nth value in `...args` + * For example, `localize('hello {0}', name)` */ export declare function localize(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): string; diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 612e892729..cca94a72a4 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -93,6 +93,7 @@ export const enum MenuId { SearchContext, StatusBarWindowIndicatorMenu, TouchBarContext, + TitleBarContext, ViewItemContext, ViewTitle, ObjectExplorerItemContext, // {{SQL CARBON EDIT}} diff --git a/src/vs/platform/backup/common/backup.ts b/src/vs/platform/backup/electron-main/backup.ts similarity index 71% rename from src/vs/platform/backup/common/backup.ts rename to src/vs/platform/backup/electron-main/backup.ts index 145f9af1f9..59effce987 100644 --- a/src/vs/platform/backup/common/backup.ts +++ b/src/vs/platform/backup/electron-main/backup.ts @@ -6,27 +6,10 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; - -export interface ISerializedWorkspace { id: string; configURIPath: string; remoteAuthority?: string; } - -export interface IBackupWorkspacesFormat { - rootURIWorkspaces: ISerializedWorkspace[]; - folderURIWorkspaces: string[]; - emptyWorkspaceInfos: IEmptyWindowBackupInfo[]; - - // deprecated - folderWorkspaces?: string[]; // use folderURIWorkspaces instead - emptyWorkspaces?: string[]; - rootWorkspaces?: { id: string, configPath: string }[]; // use rootURIWorkspaces instead -} +import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; export const IBackupMainService = createDecorator('backupMainService'); -export interface IEmptyWindowBackupInfo { - backupFolder: string; - remoteAuthority?: string; -} - export interface IWorkspaceBackupInfo { workspace: IWorkspaceIdentifier; remoteAuthority?: string; @@ -48,4 +31,4 @@ export interface IBackupMainService { unregisterWorkspaceBackupSync(workspace: IWorkspaceIdentifier): void; unregisterFolderBackupSync(folderUri: URI): void; unregisterEmptyWindowBackupSync(backupFolder: string): void; -} \ No newline at end of file +} diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index d5b98fd279..62ab01884a 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -9,7 +9,8 @@ import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import { writeFileSync, writeFile, readFile, readdir, exists, rimraf, rename, RimRafMode } from 'vs/base/node/pfs'; import * as arrays from 'vs/base/common/arrays'; -import { IBackupMainService, IBackupWorkspacesFormat, IEmptyWindowBackupInfo, IWorkspaceBackupInfo } from 'vs/platform/backup/common/backup'; +import { IBackupMainService, IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; +import { IBackupWorkspacesFormat, IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFilesConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; diff --git a/src/vs/platform/backup/node/backup.ts b/src/vs/platform/backup/node/backup.ts new file mode 100644 index 0000000000..f2e5cd022f --- /dev/null +++ b/src/vs/platform/backup/node/backup.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface ISerializedWorkspace { id: string; configURIPath: string; remoteAuthority?: string; } + +export interface IBackupWorkspacesFormat { + rootURIWorkspaces: ISerializedWorkspace[]; + folderURIWorkspaces: string[]; + emptyWorkspaceInfos: IEmptyWindowBackupInfo[]; + + // deprecated + folderWorkspaces?: string[]; // use folderURIWorkspaces instead + emptyWorkspaces?: string[]; + rootWorkspaces?: { id: string, configPath: string }[]; // use rootURIWorkspaces instead +} + +export interface IEmptyWindowBackupInfo { + backupFolder: string; + remoteAuthority?: string; +} diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index dd9ccd4d96..d888c87bda 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -9,11 +9,12 @@ import * as fs from 'fs'; import * as os from 'os'; import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; -import { URI as Uri, URI } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; -import { IBackupWorkspacesFormat, ISerializedWorkspace, IWorkspaceBackupInfo } from 'vs/platform/backup/common/backup'; +import { IWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; +import { IBackupWorkspacesFormat, ISerializedWorkspace } from 'vs/platform/backup/node/backup'; import { HotExitConfiguration } from 'vs/platform/files/common/files'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { ConsoleLogMainService } from 'vs/platform/log/common/log'; @@ -24,7 +25,7 @@ import { Schemas } from 'vs/base/common/network'; suite('BackupMainService', () => { - function assertEqualUris(actual: Uri[], expected: Uri[]) { + function assertEqualUris(actual: URI[], expected: URI[]) { assert.deepEqual(actual.map(a => a.toString()), expected.map(a => a.toString())); } @@ -43,12 +44,12 @@ suite('BackupMainService', () => { this.workspacesJsonPath = backupWorkspacesPath; } - public toBackupPath(arg: Uri | string): string { - const id = arg instanceof Uri ? super.getFolderHash(arg) : arg; + public toBackupPath(arg: URI | string): string { + const id = arg instanceof URI ? super.getFolderHash(arg) : arg; return path.join(this.backupHome, id); } - public getFolderHash(folderUri: Uri): string { + public getFolderHash(folderUri: URI): string { return super.getFolderHash(folderUri); } @@ -81,7 +82,7 @@ suite('BackupMainService', () => { }; } - async function ensureFolderExists(uri: Uri): Promise { + async function ensureFolderExists(uri: URI): Promise { if (!fs.existsSync(uri.fsPath)) { fs.mkdirSync(uri.fsPath); } @@ -110,10 +111,10 @@ suite('BackupMainService', () => { return platform.isLinux ? p : p.toLowerCase(); } - const fooFile = Uri.file(platform.isWindows ? 'C:\\foo' : '/foo'); - const barFile = Uri.file(platform.isWindows ? 'C:\\bar' : '/bar'); + const fooFile = URI.file(platform.isWindows ? 'C:\\foo' : '/foo'); + const barFile = URI.file(platform.isWindows ? 'C:\\bar' : '/bar'); - const existingTestFolder1 = Uri.file(path.join(parentDir, 'folder1')); + const existingTestFolder1 = URI.file(path.join(parentDir, 'folder1')); let service: TestBackupMainService; let configService: TestConfigurationService; @@ -231,7 +232,7 @@ suite('BackupMainService', () => { const backupPathToMigrate = service.toBackupPath(fooFile); fs.mkdirSync(backupPathToMigrate); fs.writeFileSync(path.join(backupPathToMigrate, 'backup.txt'), 'Some Data'); - service.registerFolderBackupSync(Uri.file(backupPathToMigrate)); + service.registerFolderBackupSync(URI.file(backupPathToMigrate)); const workspaceBackupPath = service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate); @@ -247,12 +248,12 @@ suite('BackupMainService', () => { const backupPathToMigrate = service.toBackupPath(fooFile); fs.mkdirSync(backupPathToMigrate); fs.writeFileSync(path.join(backupPathToMigrate, 'backup.txt'), 'Some Data'); - service.registerFolderBackupSync(Uri.file(backupPathToMigrate)); + service.registerFolderBackupSync(URI.file(backupPathToMigrate)); const backupPathToPreserve = service.toBackupPath(barFile); fs.mkdirSync(backupPathToPreserve); fs.writeFileSync(path.join(backupPathToPreserve, 'backup.txt'), 'Some Data'); - service.registerFolderBackupSync(Uri.file(backupPathToPreserve)); + service.registerFolderBackupSync(URI.file(backupPathToPreserve)); const workspaceBackupPath = service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate); @@ -270,8 +271,8 @@ suite('BackupMainService', () => { test('migration folder path to URI makes sure to preserve existing backups', async () => { let path1 = path.join(parentDir, 'folder1'); let path2 = path.join(parentDir, 'FOLDER2'); - let uri1 = Uri.file(path1); - let uri2 = Uri.file(path2); + let uri1 = URI.file(path1); + let uri2 = URI.file(path2); if (!fs.existsSync(path1)) { fs.mkdirSync(path1); @@ -372,8 +373,8 @@ suite('BackupMainService', () => { }); test('getFolderBackupPaths() should return [] when files.hotExit = "onExitAndWindowClose"', async () => { - service.registerFolderBackupSync(Uri.file(fooFile.fsPath.toUpperCase())); - assertEqualUris(service.getFolderBackupPaths(), [Uri.file(fooFile.fsPath.toUpperCase())]); + service.registerFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase())); + assertEqualUris(service.getFolderBackupPaths(), [URI.file(fooFile.fsPath.toUpperCase())]); configService.setUserConfiguration('files.hotExit', HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE); await service.initialize(); assertEqualUris(service.getFolderBackupPaths(), []); @@ -591,11 +592,11 @@ suite('BackupMainService', () => { }); test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (folder workspace)', () => { - service.registerFolderBackupSync(Uri.file(fooFile.fsPath.toUpperCase())); - assertEqualUris(service.getFolderBackupPaths(), [Uri.file(fooFile.fsPath.toUpperCase())]); + service.registerFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase())); + assertEqualUris(service.getFolderBackupPaths(), [URI.file(fooFile.fsPath.toUpperCase())]); return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { const json = JSON.parse(buffer); - assert.deepEqual(json.folderURIWorkspaces, [Uri.file(fooFile.fsPath.toUpperCase()).toString()]); + assert.deepEqual(json.folderURIWorkspaces, [URI.file(fooFile.fsPath.toUpperCase()).toString()]); }); }); @@ -681,11 +682,11 @@ suite('BackupMainService', () => { } if (platform.isMacintosh) { - assert.equal(service.getFolderHash(Uri.file('/foo')), service.getFolderHash(Uri.file('/FOO'))); + assert.equal(service.getFolderHash(URI.file('/foo')), service.getFolderHash(URI.file('/FOO'))); } if (platform.isWindows) { - assert.equal(service.getFolderHash(Uri.file('c:\\foo')), service.getFolderHash(Uri.file('C:\\FOO'))); + assert.equal(service.getFolderHash(URI.file('c:\\foo')), service.getFolderHash(URI.file('C:\\FOO'))); } }); }); @@ -693,7 +694,7 @@ suite('BackupMainService', () => { suite('mixed path casing', () => { test('should handle case insensitive paths properly (registerWindowForBackupsSync) (folder workspace)', () => { service.registerFolderBackupSync(fooFile); - service.registerFolderBackupSync(Uri.file(fooFile.fsPath.toUpperCase())); + service.registerFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase())); if (platform.isLinux) { assert.equal(service.getFolderBackupPaths().length, 2); @@ -722,7 +723,7 @@ suite('BackupMainService', () => { // mixed case service.registerFolderBackupSync(fooFile); - service.unregisterFolderBackupSync(Uri.file(fooFile.fsPath.toUpperCase())); + service.unregisterFolderBackupSync(URI.file(fooFile.fsPath.toUpperCase())); if (platform.isLinux) { assert.equal(service.getFolderBackupPaths().length, 1); diff --git a/src/vs/workbench/services/credentials/common/credentials.ts b/src/vs/platform/credentials/common/credentials.ts similarity index 100% rename from src/vs/workbench/services/credentials/common/credentials.ts rename to src/vs/platform/credentials/common/credentials.ts diff --git a/src/vs/platform/debug/common/extensionHostDebug.ts b/src/vs/platform/debug/common/extensionHostDebug.ts index 4593debff5..2c7cb8c52b 100644 --- a/src/vs/platform/debug/common/extensionHostDebug.ts +++ b/src/vs/platform/debug/common/extensionHostDebug.ts @@ -6,6 +6,8 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { IRemoteConsoleLog } from 'vs/base/common/console'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export const IExtensionHostDebugService = createDecorator('extensionHostDebugService'); @@ -37,17 +39,19 @@ export interface IExtensionHostDebugService { _serviceBrand: undefined; reload(sessionId: string): void; - onReload: Event; + readonly onReload: Event; close(sessionId: string): void; - onClose: Event; + readonly onClose: Event; attachSession(sessionId: string, port: number, subId?: string): void; - onAttachSession: Event; + readonly onAttachSession: Event; logToSession(sessionId: string, log: IRemoteConsoleLog): void; - onLogToSession: Event; + readonly onLogToSession: Event; terminateSession(sessionId: string, subId?: string): void; - onTerminateSession: Event; + readonly onTerminateSession: Event; + + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise; } diff --git a/src/vs/platform/debug/common/extensionHostDebugIpc.ts b/src/vs/platform/debug/common/extensionHostDebugIpc.ts index 35870be158..11df86a82e 100644 --- a/src/vs/platform/debug/common/extensionHostDebugIpc.ts +++ b/src/vs/platform/debug/common/extensionHostDebugIpc.ts @@ -8,6 +8,8 @@ import { IReloadSessionEvent, ICloseSessionEvent, IAttachSessionEvent, ILogToSes import { Event, Emitter } from 'vs/base/common/event'; import { IRemoteConsoleLog } from 'vs/base/common/console'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export class ExtensionHostDebugBroadcastChannel implements IServerChannel { @@ -99,4 +101,10 @@ export class ExtensionHostDebugChannelClient extends Disposable implements IExte get onTerminateSession(): Event { return this.channel.listen('terminate'); } -} \ No newline at end of file + + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + // TODO@Isidor + //return this.channel.call('openExtensionDevelopmentHostWindow', [args, env]); + return Promise.resolve(); + } +} diff --git a/src/vs/platform/diagnostics/node/diagnosticsIpc.ts b/src/vs/platform/diagnostics/node/diagnosticsIpc.ts index 365e25431e..b023a18021 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsIpc.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsIpc.ts @@ -7,7 +7,7 @@ import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IRemoteDiagnosticInfo, IRemoteDiagnosticError, SystemInfo, PerformanceInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { IDiagnosticsService } from './diagnosticsService'; import { Event } from 'vs/base/common/event'; -import { IMainProcessInfo } from 'vs/platform/launch/common/launchService'; +import { IMainProcessInfo } from 'vs/platform/launch/common/launch'; import { IWorkspace } from 'vs/platform/workspace/common/workspace'; export class DiagnosticsChannel implements IServerChannel { diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index 169bc3c60c..f1cf312dc1 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -9,13 +9,12 @@ import { readdir, stat, exists, readFile } from 'fs'; import { join, basename } from 'vs/base/common/path'; import { parse, ParseError } from 'vs/base/common/json'; import { listProcesses } from 'vs/base/node/ps'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/common/product'; import { repeat, pad } from 'vs/base/common/strings'; import { isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ProcessItem } from 'vs/base/common/processes'; -import { IMainProcessInfo } from 'vs/platform/launch/common/launchService'; +import { IMainProcessInfo } from 'vs/platform/launch/common/launch'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -267,7 +266,7 @@ export class DiagnosticsService implements IDiagnosticsService { const GB = 1024 * MB; const output: string[] = []; - output.push(`Version: ${pkg.name} ${pkg.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`); + output.push(`Version: ${product.nameShort} ${product.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`); output.push(`OS Version: ${osLib.type()} ${osLib.arch()} ${osLib.release()}`); const cpus = osLib.cpus(); if (cpus && cpus.length > 0) { diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index dda74c3614..4c0bf84e71 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -172,6 +172,11 @@ export interface IDialogService { * option then promise with index `0` is returned. */ show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise; + + /** + * Present the about dialog to the user. + */ + about(): Promise; } export const IFileDialogService = createDecorator('fileDialogService'); @@ -235,11 +240,10 @@ export interface IFileDialogService { * Shows a open file dialog and returns the chosen file URI. */ showOpenDialog(options: IOpenDialogOptions): Promise; - } const MAX_CONFIRM_FILES = 10; -export function getConfirmMessage(start: string, resourcesToConfirm: URI[]): string { +export function getConfirmMessage(start: string, resourcesToConfirm: readonly URI[]): string { const message = [start]; message.push(''); message.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => basename(r))); diff --git a/src/vs/platform/dialogs/node/dialogIpc.ts b/src/vs/platform/dialogs/electron-browser/dialogIpc.ts similarity index 58% rename from src/vs/platform/dialogs/node/dialogIpc.ts rename to src/vs/platform/dialogs/electron-browser/dialogIpc.ts index 9f71b02b82..5ff5faa824 100644 --- a/src/vs/platform/dialogs/node/dialogIpc.ts +++ b/src/vs/platform/dialogs/electron-browser/dialogIpc.ts @@ -3,9 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IDialogService, IConfirmation, IConfirmationResult, IShowResult } from 'vs/platform/dialogs/common/dialogs'; -import Severity from 'vs/base/common/severity'; +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { Event } from 'vs/base/common/event'; export class DialogChannel implements IServerChannel { @@ -20,22 +19,8 @@ export class DialogChannel implements IServerChannel { switch (command) { case 'show': return this.dialogService.show(args![0], args![1], args![2]); case 'confirm': return this.dialogService.confirm(args![0]); + case 'about': return this.dialogService.about(); } return Promise.reject(new Error('invalid command')); } } - -export class DialogChannelClient implements IDialogService { - - _serviceBrand: undefined; - - constructor(private channel: IChannel) { } - - show(severity: Severity, message: string, options: string[]): Promise { - return this.channel.call('show', [severity, message, options]); - } - - confirm(confirmation: IConfirmation): Promise { - return this.channel.call('confirm', [confirmation]); - } -} diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 4621c0e333..5e1a5efa45 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -34,7 +34,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { constructor( private windowServer: IPCServer, private options: IDriverOptions, - @IWindowsMainService private readonly windowsService: IWindowsMainService + @IWindowsMainService private readonly windowsMainService: IWindowsMainService ) { } async registerWindowDriver(windowId: number): Promise { @@ -49,7 +49,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { } async getWindowIds(): Promise { - return this.windowsService.getWindows() + return this.windowsMainService.getWindows() .map(w => w.id) .filter(id => this.registeredWindowIds.has(id) && !this.reloadingWindowIds.has(id)); } @@ -57,7 +57,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { async capturePage(windowId: number): Promise { await this.whenUnfrozen(windowId); - const window = this.windowsService.getWindowById(windowId); + const window = this.windowsMainService.getWindowById(windowId); if (!window) { throw new Error('Invalid window'); } @@ -69,16 +69,16 @@ export class Driver implements IDriver, IWindowDriverRegistry { async reloadWindow(windowId: number): Promise { await this.whenUnfrozen(windowId); - const window = this.windowsService.getWindowById(windowId); + const window = this.windowsMainService.getWindowById(windowId); if (!window) { throw new Error('Invalid window'); } this.reloadingWindowIds.add(windowId); - this.windowsService.reload(window); + this.windowsMainService.reload(window); } async exitApplication(): Promise { - return this.windowsService.quit(); + return this.windowsMainService.quit(); } async dispatchKeybinding(windowId: number, keybinding: string): Promise { @@ -96,7 +96,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { throw new Error('ScanCodeBindings not supported'); } - const window = this.windowsService.getWindowById(windowId); + const window = this.windowsMainService.getWindowById(windowId); if (!window) { throw new Error('Invalid window'); } diff --git a/src/vs/platform/electron/electron-browser/electronService.ts b/src/vs/platform/electron/electron-browser/electronService.ts new file mode 100644 index 0000000000..76c09baf92 --- /dev/null +++ b/src/vs/platform/electron/electron-browser/electronService.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { createSimpleChannelProxy } from 'vs/platform/ipc/node/simpleIpcProxy'; + +export class ElectronService { + + _serviceBrand: undefined; + + constructor(@IMainProcessService mainProcessService: IMainProcessService) { + return createSimpleChannelProxy(mainProcessService.getChannel('electron')); + } +} diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts new file mode 100644 index 0000000000..349a7de184 --- /dev/null +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; +import { MessageBoxOptions, MessageBoxReturnValue, shell } from 'electron'; + +export class ElectronMainService implements IElectronService { + + _serviceBrand: undefined; + + constructor( + @IWindowsMainService private readonly windowsMainService: IWindowsMainService + ) { + } + + //#region Window + + private get window(): ICodeWindow | undefined { + return this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); + } + + async windowCount(): Promise { + return this.windowsMainService.getWindowCount(); + } + + //#endregion + + //#region Other + + async showMessageBox(options: MessageBoxOptions): Promise { + const result = await this.windowsMainService.showMessageBox(options, this.window); + + return { + response: result.button, + checkboxChecked: !!result.checkboxChecked + }; + } + + async showItemInFolder(path: string): Promise { + shell.showItemInFolder(path); + } + + //#endregion +} diff --git a/src/vs/platform/electron/node/electron.ts b/src/vs/platform/electron/node/electron.ts new file mode 100644 index 0000000000..3de54652d5 --- /dev/null +++ b/src/vs/platform/electron/node/electron.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MessageBoxOptions, MessageBoxReturnValue } from 'electron'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IElectronService = createDecorator('electronService'); + +export interface IElectronService { + + _serviceBrand: undefined; + + // Window + windowCount(): Promise; + + // Dialogs + showMessageBox(options: MessageBoxOptions): Promise; + + // OS + showItemInFolder(path: string): Promise; +} diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 167eaab5e7..cc5df64775 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -123,6 +123,7 @@ export interface IEnvironmentService { // user roaming data userRoamingDataHome: URI; settingsResource: URI; + settingsSyncPreviewResource: URI; keybindingsResource: URI; keyboardLayoutResource: URI; localeResource: URI; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 52caa265e2..5a478f5365 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -10,8 +10,7 @@ import * as os from 'os'; import * as path from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { memoize } from 'vs/base/common/decorators'; -import pkg from 'vs/platform/product/node/package'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { toLocalISOString } from 'vs/base/common/date'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { getPathFromAmdModule } from 'vs/base/common/amd'; @@ -26,16 +25,16 @@ function getNixIPCHandle(userDataPath: string, type: string): string { if (xdgRuntimeDir && !vscodePortable) { const scope = crypto.createHash('md5').update(userDataPath).digest('hex').substr(0, 8); - return path.join(xdgRuntimeDir, `vscode-${scope}-${pkg.version}-${type}.sock`); + return path.join(xdgRuntimeDir, `vscode-${scope}-${product.version}-${type}.sock`); } - return path.join(userDataPath, `${pkg.version}-${type}.sock`); + return path.join(userDataPath, `${product.version}-${type}.sock`); } function getWin32IPCHandle(userDataPath: string, type: string): string { const scope = crypto.createHash('md5').update(userDataPath).digest('hex'); - return `\\\\.\\pipe\\${scope}-${pkg.version}-${type}-sock`; + return `\\\\.\\pipe\\${scope}-${product.version}-${type}-sock`; } function getIPCHandle(userDataPath: string, type: string): string { @@ -117,6 +116,9 @@ export class EnvironmentService implements IEnvironmentService { @memoize get settingsResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'settings.json'); } + @memoize + get settingsSyncPreviewResource(): URI { return resources.joinPath(this.userRoamingDataHome, '.settings.json'); } + @memoize get machineSettingsHome(): URI { return URI.file(path.join(this.userDataPath, 'Machine')); } diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index e3ae633b4e..12610f14af 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -26,7 +26,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { optional } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index db428a58e6..26fd2e7e01 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -28,7 +28,7 @@ import { Limiter, createCancelablePromise, CancelablePromise, Queue } from 'vs/b import { Event, Emitter } from 'vs/base/common/event'; import * as semver from 'semver-umd'; import { URI } from 'vs/base/common/uri'; -import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/common/product'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtensionsManifestCache } from 'vs/platform/extensionManagement/node/extensionsManifestCache'; @@ -46,9 +46,6 @@ import { getPathFromAmdModule } from 'vs/base/common/amd'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; -// {{SQL CARBON EDIT} -import product from 'vs/platform/product/node/product'; - const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem'; const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser'; const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled'; @@ -210,8 +207,8 @@ export class ExtensionManagementService extends Disposable implements IExtension const identifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; let operation: InstallOperation = InstallOperation.Install; // {{SQL CARBON EDIT - Check VSCode and ADS version}} - if (manifest.engines && ((manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.vscodeVersion)) || (manifest.engines.azdata && !isEngineValid(manifest.engines.azdata, pkg.version)))) { - return Promise.reject(new Error(nls.localize('incompatible', "Unable to install version '{2}' of extension '{0}' as it is not compatible with Azure Data Studio '{1}'.", identifier.id, pkg.version, manifest.version))); + if (manifest.engines && ((manifest.engines.vscode && !isEngineValid(manifest.engines.vscode, product.vscodeVersion)) || (manifest.engines.azdata && !isEngineValid(manifest.engines.azdata, product.version)))) { + return Promise.reject(new Error(nls.localize('incompatible', "Unable to install extension '{0}' as it is not compatible with Azure Data Studio '{1}'.", identifier.id, product.version))); } const identifierWithVersion = new ExtensionIdentifierWithVersion(identifier, manifest.version); return this.getInstalled(ExtensionType.User) @@ -376,7 +373,7 @@ export class ExtensionManagementService extends Disposable implements IExtension const compatibleExtension = await this.galleryService.getCompatibleExtension(extension); if (!compatibleExtension) { - return Promise.reject(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE)); + return Promise.reject(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install '{0}' extension because it is not compatible with the current version of VS Code (version {1}).", extension.identifier.id, product.version), INSTALL_ERROR_INCOMPATIBLE)); } return compatibleExtension; diff --git a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts index cfb00237a3..fde16be7db 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts @@ -18,7 +18,7 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; -import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/common/product'; suite('Extension Gallery Service', () => { const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'extensiongalleryservice'); @@ -53,10 +53,10 @@ suite('Extension Gallery Service', () => { const args = ['--user-data-dir', marketplaceHome]; const environmentService = new EnvironmentService(parseArgs(args, OPTIONS), process.execPath); - return resolveMarketplaceHeaders(pkg.version, environmentService, fileService).then(headers => { + return resolveMarketplaceHeaders(product.version, environmentService, fileService).then(headers => { assert.ok(isUUID(headers['X-Market-User-Id'])); - return resolveMarketplaceHeaders(pkg.version, environmentService, fileService).then(headers2 => { + return resolveMarketplaceHeaders(product.version, environmentService, fileService).then(headers2 => { assert.equal(headers['X-Market-User-Id'], headers2['X-Market-User-Id']); }); }); diff --git a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts index 7944b70982..43115e0a63 100644 --- a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts +++ b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as chokidar from 'vscode-chokidar'; +import * as chokidar from 'chokidar'; import * as fs from 'fs'; import * as gracefulFs from 'graceful-fs'; gracefulFs.gracefulify(fs); diff --git a/src/vs/platform/history/common/history.ts b/src/vs/platform/history/common/history.ts index c082a5b5b7..7fbdfc505f 100644 --- a/src/vs/platform/history/common/history.ts +++ b/src/vs/platform/history/common/history.ts @@ -3,13 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event as CommonEvent } from 'vs/base/common/event'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; -import { IPath } from 'vs/platform/windows/common/windows'; - -export const IHistoryMainService = createDecorator('historyMainService'); export interface IRecentlyOpened { workspaces: Array; @@ -44,17 +39,3 @@ export function isRecentFolder(curr: IRecent): curr is IRecentFolder { export function isRecentFile(curr: IRecent): curr is IRecentFile { return curr.hasOwnProperty('fileUri'); } - - -export interface IHistoryMainService { - _serviceBrand: undefined; - - onRecentlyOpenedChange: CommonEvent; - - addRecentlyOpened(recents: IRecent[]): void; - getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened; - removeFromRecentlyOpened(paths: URI[]): void; - clearRecentlyOpened(): void; - - updateWindowsJumpList(): void; -} \ 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 ab2806962d..3fba419c56 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -12,8 +12,9 @@ import { getBaseLabel, getPathLabel } from 'vs/base/common/labels'; import { IPath } from 'vs/platform/windows/common/windows'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; -import { IWorkspaceIdentifier, IWorkspacesMainService, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IHistoryMainService, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile } from 'vs/platform/history/common/history'; +import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile } from 'vs/platform/history/common/history'; import { ThrottledDelayer } from 'vs/base/common/async'; import { isEqual as areResourcesEqual, dirname, originalFSPath, basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; @@ -22,7 +23,24 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { getSimpleWorkspaceLabel } from 'vs/platform/label/common/label'; import { toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData } from 'vs/platform/history/common/historyStorage'; import { exists } from 'vs/base/node/pfs'; -import { ILifecycleService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IHistoryMainService = createDecorator('historyMainService'); + +export interface IHistoryMainService { + + _serviceBrand: undefined; + + onRecentlyOpenedChange: CommonEvent; + + addRecentlyOpened(recents: IRecent[]): void; + getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened; + removeFromRecentlyOpened(paths: URI[]): void; + clearRecentlyOpened(): void; + + updateWindowsJumpList(): void; +} export class HistoryMainService implements IHistoryMainService { @@ -51,11 +69,11 @@ export class HistoryMainService implements IHistoryMainService { @ILogService private readonly logService: ILogService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILifecycleService lifecycleService: ILifecycleService + @ILifecycleMainService lifecycleMainService: ILifecycleMainService ) { this.macOSRecentDocumentsUpdater = new ThrottledDelayer(800); - lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList()); + lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList()); } private handleWindowsJumpList(): void { diff --git a/src/vs/platform/ipc/node/simpleIpcProxy.ts b/src/vs/platform/ipc/node/simpleIpcProxy.ts new file mode 100644 index 0000000000..a9e9c7d157 --- /dev/null +++ b/src/vs/platform/ipc/node/simpleIpcProxy.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; + +// +// Use both `SimpleServiceProxyChannel` and `createSimpleChannelProxy` +// for a very basic process <=> process communication over methods. +// + +export class SimpleServiceProxyChannel implements IServerChannel { + + private service: { [key: string]: unknown }; + + constructor(service: unknown) { + this.service = service as { [key: string]: unknown }; + } + + listen(_: unknown, event: string): Event { + throw new Error(`Events are currently unsupported by SimpleServiceProxyChannel: ${event}`); + } + + call(_: unknown, command: string, args: any[]): Promise { + const target = this.service[command]; + if (typeof target === 'function') { + return target.apply(this.service, args); + } + + throw new Error(`Method not found: ${command}`); + } +} + +export function createSimpleChannelProxy(channel: IChannel): T { + return new Proxy({}, { + get(_target, propKey, _receiver) { + if (typeof propKey === 'string') { + return function (...args: any[]) { + return channel.call(propKey, args); + }; + } + + throw new Error(`Unable to provide main channel proxy implementation for: ${String(propKey)}`); + } + }) as T; +} diff --git a/src/vs/platform/issue/electron-browser/issueService.ts b/src/vs/platform/issue/electron-browser/issueService.ts index 9764d021a4..656eb16d79 100644 --- a/src/vs/platform/issue/electron-browser/issueService.ts +++ b/src/vs/platform/issue/electron-browser/issueService.ts @@ -3,29 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IIssueService, IssueReporterData, ProcessExplorerData } from 'vs/platform/issue/node/issue'; +import { IIssueService } from 'vs/platform/issue/node/issue'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { createSimpleChannelProxy } from 'vs/platform/ipc/node/simpleIpcProxy'; -export class IssueService implements IIssueService { +export class IssueService { _serviceBrand: undefined; - private channel: IChannel; - constructor(@IMainProcessService mainProcessService: IMainProcessService) { - this.channel = mainProcessService.getChannel('issue'); - } - - openReporter(data: IssueReporterData): Promise { - return this.channel.call('openIssueReporter', data); - } - - openProcessExplorer(data: ProcessExplorerData): Promise { - return this.channel.call('openProcessExplorer', data); - } - - getSystemStatus(): Promise { - return this.channel.call('getSystemStatus'); + return createSimpleChannelProxy(mainProcessService.getChannel('issue')); } } diff --git a/src/vs/platform/issue/electron-main/issueService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts similarity index 84% rename from src/vs/platform/issue/electron-main/issueService.ts rename to src/vs/platform/issue/electron-main/issueMainService.ts index 297260707c..30a9909438 100644 --- a/src/vs/platform/issue/electron-main/issueService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -7,8 +7,8 @@ import { localize } from 'vs/nls'; import * as objects from 'vs/base/common/objects'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { IIssueService, IssueReporterData, IssueReporterFeatures, ProcessExplorerData } from 'vs/platform/issue/node/issue'; -import { BrowserWindow, ipcMain, screen, Event, dialog } from 'electron'; -import { ILaunchService } from 'vs/platform/launch/electron-main/launchService'; +import { BrowserWindow, ipcMain, screen, dialog } from 'electron'; +import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchService'; import { PerformanceInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -20,7 +20,7 @@ import { listProcesses } from 'vs/base/node/ps'; const DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; -export class IssueService implements IIssueService { +export class IssueMainService implements IIssueService { _serviceBrand: undefined; _issueWindow: BrowserWindow | null = null; _issueParentWindow: BrowserWindow | null = null; @@ -31,7 +31,7 @@ export class IssueService implements IIssueService { private machineId: string, private userEnv: IProcessEnvironment, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILaunchService private readonly launchService: ILaunchService, + @ILaunchMainService private readonly launchMainService: ILaunchMainService, @ILogService private readonly logService: ILogService, @IDiagnosticsService private readonly diagnosticsService: IDiagnosticsService, @IWindowsService private readonly windowsService: IWindowsService @@ -40,8 +40,8 @@ export class IssueService implements IIssueService { } private registerListeners(): void { - ipcMain.on('vscode:issueSystemInfoRequest', async (event: Event) => { - Promise.all([this.launchService.getMainProcessInfo(), this.launchService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) + ipcMain.on('vscode:issueSystemInfoRequest', async (event: Electron.IpcMainEvent) => { + Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) .then(result => { const [info, remoteData] = result; this.diagnosticsService.getSystemInfo(info, remoteData).then(msg => { @@ -50,13 +50,13 @@ export class IssueService implements IIssueService { }); }); - ipcMain.on('vscode:listProcesses', async (event: Event) => { + ipcMain.on('vscode:listProcesses', async (event: Electron.IpcMainEvent) => { const processes = []; try { - const mainPid = await this.launchService.getMainProcessId(); + const mainPid = await this.launchMainService.getMainProcessId(); processes.push({ name: localize('local', "Local"), rootProcess: await listProcesses(mainPid) }); - (await this.launchService.getRemoteDiagnostics({ includeProcesses: true })) + (await this.launchMainService.getRemoteDiagnostics({ includeProcesses: true })) .forEach(data => { if (isRemoteDiagnosticError(data)) { processes.push({ @@ -79,7 +79,7 @@ export class IssueService implements IIssueService { event.sender.send('vscode:listProcessesResponse', processes); }); - ipcMain.on('vscode:issueReporterClipboard', (event: Event) => { + ipcMain.on('vscode:issueReporterClipboard', (event: Electron.IpcMainEvent) => { const messageOptions = { message: localize('issueReporterWriteToClipboard', "There is too much data to send to GitHub. Would you like to write the information to the clipboard so that it can be pasted?"), type: 'warning', @@ -90,13 +90,14 @@ export class IssueService implements IIssueService { }; if (this._issueWindow) { - dialog.showMessageBox(this._issueWindow, messageOptions, response => { - event.sender.send('vscode:issueReporterClipboardResponse', response === 0); - }); + dialog.showMessageBox(this._issueWindow, messageOptions) + .then(result => { + event.sender.send('vscode:issueReporterClipboardResponse', result.response === 0); + }); } }); - ipcMain.on('vscode:issuePerformanceInfoRequest', (event: Event) => { + ipcMain.on('vscode:issuePerformanceInfoRequest', (event: Electron.IpcMainEvent) => { this.getPerformanceInfo().then(msg => { event.sender.send('vscode:issuePerformanceInfoResponse', msg); }); @@ -113,14 +114,15 @@ export class IssueService implements IIssueService { }; if (this._issueWindow) { - dialog.showMessageBox(this._issueWindow, messageOptions, (response) => { - if (response === 0) { - if (this._issueWindow) { - this._issueWindow.destroy(); - this._issueWindow = null; + dialog.showMessageBox(this._issueWindow, messageOptions) + .then(result => { + if (result.response === 0) { + if (this._issueWindow) { + this._issueWindow.destroy(); + this._issueWindow = null; + } } - } - }); + }); } }); @@ -148,14 +150,14 @@ export class IssueService implements IIssueService { this.windowsService.openExternal(arg); }); - ipcMain.on('vscode:closeIssueReporter', (event: Event) => { + ipcMain.on('vscode:closeIssueReporter', (event: Electron.IpcMainEvent) => { if (this._issueWindow) { this._issueWindow.close(); } }); - ipcMain.on('windowsInfoRequest', (event: Event) => { - this.launchService.getMainProcessInfo().then(info => { + ipcMain.on('windowsInfoRequest', (event: Electron.IpcMainEvent) => { + this.launchMainService.getMainProcessInfo().then(info => { event.sender.send('vscode:windowsInfoResponse', info.windows); }); }); @@ -178,7 +180,10 @@ export class IssueService implements IIssueService { x: position.x, y: position.y, title: localize('issueReporter', "Issue Reporter"), - backgroundColor: data.styles.backgroundColor || DEFAULT_BACKGROUND_COLOR + backgroundColor: data.styles.backgroundColor || DEFAULT_BACKGROUND_COLOR, + webPreferences: { + nodeIntegration: true + } }); this._issueWindow.setMenuBarVisibility(false); // workaround for now, until a menu is implemented @@ -224,7 +229,10 @@ export class IssueService implements IIssueService { x: position.x, y: position.y, backgroundColor: data.styles.backgroundColor, - title: localize('processExplorer', "Process Explorer") + title: localize('processExplorer', "Process Explorer"), + webPreferences: { + nodeIntegration: true + } }); this._processExplorerWindow.setMenuBarVisibility(false); @@ -260,7 +268,7 @@ export class IssueService implements IIssueService { } public async getSystemStatus(): Promise { - return Promise.all([this.launchService.getMainProcessInfo(), this.launchService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) + return Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) .then(result => { const [info, remoteData] = result; return this.diagnosticsService.getDiagnostics(info, remoteData); @@ -337,7 +345,7 @@ export class IssueService implements IIssueService { private getPerformanceInfo(): Promise { return new Promise(async (resolve, reject) => { - Promise.all([this.launchService.getMainProcessInfo(), this.launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]) + Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]) .then(result => { const [info, remoteData] = result; this.diagnosticsService.getPerformanceInfo(info, remoteData) diff --git a/src/vs/platform/issue/node/issueIpc.ts b/src/vs/platform/issue/node/issueIpc.ts deleted file mode 100644 index ae64e84335..0000000000 --- a/src/vs/platform/issue/node/issueIpc.ts +++ /dev/null @@ -1,30 +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 { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event } from 'vs/base/common/event'; -import { IIssueService } from 'vs/platform/issue/node/issue'; - -export class IssueChannel implements IServerChannel { - - constructor(private service: IIssueService) { } - - listen(_: unknown, event: string): Event { - throw new Error(`Event not found: ${event}`); - } - - call(_: unknown, command: string, arg?: any): Promise { - switch (command) { - case 'openIssueReporter': - return this.service.openReporter(arg); - case 'openProcessExplorer': - return this.service.openProcessExplorer(arg); - case 'getSystemStatus': - return this.service.getSystemStatus(); - } - - throw new Error(`Call not found: ${command}`); - } -} \ No newline at end of file diff --git a/src/vs/platform/launch/common/launchService.ts b/src/vs/platform/launch/common/launch.ts similarity index 99% rename from src/vs/platform/launch/common/launchService.ts rename to src/vs/platform/launch/common/launch.ts index 9e865a62f5..19add1c3e0 100644 --- a/src/vs/platform/launch/common/launchService.ts +++ b/src/vs/platform/launch/common/launch.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import { UriComponents } from 'vs/base/common/uri'; export interface IWindowInfo { @@ -18,4 +19,4 @@ export interface IMainProcessInfo { windows: IWindowInfo[]; screenReader: boolean; gpuFeatureStatus: any; -} \ No newline at end of file +} diff --git a/src/vs/platform/launch/electron-main/launchService.ts b/src/vs/platform/launch/electron-main/launchService.ts index 133e304bf9..b3ac21e37e 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchService.ts @@ -12,17 +12,17 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { OpenContext, IWindowSettings } from 'vs/platform/windows/common/windows'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { whenDeleted } from 'vs/base/node/pfs'; -import { IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; import { BrowserWindow, ipcMain, Event as IpcEvent, app } from 'electron'; import { Event } from 'vs/base/common/event'; import { coalesce } from 'vs/base/common/arrays'; import { IDiagnosticInfoOptions, IDiagnosticInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; -import { IMainProcessInfo, IWindowInfo } from 'vs/platform/launch/common/launchService'; +import { IMainProcessInfo, IWindowInfo } from 'vs/platform/launch/common/launch'; -export const ID = 'launchService'; -export const ILaunchService = createDecorator(ID); +export const ID = 'launchMainService'; +export const ILaunchMainService = createDecorator(ID); export interface IStartArguments { args: ParsedArgs; @@ -51,7 +51,7 @@ function parseOpenUrl(args: ParsedArgs): URI[] { return []; } -export interface ILaunchService { +export interface ILaunchMainService { _serviceBrand: undefined; start(args: ParsedArgs, userEnv: IProcessEnvironment): Promise; getMainProcessId(): Promise; @@ -62,7 +62,7 @@ export interface ILaunchService { export class LaunchChannel implements IServerChannel { - constructor(private service: ILaunchService) { } + constructor(private service: ILaunchMainService) { } listen(_: unknown, event: string): Event { throw new Error(`Event not found: ${event}`); @@ -91,7 +91,7 @@ export class LaunchChannel implements IServerChannel { } } -export class LaunchChannelClient implements ILaunchService { +export class LaunchChannelClient implements ILaunchMainService { _serviceBrand: undefined; @@ -118,7 +118,7 @@ export class LaunchChannelClient implements ILaunchService { } } -export class LaunchService implements ILaunchService { +export class LaunchMainService implements ILaunchMainService { _serviceBrand: undefined; @@ -276,7 +276,7 @@ export class LaunchService implements ILaunchService { mainPID: process.pid, mainArguments: process.argv.slice(1), windows, - screenReader: app.isAccessibilitySupportEnabled(), + screenReader: !!app.accessibilitySupportEnabled, gpuFeatureStatus: app.getGPUFeatureStatus() }); } diff --git a/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts b/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts index c0cf5edbc6..2ff4981aed 100644 --- a/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts +++ b/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts @@ -19,7 +19,7 @@ export class LifecycleService extends AbstractLifecycleService { _serviceBrand: undefined; - private shutdownReason: ShutdownReason; + private shutdownReason: ShutdownReason | undefined; constructor( @INotificationService private readonly notificationService: INotificationService, diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts similarity index 96% rename from src/vs/platform/lifecycle/electron-main/lifecycleMain.ts rename to src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts index 86a1006bf7..62b00af9d5 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMainService.ts @@ -14,7 +14,7 @@ import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; import { Barrier } from 'vs/base/common/async'; -export const ILifecycleService = createDecorator('lifecycleService'); +export const ILifecycleMainService = createDecorator('lifecycleMainService'); export const enum UnloadReason { CLOSE = 1, @@ -38,7 +38,7 @@ export interface ShutdownEvent { join(promise: Promise): void; } -export interface ILifecycleService { +export interface ILifecycleMainService { _serviceBrand: undefined; @@ -129,7 +129,7 @@ export const enum LifecycleMainPhase { AfterWindowOpen = 3 } -export class LifecycleService extends Disposable implements ILifecycleService { +export class LifecycleMainService extends Disposable implements ILifecycleMainService { _serviceBrand: undefined; @@ -178,10 +178,10 @@ export class LifecycleService extends Disposable implements ILifecycleService { } private handleRestarted(): void { - this._wasRestarted = !!this.stateService.getItem(LifecycleService.QUIT_FROM_RESTART_MARKER); + this._wasRestarted = !!this.stateService.getItem(LifecycleMainService.QUIT_FROM_RESTART_MARKER); if (this._wasRestarted) { - this.stateService.removeItem(LifecycleService.QUIT_FROM_RESTART_MARKER); // remove the marker right after if found + this.stateService.removeItem(LifecycleMainService.QUIT_FROM_RESTART_MARKER); // remove the marker right after if found } } @@ -468,7 +468,7 @@ export class LifecycleService extends Disposable implements ILifecycleService { // Remember the reason for quit was to restart if (fromUpdate) { - this.stateService.setItem(LifecycleService.QUIT_FROM_RESTART_MARKER, true); + this.stateService.setItem(LifecycleMainService.QUIT_FROM_RESTART_MARKER, true); } this.pendingQuitPromise = new Promise(resolve => { @@ -507,7 +507,7 @@ export class LifecycleService extends Disposable implements ILifecycleService { if (!quitVetoed) { // Remember the reason for quit was to restart - this.stateService.setItem(LifecycleService.QUIT_FROM_RESTART_MARKER, true); + this.stateService.setItem(LifecycleMainService.QUIT_FROM_RESTART_MARKER, true); // Windows: we are about to restart and as such we need to restore the original // current working directory we had on startup to get the exact same startup diff --git a/src/vs/platform/localizations/node/localizations.ts b/src/vs/platform/localizations/node/localizations.ts index 25144e562e..c5e1987b8c 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/localizations/node/localizations.ts @@ -12,7 +12,7 @@ import { Queue } from 'vs/base/common/async'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ILogService } from 'vs/platform/log/common/log'; import { isValidLocalization, ILocalizationsService, LanguageType } from 'vs/platform/localizations/common/localizations'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { distinct, equals } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index 86cedcca7f..9ebc4e3d43 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -8,6 +8,7 @@ import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { isWindows } from 'vs/base/common/platform'; import { Event, Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; export const ILogService = createServiceDecorator('logService'); @@ -183,6 +184,54 @@ export class ConsoleLogService extends AbstractLogService implements ILogService dispose(): void { } } +export class ConsoleLogInMainService extends AbstractLogService implements ILogService { + + _serviceBrand: undefined; + + constructor(private readonly client: LoggerChannelClient, logLevel: LogLevel = DEFAULT_LOG_LEVEL) { + super(); + this.setLevel(logLevel); + } + + trace(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Trace) { + this.client.consoleLog('trace', [message, ...args]); + } + } + + debug(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Debug) { + this.client.consoleLog('debug', [message, ...args]); + } + } + + info(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Info) { + this.client.consoleLog('info', [message, ...args]); + } + } + + warn(message: string | Error, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Warning) { + this.client.consoleLog('warn', [message, ...args]); + } + } + + error(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Error) { + this.client.consoleLog('error', [message, ...args]); + } + } + + critical(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Critical) { + this.client.consoleLog('critical', [message, ...args]); + } + } + + dispose(): void { } +} + export class MultiplexLogService extends AbstractLogService implements ILogService { _serviceBrand: undefined; @@ -326,4 +375,4 @@ export function getLogLevel(environmentService: IEnvironmentService): LogLevel { } } return DEFAULT_LOG_LEVEL; -} \ No newline at end of file +} diff --git a/src/vs/platform/log/common/logIpc.ts b/src/vs/platform/log/common/logIpc.ts index 967d08b78b..aff760b50c 100644 --- a/src/vs/platform/log/common/logIpc.ts +++ b/src/vs/platform/log/common/logIpc.ts @@ -7,7 +7,7 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { LogLevel, ILogService, DelegatedLogService } from 'vs/platform/log/common/log'; import { Event } from 'vs/base/common/event'; -export class LogLevelSetterChannel implements IServerChannel { +export class LoggerChannel implements IServerChannel { onDidChangeLogLevel: Event; @@ -26,13 +26,32 @@ export class LogLevelSetterChannel implements IServerChannel { call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'setLevel': this.service.setLevel(arg); return Promise.resolve(); + case 'consoleLog': this.consoleLog(arg[0], arg[1]); return Promise.resolve(); } throw new Error(`Call not found: ${command}`); } + + private consoleLog(severity: string, args: string[]): void { + let consoleFn = console.log; + + switch (severity) { + case 'error': + consoleFn = console.error; + break; + case 'warn': + consoleFn = console.warn; + break; + case 'info': + consoleFn = console.info; + break; + } + + consoleFn.call(console, ...args); + } } -export class LogLevelSetterChannelClient { +export class LoggerChannelClient { constructor(private channel: IChannel) { } @@ -43,12 +62,16 @@ export class LogLevelSetterChannelClient { setLevel(level: LogLevel): void { this.channel.call('setLevel', level); } + + consoleLog(severity: string, args: string[]): void { + this.channel.call('consoleLog', [severity, args]); + } } export class FollowerLogService extends DelegatedLogService implements ILogService { _serviceBrand: undefined; - constructor(private master: LogLevelSetterChannelClient, logService: ILogService) { + constructor(private master: LoggerChannelClient, logService: ILogService) { super(logService); this._register(master.onDidChangeLogLevel(level => logService.setLevel(level))); } @@ -56,4 +79,4 @@ export class FollowerLogService extends DelegatedLogService implements ILogServi setLevel(level: LogLevel): void { this.master.setLevel(level); } -} \ No newline at end of file +} diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 1dafdd4c5f..0a7566ac1f 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -11,16 +11,16 @@ import { OpenContext, IRunActionInWindowRequest, getTitleBarStyle, IRunKeybindin import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService, StateType } from 'vs/platform/update/common/update'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { RunOnceScheduler } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; import { mnemonicMenuLabel as baseMnemonicLabel } from 'vs/base/common/labels'; import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows'; -import { IHistoryMainService } from 'vs/platform/history/common/history'; +import { IHistoryMainService } from 'vs/platform/history/electron-main/historyMainService'; import { IMenubarData, IMenubarKeybinding, MenubarMenuItem, isMenubarMenuItemSeparator, isMenubarMenuItemSubmenu, isMenubarMenuItemAction, IMenubarMenu, isMenubarMenuItemUriAction } from 'vs/platform/menubar/node/menubar'; import { URI } from 'vs/base/common/uri'; import { IStateService } from 'vs/platform/state/common/state'; -import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; const telemetryFrom = 'menu'; @@ -68,7 +68,7 @@ export class Menubar { @ITelemetryService private readonly telemetryService: ITelemetryService, @IHistoryMainService private readonly historyMainService: IHistoryMainService, @IStateService private readonly stateService: IStateService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService ) { this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0); @@ -160,7 +160,7 @@ export class Menubar { private registerListeners(): void { // Keep flag when app quits - this.lifecycleService.onWillShutdown(() => this.willShutdown = true); + this.lifecycleMainService.onWillShutdown(() => this.willShutdown = true); // // Listen to some events from window service to update menu this.windowsMainService.onWindowsCountChanged(e => this.onWindowsCountChanged(e)); @@ -368,7 +368,7 @@ export class Menubar { const servicesMenu = new Menu(); const services = new MenuItem({ label: nls.localize('mServices', "Services"), role: 'services', submenu: servicesMenu }); const hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", product.nameLong), role: 'hide', accelerator: 'Command+H' }); - const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideothers', accelerator: 'Command+Alt+H' }); + const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideOthers', accelerator: 'Command+Alt+H' }); const showAll = new MenuItem({ label: nls.localize('mShowAll', "Show All"), role: 'unhide' }); const quit = new MenuItem(this.likeAction('workbench.action.quit', { label: nls.localize('miQuit', "Quit {0}", product.nameLong), click: () => { @@ -503,7 +503,7 @@ export class Menubar { }, false)); } - private isOptionClick(event: Electron.Event): boolean { + private isOptionClick(event: Electron.KeyboardEvent): boolean { return !!(event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey)))); } @@ -597,7 +597,7 @@ export class Menubar { } } - private static _menuItemIsTriggeredViaKeybinding(event: Electron.Event, userSettingsLabel: string): boolean { + private static _menuItemIsTriggeredViaKeybinding(event: Electron.KeyboardEvent, userSettingsLabel: string): boolean { // The event coming in from Electron will inform us only about the modifier keys pressed. // The strategy here is to check if the modifier keys match those of the keybinding, // since it is highly unlikely to use modifier keys when clicking with the mouse diff --git a/src/vs/platform/menubar/electron-main/menubarService.ts b/src/vs/platform/menubar/electron-main/menubarMainService.ts similarity index 94% rename from src/vs/platform/menubar/electron-main/menubarService.ts rename to src/vs/platform/menubar/electron-main/menubarMainService.ts index f139b522db..e227d807c1 100644 --- a/src/vs/platform/menubar/electron-main/menubarService.ts +++ b/src/vs/platform/menubar/electron-main/menubarMainService.ts @@ -8,7 +8,8 @@ import { Menubar } from 'vs/platform/menubar/electron-main/menubar'; import { ILogService } from 'vs/platform/log/common/log'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -export class MenubarService implements IMenubarService { +export class MenubarMainService implements IMenubarService { + _serviceBrand: undefined; private _menubar: Menubar; @@ -30,4 +31,4 @@ export class MenubarService implements IMenubarService { return Promise.resolve(undefined); } -} \ No newline at end of file +} diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index fa40e7a678..28cb4de161 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; export const IOpenerService = createDecorator('openerService'); @@ -18,6 +18,10 @@ export interface IValidator { shouldOpen(resource: URI): Promise; } +export interface IExternalUriResolver { + resolveExternalUri(resource: URI): Promise; +} + export interface IOpenerService { _serviceBrand: undefined; @@ -29,10 +33,15 @@ export interface IOpenerService { /** * Register a participant that can validate if the URI resource be opened. - * validators are run before openers. + * Validators are run before openers. */ registerValidator(validator: IValidator): IDisposable; + /** + * Register a participant that can resolve an external URI resource to be opened. + */ + registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable; + /** * Opens a resource, like a webaddress, a document uri, or executes command. * @@ -45,7 +54,8 @@ export interface IOpenerService { export const NullOpenerService: IOpenerService = Object.freeze({ _serviceBrand: undefined, - registerOpener() { return { dispose() { } }; }, - registerValidator() { return { dispose() { } }; }, + registerOpener() { return Disposable.None; }, + registerValidator() { return Disposable.None; }, + registerExternalUriResolver() { return Disposable.None; }, open() { return Promise.resolve(false); }, }); diff --git a/src/vs/platform/product/browser/product.ts b/src/vs/platform/product/browser/product.ts deleted file mode 100644 index 2370c9d615..0000000000 --- a/src/vs/platform/product/browser/product.ts +++ /dev/null @@ -1,21 +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 { IProductConfiguration } from 'vs/platform/product/common/product'; -import { assign } from 'vs/base/common/objects'; - -// Built time configuration (do NOT modify) -const product = { /*BUILD->INSERT_PRODUCT_CONFIGURATION*/ } as IProductConfiguration; - -// Running out of sources -if (Object.keys(product).length === 0) { - assign(product, { - version: '1.39.0-dev', - nameLong: 'Visual Studio Code Web Dev', - nameShort: 'VSCode Web Dev' - }); -} - -export default product; diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 40af9658f4..5cb97af4c8 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -3,125 +3,57 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IProductConfiguration } from 'vs/platform/product/common/productService'; +import { assign } from 'vs/base/common/objects'; +import { isWeb } from 'vs/base/common/platform'; +import * as path from 'vs/base/common/path'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { env } from 'vs/base/common/process'; -export const IProductService = createDecorator('productService'); +let product: IProductConfiguration; -export interface IProductService extends Readonly { +// Web +if (isWeb) { - _serviceBrand: undefined; + // Built time configuration (do NOT modify) + product = { /*BUILD->INSERT_PRODUCT_CONFIGURATION*/ } as IProductConfiguration; + // Running out of sources + if (Object.keys(product).length === 0) { + assign(product, { + version: '1.39.0-dev', + nameLong: 'Visual Studio Code Web Dev', + nameShort: 'VSCode Web Dev' + }); + } } -export interface IProductConfiguration { - readonly version: string; - readonly date?: string; - readonly quality?: string; - readonly commit?: string; +// Node: AMD loader +else if (typeof require !== 'undefined' && typeof require.__$__nodeRequire === 'function') { - readonly nameShort: string; - readonly nameLong: string; + // Obtain values from product.json and package.json + const rootPath = path.dirname(getPathFromAmdModule(require, '')); - readonly win32AppUserModelId?: string; - readonly win32MutexName?: string; - readonly applicationName: string; + product = assign({}, require.__$__nodeRequire(path.join(rootPath, 'product.json')) as IProductConfiguration); + const pkg = require.__$__nodeRequire(path.join(rootPath, 'package.json')) as { version: string; }; - readonly urlProtocol: string; - readonly dataFolderName: string; + // Running out of sources + if (env['VSCODE_DEV']) { + assign(product, { + nameShort: `${product.nameShort} Dev`, + nameLong: `${product.nameLong} Dev`, + dataFolderName: `${product.dataFolderName}-dev` + }); + } - readonly downloadUrl?: string; - readonly updateUrl?: string; - readonly target?: string; - - readonly settingsSearchBuildId?: number; - readonly settingsSearchUrl?: string; - - readonly experimentsUrl?: string; - - readonly extensionsGallery?: { - readonly serviceUrl: string; - readonly itemUrl: string; - readonly controlUrl: string; - readonly recommendationsUrl: string; - }; - - readonly extensionTips?: { [id: string]: string; }; - readonly extensionImportantTips?: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; }; - readonly exeBasedExtensionTips?: { [id: string]: IExeBasedExtensionTip; }; - readonly extensionKeywords?: { [extension: string]: readonly string[]; }; - readonly keymapExtensionTips?: readonly string[]; - - readonly recommendedExtensions: string[]; // {{SQL CARBON EDIT}} - readonly recommendedExtensionsByScenario: { [area: string]: Array }; // {{SQL CARBON EDIT}} - readonly vscodeVersion: string; // {{SQL CARBON EDIT}} add vscode version - readonly gettingStartedUrl: string; // {SQL CARBON EDIT} - - readonly crashReporter?: { - readonly companyName: string; - readonly productName: string; - }; - - readonly welcomePage?: string; - - readonly enableTelemetry?: boolean; - readonly aiConfig?: { - readonly asimovKey: string; - }; - - readonly sendASmile?: { - readonly reportIssueUrl: string, - readonly requestFeatureUrl: string - }; - - readonly documentationUrl?: string; - readonly releaseNotesUrl?: string; - readonly keyboardShortcutsUrlMac?: string; - readonly keyboardShortcutsUrlLinux?: string; - readonly keyboardShortcutsUrlWin?: string; - readonly introductoryVideosUrl?: string; - readonly tipsAndTricksUrl?: string; - readonly newsletterSignupUrl?: string; - readonly twitterUrl?: string; - readonly requestFeatureUrl?: string; - readonly reportIssueUrl?: string; - readonly licenseUrl?: string; - readonly privacyStatementUrl?: string; - readonly telemetryOptOutUrl?: string; - - readonly npsSurveyUrl?: string; - readonly surveys?: readonly ISurveyData[]; - - readonly checksums?: { [path: string]: string; }; - readonly checksumFailMoreInfoUrl?: string; - - readonly hockeyApp?: { - readonly 'win32-ia32': string; - readonly 'win32-x64': string; - readonly 'linux-x64': string; - readonly 'darwin': string; - }; - - readonly portable?: string; - - readonly uiExtensions?: readonly string[]; - readonly extensionAllowedProposedApi?: readonly string[]; - - readonly msftInternalDomains?: string[]; - readonly linkProtectionTrustedDomains?: readonly string[]; + assign(product, { + version: pkg.version + }); } -export interface IExeBasedExtensionTip { - friendlyName: string; - windowsPath?: string; - recommendations: readonly string[]; - important?: boolean; - exeFriendlyName?: string; +// Unknown +else { + throw new Error('Unable to resolve product configuration'); } -export interface ISurveyData { - surveyId: string; - surveyUrl: string; - languageId: string; - editCount: number; - userProbability: number; -} +export default product; diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts new file mode 100644 index 0000000000..750b92e29d --- /dev/null +++ b/src/vs/platform/product/common/productService.ts @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IProductService = createDecorator('productService'); + +export interface IProductService extends Readonly { + + _serviceBrand: undefined; + +} + +export interface IProductConfiguration { + readonly version: string; + readonly date?: string; + readonly quality?: string; + readonly commit?: string; + + readonly nameShort: string; + readonly nameLong: string; + + readonly win32AppUserModelId?: string; + readonly win32MutexName?: string; + readonly applicationName: string; + + readonly urlProtocol: string; + readonly dataFolderName: string; + + readonly downloadUrl?: string; + readonly updateUrl?: string; + readonly target?: string; + + readonly settingsSearchBuildId?: number; + readonly settingsSearchUrl?: string; + + readonly experimentsUrl?: string; + + readonly extensionsGallery?: { + readonly serviceUrl: string; + readonly itemUrl: string; + readonly controlUrl: string; + readonly recommendationsUrl: string; + }; + + readonly extensionTips?: { [id: string]: string; }; + readonly extensionImportantTips?: { [id: string]: { name: string; pattern: string; isExtensionPack?: boolean }; }; + readonly exeBasedExtensionTips?: { [id: string]: IExeBasedExtensionTip; }; + readonly extensionKeywords?: { [extension: string]: readonly string[]; }; + readonly keymapExtensionTips?: readonly string[]; + + readonly recommendedExtensions: string[]; // {{SQL CARBON EDIT}} + readonly recommendedExtensionsByScenario: { [area: string]: Array }; // {{SQL CARBON EDIT}} + readonly vscodeVersion: string; // {{SQL CARBON EDIT}} add vscode version + readonly gettingStartedUrl: string; // {SQL CARBON EDIT} + + readonly crashReporter?: { + readonly companyName: string; + readonly productName: string; + }; + + readonly welcomePage?: string; + + readonly enableTelemetry?: boolean; + readonly aiConfig?: { + readonly asimovKey: string; + }; + + readonly sendASmile?: { + readonly reportIssueUrl: string, + readonly requestFeatureUrl: string + }; + + readonly documentationUrl?: string; + readonly releaseNotesUrl?: string; + readonly keyboardShortcutsUrlMac?: string; + readonly keyboardShortcutsUrlLinux?: string; + readonly keyboardShortcutsUrlWin?: string; + readonly introductoryVideosUrl?: string; + readonly tipsAndTricksUrl?: string; + readonly newsletterSignupUrl?: string; + readonly twitterUrl?: string; + readonly requestFeatureUrl?: string; + readonly reportIssueUrl?: string; + readonly licenseUrl?: string; + readonly privacyStatementUrl?: string; + readonly telemetryOptOutUrl?: string; + + readonly npsSurveyUrl?: string; + readonly surveys?: readonly ISurveyData[]; + + readonly checksums?: { [path: string]: string; }; + readonly checksumFailMoreInfoUrl?: string; + + readonly hockeyApp?: { + readonly 'win32-ia32': string; + readonly 'win32-x64': string; + readonly 'linux-x64': string; + readonly 'darwin': string; + }; + + readonly portable?: string; + + readonly uiExtensions?: readonly string[]; + readonly extensionAllowedProposedApi?: readonly string[]; + + readonly msftInternalDomains?: string[]; + readonly linkProtectionTrustedDomains?: readonly string[]; + readonly settingsSyncStoreUrl?: string; +} + +export interface IExeBasedExtensionTip { + friendlyName: string; + windowsPath?: string; + recommendations: readonly string[]; + important?: boolean; + exeFriendlyName?: string; +} + +export interface ISurveyData { + surveyId: string; + surveyUrl: string; + languageId: string; + editCount: number; + userProbability: number; +} diff --git a/src/vs/platform/product/node/package.ts b/src/vs/platform/product/node/package.ts deleted file mode 100644 index 51154fcfa1..0000000000 --- a/src/vs/platform/product/node/package.ts +++ /dev/null @@ -1,16 +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 path from 'vs/base/common/path'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; - -export interface IPackageConfiguration { - name: string; - version: string; -} - -const rootPath = path.dirname(getPathFromAmdModule(require, '')); -const packageJsonPath = path.join(rootPath, 'package.json'); -export default require.__$__nodeRequire(packageJsonPath) as IPackageConfiguration; diff --git a/src/vs/platform/product/node/product.ts b/src/vs/platform/product/node/product.ts deleted file mode 100644 index c3a295ebef..0000000000 --- a/src/vs/platform/product/node/product.ts +++ /dev/null @@ -1,28 +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 path from 'vs/base/common/path'; -import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { IProductConfiguration } from 'vs/platform/product/common/product'; -import pkg from 'vs/platform/product/node/package'; -import { assign } from 'vs/base/common/objects'; - -const rootPath = path.dirname(getPathFromAmdModule(require, '')); -const productJsonPath = path.join(rootPath, 'product.json'); -const product = require.__$__nodeRequire(productJsonPath) as IProductConfiguration; - -if (process.env['VSCODE_DEV']) { - assign(product, { - nameShort: `${product.nameShort} Dev`, - nameLong: `${product.nameLong} Dev`, - dataFolderName: `${product.dataFolderName}-dev` - }); -} - -assign(product, { - version: pkg.version -}); - -export default product; diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index 519e908154..ff112be60a 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -8,6 +8,9 @@ import { ISocket } from 'vs/base/parts/ipc/common/ipc.net'; import { VSBuffer } from 'vs/base/common/buffer'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; +import * as dom from 'vs/base/browser/dom'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; export interface IWebSocketFactory { create(url: string): IWebSocket; @@ -23,27 +26,34 @@ export interface IWebSocket { close(): void; } -class BrowserWebSocket implements IWebSocket { +class BrowserWebSocket extends Disposable implements IWebSocket { private readonly _onData = new Emitter(); public readonly onData = this._onData.event; public readonly onOpen: Event; - public readonly onClose: Event; - public readonly onError: Event; + + private readonly _onClose = this._register(new Emitter()); + public readonly onClose = this._onClose.event; + + private readonly _onError = this._register(new Emitter()); + public readonly onError = this._onError.event; private readonly _socket: WebSocket; private readonly _fileReader: FileReader; private readonly _queue: Blob[]; private _isReading: boolean; + private _isClosed: boolean; private readonly _socketMessageListener: (ev: MessageEvent) => void; constructor(socket: WebSocket) { + super(); this._socket = socket; this._fileReader = new FileReader(); this._queue = []; this._isReading = false; + this._isClosed = false; this._fileReader.onload = (event) => { this._isReading = false; @@ -71,17 +81,79 @@ class BrowserWebSocket implements IWebSocket { this._socket.addEventListener('message', this._socketMessageListener); this.onOpen = Event.fromDOMEventEmitter(this._socket, 'open'); - this.onClose = Event.fromDOMEventEmitter(this._socket, 'close'); - this.onError = Event.fromDOMEventEmitter(this._socket, 'error'); + + // WebSockets emit error events that do not contain any real information + // Our only chance of getting to the root cause of an error is to + // listen to the close event which gives out some real information: + // - https://www.w3.org/TR/websockets/#closeevent + // - https://tools.ietf.org/html/rfc6455#section-11.7 + // + // But the error event is emitted before the close event, so we therefore + // delay the error event processing in the hope of receiving a close event + // with more information + + let pendingErrorEvent: any | null = null; + + const sendPendingErrorNow = () => { + const err = pendingErrorEvent; + pendingErrorEvent = null; + this._onError.fire(err); + }; + + const errorRunner = this._register(new RunOnceScheduler(sendPendingErrorNow, 0)); + + const sendErrorSoon = (err: any) => { + errorRunner.cancel(); + pendingErrorEvent = err; + errorRunner.schedule(); + }; + + const sendErrorNow = (err: any) => { + errorRunner.cancel(); + pendingErrorEvent = err; + sendPendingErrorNow(); + }; + + this._register(dom.addDisposableListener(this._socket, 'close', (e: CloseEvent) => { + this._isClosed = true; + + if (pendingErrorEvent) { + if (!window.navigator.onLine) { + // The browser is offline => this is a temporary error which might resolve itself + sendErrorNow(new RemoteAuthorityResolverError('Browser is offline', RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable, e)); + } else { + // An error event is pending + // The browser appears to be online... + if (!e.wasClean) { + // Let's be optimistic and hope that perhaps the server could not be reached or something + sendErrorNow(new RemoteAuthorityResolverError(e.reason || `WebSocket close with status code ${e.code}`, RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable, e)); + } else { + // this was a clean close => send existing error + errorRunner.cancel(); + sendPendingErrorNow(); + } + } + } + + this._onClose.fire(); + })); + + this._register(dom.addDisposableListener(this._socket, 'error', sendErrorSoon)); } send(data: ArrayBuffer | ArrayBufferView): void { + if (this._isClosed) { + // Refuse to write data to closed WebSocket... + return; + } this._socket.send(data); } close(): void { + this._isClosed = true; this._socket.close(); this._socket.removeEventListener('message', this._socketMessageListener); + this.dispose(); } } diff --git a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts index 8dd2f5ffa4..ae9f2e7e87 100644 --- a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts @@ -5,14 +5,14 @@ import { ResolvedAuthority, IRemoteAuthorityResolverService, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { RemoteAuthorities } from 'vs/base/common/network'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; export class RemoteAuthorityResolverService implements IRemoteAuthorityResolverService { _serviceBrand: undefined; constructor( - resourceUriProvider: ((uri: URI) => UriComponents) | undefined + resourceUriProvider: ((uri: URI) => URI) | undefined ) { if (resourceUriProvider) { RemoteAuthorities.setDelegate(resourceUriProvider); diff --git a/src/vs/platform/request/electron-main/requestService.ts b/src/vs/platform/request/electron-main/requestMainService.ts similarity index 94% rename from src/vs/platform/request/electron-main/requestService.ts rename to src/vs/platform/request/electron-main/requestMainService.ts index efc7819c11..83ff6c29da 100644 --- a/src/vs/platform/request/electron-main/requestService.ts +++ b/src/vs/platform/request/electron-main/requestMainService.ts @@ -13,7 +13,7 @@ function getRawRequest(options: IRequestOptions): IRawRequestFunction { return net.request as any as IRawRequestFunction; } -export class RequestService extends NodeRequestService { +export class RequestMainService extends NodeRequestService { request(options: IRequestOptions, token: CancellationToken): Promise { return super.request(assign({}, options || {}, { getRawRequest }), token); diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index bb087f5e5d..ddebe7d2cf 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -150,7 +150,7 @@ export class BrowserStorageService extends Disposable implements IStorageService } async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise { - // TODO@ben implement storage migration in web + throw new Error('Migrating storage is currently unsupported in Web'); } close(): void { diff --git a/src/vs/platform/telemetry/node/commonProperties.ts b/src/vs/platform/telemetry/node/commonProperties.ts index 28a0f1fdec..e3d46f1891 100644 --- a/src/vs/platform/telemetry/node/commonProperties.ts +++ b/src/vs/platform/telemetry/node/commonProperties.ts @@ -8,7 +8,7 @@ import * as os from 'os'; import * as uuid from 'vs/base/common/uuid'; import { readFile } from 'vs/base/node/pfs'; -import product from 'vs/platform/product/node/product'; // {{SQL CARBON EDIT}} +import product from 'vs/platform/product/common/product'; // {{SQL CARBON EDIT}} const productObject = product; // {{SQL CARBON EDIT}} export async function resolveCommonProperties(commit: string | undefined, version: string | undefined, machineId: string | undefined, msftInternalDomains: string[] | undefined, installSourcePath: string, product?: string): Promise<{ [name: string]: string | boolean | undefined; }> { const result: { [name: string]: string | boolean | undefined; } = Object.create(null); diff --git a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts index 8ebd27a479..975f814238 100644 --- a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts @@ -8,7 +8,7 @@ import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProper import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry'; import { cleanRemoteAuthority } from 'vs/platform/telemetry/common/telemetryUtils'; -import product from 'vs/platform/product/node/product'; // {{ SQL CARBON EDIT }} +import product from 'vs/platform/product/common/product'; // {{ SQL CARBON EDIT }} export async function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string | undefined, version: string | undefined, machineId: string, msftInternalDomains: string[] | undefined, installSourcePath: string, remoteAuthority?: string): Promise<{ [name: string]: string | boolean | undefined }> { const result = await resolveCommonProperties(commit, version, machineId, msftInternalDomains, installSourcePath); diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 69f60e56ad..0bb269a4dd 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -189,6 +189,8 @@ export const activeContrastBorder = registerColor('contrastActiveBorder', { ligh export const selectionBackground = registerColor('selection.background', { light: null, dark: null, hc: null }, nls.localize('selectionBackground', "The background color of text selections in the workbench (e.g. for input fields or text areas). Note that this does not apply to selections within the editor.")); +export const iconForeground = registerColor('icon.foreground', { light: '#424242', dark: '#C5C5C5', hc: '#FFFFFF' }, nls.localize('iconForeground', "The default color for icons in the workbench.")); + // ------ text colors export const textSeparatorForeground = registerColor('textSeparator.foreground', { light: '#0000002e', dark: '#ffffff2e', hc: Color.black }, nls.localize('textSeparatorForeground', "Color for text separators.")); diff --git a/src/vs/platform/theme/electron-main/themeMainService.ts b/src/vs/platform/theme/electron-main/themeMainService.ts index 6acb020255..cbd2196703 100644 --- a/src/vs/platform/theme/electron-main/themeMainService.ts +++ b/src/vs/platform/theme/electron-main/themeMainService.ts @@ -41,7 +41,7 @@ export class ThemeMainService implements IThemeMainService { this.stateService.setItem(THEME_BG_STORAGE_KEY, data.background); } - public getBackgroundColor(): string { + getBackgroundColor(): string { if (isWindows && systemPreferences.isInvertedColorScheme()) { return DEFAULT_BG_HC_BLACK; } @@ -64,4 +64,4 @@ export class ThemeMainService implements IThemeMainService { return background; } -} \ No newline at end of file +} diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 7863278d8b..5cf87f42b7 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -6,8 +6,8 @@ import { Event, Emitter } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; import { IConfigurationService, getMigratedSettingValue } from 'vs/platform/configuration/common/configuration'; -import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; -import product from 'vs/platform/product/node/product'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import product from 'vs/platform/product/common/product'; import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; @@ -44,7 +44,7 @@ export abstract class AbstractUpdateService implements IUpdateService { } constructor( - @ILifecycleService private readonly lifecycleService: ILifecycleService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService protected configurationService: IConfigurationService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IRequestService protected requestService: IRequestService, @@ -152,7 +152,7 @@ export abstract class AbstractUpdateService implements IUpdateService { this.logService.trace('update#quitAndInstall(): before lifecycle quit()'); - this.lifecycleService.quit(true /* from update */).then(vetod => { + this.lifecycleMainService.quit(true /* from update */).then(vetod => { this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`); if (vetod) { return; diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index 539f9d79c1..783d487d5d 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -8,7 +8,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { memoize } from 'vs/base/common/decorators'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { State, IUpdate, StateType, UpdateType } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -28,14 +28,14 @@ export class DarwinUpdateService extends AbstractUpdateService { @memoize private get onRawUpdateDownloaded(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-downloaded', (_, releaseNotes, version, date) => ({ releaseNotes, version, productVersion: version, date })); } constructor( - @ILifecycleService lifecycleService: ILifecycleService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IEnvironmentService environmentService: IEnvironmentService, @IRequestService requestService: IRequestService, @ILogService logService: ILogService ) { - super(lifecycleService, configurationService, environmentService, requestService, logService); + super(lifecycleMainService, configurationService, environmentService, requestService, logService); this.onRawError(this.onError, this, this.disposables); this.onRawUpdateAvailable(this.onUpdateAvailable, this, this.disposables); this.onRawUpdateDownloaded(this.onUpdateDownloaded, this, this.disposables); diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index d895054ad1..944006ed95 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { State, IUpdate, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -20,14 +20,14 @@ export class LinuxUpdateService extends AbstractUpdateService { _serviceBrand: undefined; constructor( - @ILifecycleService lifecycleService: ILifecycleService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IEnvironmentService environmentService: IEnvironmentService, @IRequestService requestService: IRequestService, @ILogService logService: ILogService ) { - super(lifecycleService, configurationService, environmentService, requestService, logService); + super(lifecycleMainService, configurationService, environmentService, requestService, logService); } protected buildUpdateFeedUrl(quality: string): string { diff --git a/src/vs/platform/update/electron-main/updateService.snap.ts b/src/vs/platform/update/electron-main/updateService.snap.ts index e5449de382..21d2ae4866 100644 --- a/src/vs/platform/update/electron-main/updateService.snap.ts +++ b/src/vs/platform/update/electron-main/updateService.snap.ts @@ -5,7 +5,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; -import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; @@ -35,7 +35,7 @@ abstract class AbstractUpdateService2 implements IUpdateService { } constructor( - @ILifecycleService private readonly lifecycleService: ILifecycleService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IEnvironmentService environmentService: IEnvironmentService, @ILogService protected logService: ILogService, ) { @@ -106,7 +106,7 @@ abstract class AbstractUpdateService2 implements IUpdateService { this.logService.trace('update#quitAndInstall(): before lifecycle quit()'); - this.lifecycleService.quit(true /* from update */).then(vetod => { + this.lifecycleMainService.quit(true /* from update */).then(vetod => { this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`); if (vetod) { return; @@ -139,12 +139,12 @@ export class SnapUpdateService extends AbstractUpdateService2 { constructor( private snap: string, private snapRevision: string, - @ILifecycleService lifecycleService: ILifecycleService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IEnvironmentService environmentService: IEnvironmentService, @ILogService logService: ILogService, @ITelemetryService private readonly telemetryService: ITelemetryService ) { - super(lifecycleService, environmentService, logService); + super(lifecycleMainService, environmentService, logService); const watcher = watch(path.dirname(this.snap)); const onChange = Event.fromNodeEventEmitter(watcher, 'change', (_, fileName: string) => fileName); @@ -152,7 +152,7 @@ export class SnapUpdateService extends AbstractUpdateService2 { const onDebouncedCurrentChange = Event.debounce(onCurrentChange, (_, e) => e, 2000); const listener = onDebouncedCurrentChange(this.checkForUpdates, this); - lifecycleService.onWillShutdown(() => { + lifecycleMainService.onWillShutdown(() => { listener.dispose(); watcher.close(); }); diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 4898949819..41b8a7e1e3 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -8,8 +8,8 @@ import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { memoize } from 'vs/base/common/decorators'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; -import product from 'vs/platform/product/node/product'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import product from 'vs/platform/product/common/product'; import { State, IUpdate, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -61,7 +61,7 @@ export class Win32UpdateService extends AbstractUpdateService { } constructor( - @ILifecycleService lifecycleService: ILifecycleService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IEnvironmentService environmentService: IEnvironmentService, @@ -69,7 +69,7 @@ export class Win32UpdateService extends AbstractUpdateService { @ILogService logService: ILogService, @IFileService private readonly fileService: IFileService ) { - super(lifecycleService, configurationService, environmentService, requestService, logService); + super(lifecycleMainService, configurationService, environmentService, requestService, logService); if (getUpdateType() === UpdateType.Setup) { /* __GDPR__ diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index 5ea97de746..8933049e68 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { IURLService } from 'vs/platform/url/common/url'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { app } from 'electron'; import { URI } from 'vs/base/common/uri'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -28,7 +28,7 @@ export class ElectronURLListener { constructor( initial: string | string[], @IURLService private readonly urlService: IURLService, - @IWindowsMainService windowsService: IWindowsMainService + @IWindowsMainService windowsMainService: IWindowsMainService ) { const globalBuffer = ((global).getOpenUrls() || []) as string[]; const rawBuffer = [ @@ -58,18 +58,18 @@ export class ElectronURLListener { const onOpenUrl = Event.filter(Event.map(onOpenElectronUrl, uriFromRawUrl), uri => !!uri); onOpenUrl(this.urlService.open, this.urlService, this.disposables); - const isWindowReady = windowsService.getWindows() + const isWindowReady = windowsMainService.getWindows() .filter(w => w.isReady) .length > 0; if (isWindowReady) { flush(); } else { - Event.once(windowsService.onWindowReady)(flush); + Event.once(windowsMainService.onWindowReady)(flush); } } dispose(): void { this.disposables = dispose(this.disposables); } -} \ No newline at end of file +} diff --git a/src/vs/platform/url/node/urlService.ts b/src/vs/platform/url/node/urlService.ts index 25928b6240..fdfe1e4d5c 100644 --- a/src/vs/platform/url/node/urlService.ts +++ b/src/vs/platform/url/node/urlService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI, UriComponents } from 'vs/base/common/uri'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { AbstractURLService } from 'vs/platform/url/common/urlService'; export class URLService extends AbstractURLService { diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts new file mode 100644 index 0000000000..c9bb42130f --- /dev/null +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -0,0 +1,253 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files'; +import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, ISettingsMergeService, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { parse, ParseError } from 'vs/base/common/json'; +import { localize } from 'vs/nls'; +import { Emitter, Event } from 'vs/base/common/event'; +import { ILogService } from 'vs/platform/log/common/log'; +import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { URI } from 'vs/base/common/uri'; +import { joinPath } from 'vs/base/common/resources'; + +interface ISyncPreviewResult { + readonly fileContent: IFileContent | null; + readonly remoteUserData: IUserData; + readonly hasLocalChanged: boolean; + readonly hasRemoteChanged: boolean; + readonly hasConflicts: boolean; +} + +export class SettingsSynchroniser extends Disposable implements ISynchroniser { + + private static EXTERNAL_USER_DATA_SETTINGS_KEY: string = 'settings'; + + private syncPreviewResultPromise: CancelablePromise | null = null; + + private _status: SyncStatus = SyncStatus.Idle; + get status(): SyncStatus { return this._status; } + private _onDidChangStatus: Emitter = this._register(new Emitter()); + readonly onDidChangeStatus: Event = this._onDidChangStatus.event; + + private readonly throttledDelayer: ThrottledDelayer; + private _onDidChangeLocal: Emitter = this._register(new Emitter()); + readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; + + private readonly lastSyncSettingsResource: URI; + + constructor( + @IFileService private readonly fileService: IFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @ISettingsMergeService private readonly settingsMergeService: ISettingsMergeService, + @ILogService private readonly logService: ILogService, + ) { + super(); + this.lastSyncSettingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncSettings.json'); + this.throttledDelayer = this._register(new ThrottledDelayer(500)); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.settingsResource))(() => this.throttledDelayer.trigger(() => this.onDidChangeSettings()))); + } + + private async onDidChangeSettings(): Promise { + const localFileContent = await this.getLocalFileContent(); + const lastSyncData = await this.getLastSyncUserData(); + if (localFileContent && lastSyncData) { + if (localFileContent.value.toString() !== lastSyncData.content) { + this._onDidChangeLocal.fire(); + return; + } + } + if (!localFileContent || !lastSyncData) { + this._onDidChangeLocal.fire(); + return; + } + } + + private setStatus(status: SyncStatus): void { + if (this._status !== status) { + this._status = status; + this._onDidChangStatus.fire(status); + } + } + + async sync(_continue?: boolean): Promise { + + if (_continue) { + return this.continueSync(); + } + + if (this.status !== SyncStatus.Idle) { + return false; + } + + this.setStatus(SyncStatus.Syncing); + + try { + const result = await this.getPreview(); + if (result.hasConflicts) { + this.setStatus(SyncStatus.HasConflicts); + return false; + } + await this.apply(); + return true; + } catch (e) { + this.syncPreviewResultPromise = null; + this.setStatus(SyncStatus.Idle); + if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + // Rejected as there is a new remote version. Syncing again, + this.logService.info('Failed to Synchronise settings as there is a new remote version available. Synchronising again...'); + return this.sync(); + } + if (e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) { + // Rejected as there is a new local version. Syncing again. + this.logService.info('Failed to Synchronise settings as there is a new local version available. Synchronising again...'); + return this.sync(); + } + throw e; + } + } + + private async continueSync(): Promise { + if (this.status !== SyncStatus.HasConflicts) { + return false; + } + await this.apply(); + return true; + } + + private async apply(): Promise { + if (!this.syncPreviewResultPromise) { + return; + } + + if (await this.fileService.exists(this.environmentService.settingsSyncPreviewResource)) { + const settingsPreivew = await this.fileService.readFile(this.environmentService.settingsSyncPreviewResource); + const content = settingsPreivew.value.toString(); + if (this.hasErrors(content)) { + return Promise.reject(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again.")); + } + + let { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise; + if (hasRemoteChanged) { + const ref = await this.writeToRemote(content, remoteUserData.ref); + remoteUserData = { ref, content }; + } + if (hasLocalChanged) { + await this.writeToLocal(content, fileContent); + } + if (remoteUserData.content) { + await this.updateLastSyncValue(remoteUserData); + } + + // Delete the preview + await this.fileService.del(this.environmentService.settingsSyncPreviewResource); + } + + this.syncPreviewResultPromise = null; + this.setStatus(SyncStatus.Idle); + } + + private hasErrors(content: string): boolean { + const parseErrors: ParseError[] = []; + parse(content, parseErrors); + return parseErrors.length > 0; + } + + private getPreview(): Promise { + if (!this.syncPreviewResultPromise) { + this.syncPreviewResultPromise = createCancelablePromise(token => this.generatePreview()); + } + return this.syncPreviewResultPromise; + } + + private async generatePreview(): Promise { + const lastSyncData = await this.getLastSyncUserData(); + const remoteUserData = await this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData); + const remoteContent: string | null = remoteUserData.content; + // Get file content last to get the latest + const fileContent = await this.getLocalFileContent(); + let hasLocalChanged: boolean = false; + let hasRemoteChanged: boolean = false; + let hasConflicts: boolean = false; + + // First time sync to remote + if (fileContent && !remoteContent) { + this.logService.trace('Settings Sync: Remote contents does not exist. So sync with settings file.'); + hasRemoteChanged = true; + await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(fileContent.value.toString())); + return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + } + + // Settings file does not exist, so sync with remote contents. + if (remoteContent && !fileContent) { + this.logService.trace('Settings Sync: Settings file does not exist. So sync with remote contents'); + hasLocalChanged = true; + await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(remoteContent)); + return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + } + + if (fileContent && remoteContent) { + const localContent: string = fileContent.value.toString(); + if (!lastSyncData // First time sync + || lastSyncData.content !== localContent // Local has moved forwarded + || lastSyncData.content !== remoteContent // Remote has moved forwarded + ) { + this.logService.trace('Settings Sync: Merging remote contents with settings file.'); + const result = await this.settingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null); + // Sync only if there are changes + if (result.hasChanges) { + hasLocalChanged = result.mergeContent !== localContent; + hasRemoteChanged = result.mergeContent !== remoteContent; + hasConflicts = result.hasConflicts; + await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(result.mergeContent)); + return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + } + } + } + + this.logService.trace('Settings Sync: No changes.'); + return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + } + + private async getLastSyncUserData(): Promise { + try { + const content = await this.fileService.readFile(this.lastSyncSettingsResource); + return JSON.parse(content.value.toString()); + } catch (error) { + return null; + } + } + + private async getLocalFileContent(): Promise { + try { + return await this.fileService.readFile(this.environmentService.settingsResource); + } catch (error) { + return null; + } + } + + private async writeToRemote(content: string, ref: string | null): Promise { + return this.userDataSyncStoreService.write(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, content, ref); + } + + private async writeToLocal(newContent: string, oldContent: IFileContent | null): Promise { + if (oldContent) { + // file exists already + await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(newContent), oldContent); + } else { + // file does not exist + await this.fileService.createFile(this.environmentService.settingsResource, VSBuffer.fromString(newContent), { overwrite: false }); + } + } + + private async updateLastSyncValue(remoteUserData: IUserData): Promise { + await this.fileService.writeFile(this.lastSyncSettingsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); + } + +} diff --git a/src/vs/platform/userDataSync/common/settingsSyncIpc.ts b/src/vs/platform/userDataSync/common/settingsSyncIpc.ts new file mode 100644 index 0000000000..32cc62c5d6 --- /dev/null +++ b/src/vs/platform/userDataSync/common/settingsSyncIpc.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Event } from 'vs/base/common/event'; +import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; + +export class SettingsMergeChannel implements IServerChannel { + + constructor(private readonly service: ISettingsMergeService) { } + + listen(_: unknown, event: string): Event { + throw new Error(`Event not found: ${event}`); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'merge': return this.service.merge(args[0], args[1], args[2]); + } + throw new Error('Invalid call'); + } +} + +export class SettingsMergeChannelClient implements ISettingsMergeService { + + _serviceBrand: undefined; + + constructor(private readonly channel: IChannel) { + } + + merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { + return this.channel.call('merge', [localContent, remoteContent, baseContent]); + } + +} diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts new file mode 100644 index 0000000000..b0b751787e --- /dev/null +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Event } from 'vs/base/common/event'; + +export interface IUserData { + ref: string; + content: string | null; +} + +export enum UserDataSyncStoreErrorCode { + Rejected = 'Rejected', + Unknown = 'Unknown' +} + +export class UserDataSyncStoreError extends Error { + + constructor(message: string, public readonly code: UserDataSyncStoreErrorCode) { + super(message); + } + +} + +export const IUserDataSyncStoreService = createDecorator('IUserDataSyncStoreService'); + +export interface IUserDataSyncStoreService { + _serviceBrand: undefined; + + readonly enabled: boolean; + + readonly loggedIn: boolean; + readonly onDidChangeLoggedIn: Event; + login(): Promise; + logout(): Promise; + + read(key: string, oldValue: IUserData | null): Promise; + write(key: string, content: string, ref: string | null): Promise; +} + +export enum SyncSource { + Settings = 1, + Extensions +} + +export enum SyncStatus { + Uninitialized = 'uninitialized', + Idle = 'idle', + Syncing = 'syncing', + HasConflicts = 'hasConflicts', +} + +export interface ISynchroniser { + + readonly status: SyncStatus; + readonly onDidChangeStatus: Event; + readonly onDidChangeLocal: Event; + + sync(_continue?: boolean): Promise; +} + +export const IUserDataSyncService = createDecorator('IUserDataSyncService'); + +export interface IUserDataSyncService extends ISynchroniser { + _serviceBrand: any; + readonly conflictsSource: SyncSource | null; +} + +export const ISettingsMergeService = createDecorator('ISettingsMergeService'); + +export interface ISettingsMergeService { + + _serviceBrand: undefined; + + merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }>; + +} diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts new file mode 100644 index 0000000000..c3a7b4b1eb --- /dev/null +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Event } from 'vs/base/common/event'; +import { IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; + +export class UserDataSyncChannel implements IServerChannel { + + constructor(private readonly service: IUserDataSyncService) { } + + listen(_: unknown, event: string): Event { + switch (event) { + case 'onDidChangeStatus': return this.service.onDidChangeStatus; + case 'onDidChangeLocal': return this.service.onDidChangeLocal; + } + throw new Error(`Event not found: ${event}`); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'sync': return this.service.sync(args[0]); + case 'getConflictsSource': return Promise.resolve(this.service.conflictsSource); + } + throw new Error('Invalid call'); + } +} diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts new file mode 100644 index 0000000000..c58fce2fc6 --- /dev/null +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { timeout } from 'vs/base/common/async'; + +export class UserDataSyncService extends Disposable implements IUserDataSyncService { + + _serviceBrand: any; + + private readonly synchronisers: ISynchroniser[]; + + private _status: SyncStatus = SyncStatus.Uninitialized; + get status(): SyncStatus { return this._status; } + private _onDidChangeStatus: Emitter = this._register(new Emitter()); + readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + + readonly onDidChangeLocal: Event; + + private _conflictsSource: SyncSource | null = null; + get conflictsSource(): SyncSource | null { return this._conflictsSource; } + + constructor( + @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(); + this.synchronisers = [ + this.instantiationService.createInstance(SettingsSynchroniser) + ]; + this.updateStatus(); + this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus())); + this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal)); + } + + async sync(_continue?: boolean): Promise { + if (!this.userDataSyncStoreService.enabled) { + throw new Error('Not enabled'); + } + for (const synchroniser of this.synchronisers) { + if (!await synchroniser.sync(_continue)) { + return false; + } + } + return true; + } + + private updateStatus(): void { + this._conflictsSource = this.computeConflictsSource(); + this.setStatus(this.computeStatus()); + } + + private setStatus(status: SyncStatus): void { + if (this._status !== status) { + this._status = status; + this._onDidChangeStatus.fire(status); + } + } + + private computeStatus(): SyncStatus { + if (!this.userDataSyncStoreService.enabled) { + return SyncStatus.Uninitialized; + } + if (this.synchronisers.some(s => s.status === SyncStatus.HasConflicts)) { + return SyncStatus.HasConflicts; + } + if (this.synchronisers.some(s => s.status === SyncStatus.Syncing)) { + return SyncStatus.Syncing; + } + return SyncStatus.Idle; + } + + private computeConflictsSource(): SyncSource | null { + const source = this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts)[0]; + if (source) { + if (source instanceof SettingsSynchroniser) { + return SyncSource.Settings; + } + } + return null; + } + +} + +export class UserDataAutoSync extends Disposable { + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + ) { + super(); + if (userDataSyncStoreService.enabled) { + this.sync(true); + this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('userConfiguration.enableSync'))(() => this.sync(true))); + + // Sync immediately if there is a local change. + this._register(Event.debounce(this.userDataSyncService.onDidChangeLocal, () => undefined, 500)(() => this.sync(false))); + } + } + + private async sync(loop: boolean): Promise { + if (this.isSyncEnabled()) { + try { + await this.userDataSyncService.sync(); + } catch (e) { + // Ignore errors + } + if (loop) { + await timeout(1000 * 5); // Loop sync for every 5s. + this.sync(loop); + } + } + } + + private isSyncEnabled(): boolean { + const { user: userLocal } = this.configurationService.inspect('userConfiguration.enableSync'); + return userLocal === undefined || userLocal; + } + +} diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts new file mode 100644 index 0000000000..e09ddd3a7e --- /dev/null +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, } from 'vs/base/common/lifecycle'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IRequestService, asText } from 'vs/platform/request/common/request'; +import { URI } from 'vs/base/common/uri'; +import { joinPath } from 'vs/base/common/resources'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IHeaders } from 'vs/base/parts/request/common/request'; + +export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService { + + _serviceBrand: any; + + get enabled(): boolean { return !!this.productService.settingsSyncStoreUrl; } + + private _loggedIn: boolean = false; + get loggedIn(): boolean { return this._loggedIn; } + private readonly _onDidChangeLoggedIn: Emitter = this._register(new Emitter()); + readonly onDidChangeLoggedIn: Event = this._onDidChangeLoggedIn.event; + + constructor( + @IProductService private readonly productService: IProductService, + @IRequestService private readonly requestService: IRequestService, + ) { + super(); + } + + async login(): Promise { + } + + async logout(): Promise { + } + + async read(key: string, oldValue: IUserData | null): Promise { + if (!this.enabled) { + return Promise.reject(new Error('No settings sync store url configured.')); + } + + const url = joinPath(URI.parse(this.productService.settingsSyncStoreUrl!), key).toString(); + const headers: IHeaders = {}; + if (oldValue) { + headers['If-None-Match'] = oldValue.ref; + } + + const context = await this.requestService.request({ type: 'GET', url, headers }, CancellationToken.None); + + if (context.res.statusCode === 304) { + // There is no new value. Hence return the old value. + return oldValue!; + } + + const ref = context.res.headers['etag']; + if (!ref) { + throw new Error('Server did not return the ref'); + } + const content = await asText(context); + return { ref, content }; + } + + async write(key: string, data: string, ref: string | null): Promise { + if (!this.enabled) { + return Promise.reject(new Error('No settings sync store url configured.')); + } + + const url = joinPath(URI.parse(this.productService.settingsSyncStoreUrl!), key).toString(); + const headers: IHeaders = { 'Content-Type': 'text/plain' }; + if (ref) { + headers['If-Match'] = ref; + } + + const context = await this.requestService.request({ type: 'POST', url, data, headers }, CancellationToken.None); + + if (context.res.statusCode === 412) { + // There is a new value. Throw Rejected Error + throw new UserDataSyncStoreError('New data exists', UserDataSyncStoreErrorCode.Rejected); + } + + const newRef = context.res.headers['etag']; + if (!newRef) { + throw new Error('Server did not return the ref'); + } + return newRef; + } + +} diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 9f91c8760c..df28349112 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -155,9 +155,6 @@ export interface IWindowsService { openNewWindow(options?: INewWindowOptions): Promise; openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise; getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]>; - getWindowCount(): Promise; - log(severity: string, args: string[]): Promise; - showItemInFolder(path: URI): Promise; getActiveWindowId(): Promise; // This needs to be handled from browser process to prevent @@ -167,7 +164,6 @@ export interface IWindowsService { // TODO: this is a bit backwards startCrashReporter(config: CrashReporterStartOptions): Promise; - openAboutDialog(): Promise; resolveProxy(windowId: number, url: string): Promise; } diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 2323c97e43..8208fa4e83 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -104,17 +104,13 @@ export class WindowsChannel implements IServerChannel { case 'openNewWindow': return this.service.openNewWindow(arg); case 'openExtensionDevelopmentHostWindow': return this.service.openExtensionDevelopmentHostWindow(arg[0], arg[1]); case 'getWindows': return this.service.getWindows(); - case 'getWindowCount': return this.service.getWindowCount(); case 'relaunch': return this.service.relaunch(arg[0]); case 'whenSharedProcessReady': return this.service.whenSharedProcessReady(); case 'toggleSharedProcess': return this.service.toggleSharedProcess(); case 'quit': return this.service.quit(); - case 'log': return this.service.log(arg[0], arg[1]); - case 'showItemInFolder': return this.service.showItemInFolder(URI.revive(arg)); case 'getActiveWindowId': return this.service.getActiveWindowId(); case 'openExternal': return this.service.openExternal(arg); case 'startCrashReporter': return this.service.startCrashReporter(arg); - case 'openAboutDialog': return this.service.openAboutDialog(); case 'resolveProxy': return this.service.resolveProxy(arg[0], arg[1]); } diff --git a/src/vs/platform/windows/electron-browser/windowsService.ts b/src/vs/platform/windows/electron-browser/windowsService.ts index 39d7cf5cb8..136f0aea0b 100644 --- a/src/vs/platform/windows/electron-browser/windowsService.ts +++ b/src/vs/platform/windows/electron-browser/windowsService.ts @@ -222,18 +222,6 @@ export class WindowsService implements IWindowsService { return result; } - getWindowCount(): Promise { - return this.channel.call('getWindowCount'); - } - - log(severity: string, args: string[]): Promise { - return this.channel.call('log', [severity, args]); - } - - showItemInFolder(path: URI): Promise { - return this.channel.call('showItemInFolder', path); - } - getActiveWindowId(): Promise { return this.channel.call('getActiveWindowId'); } @@ -250,10 +238,6 @@ export class WindowsService implements IWindowsService { return this.channel.call('updateTouchBar', [windowId, items]); } - openAboutDialog(): Promise { - return this.channel.call('openAboutDialog'); - } - resolveProxy(windowId: number, url: string): Promise { return Promise.resolve(this.channel.call('resolveProxy', [windowId, url])); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 709c9af83f..0797cb5cc0 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -3,25 +3,22 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import * as os from 'os'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; -import product from 'vs/platform/product/node/product'; import { IWindowsService, OpenContext, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IDevToolsOptions, INewWindowOptions, IOpenSettings, IURIToOpen } from 'vs/platform/windows/common/windows'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; -import { shell, crashReporter, app, Menu, clipboard } from 'electron'; +import { shell, crashReporter, app, Menu } from 'electron'; import { Event } from 'vs/base/common/event'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; -import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IWindowsMainService, ISharedProcess, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; -import { IHistoryMainService, IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; +import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; +import { IHistoryMainService } from 'vs/platform/history/electron-main/historyMainService'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { Schemas } from 'vs/base/common/network'; -import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { isMacintosh, isLinux, IProcessEnvironment } from 'vs/base/common/platform'; +import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; export class WindowsService extends Disposable implements IWindowsService, IURLHandler { @@ -41,15 +38,15 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)) ); - readonly onRecentlyOpenedChange: Event = this.historyService.onRecentlyOpenedChange; + readonly onRecentlyOpenedChange: Event = this.historyMainService.onRecentlyOpenedChange; constructor( private sharedProcess: ISharedProcess, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IURLService urlService: IURLService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IHistoryMainService private readonly historyService: IHistoryMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, + @IHistoryMainService private readonly historyMainService: IHistoryMainService, @ILogService private readonly logService: ILogService ) { super(); @@ -160,25 +157,25 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH async addRecentlyOpened(recents: IRecent[]): Promise { this.logService.trace('windowsService#addRecentlyOpened'); - this.historyService.addRecentlyOpened(recents); + this.historyMainService.addRecentlyOpened(recents); } async removeFromRecentlyOpened(paths: URI[]): Promise { this.logService.trace('windowsService#removeFromRecentlyOpened'); - this.historyService.removeFromRecentlyOpened(paths); + this.historyMainService.removeFromRecentlyOpened(paths); } async clearRecentlyOpened(): Promise { this.logService.trace('windowsService#clearRecentlyOpened'); - this.historyService.clearRecentlyOpened(); + this.historyMainService.clearRecentlyOpened(); } async getRecentlyOpened(windowId: number): Promise { this.logService.trace('windowsService#getRecentlyOpened', windowId); - return this.withWindow(windowId, codeWindow => this.historyService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpenOrCreate), () => this.historyService.getRecentlyOpened())!; + return this.withWindow(windowId, codeWindow => this.historyMainService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpenOrCreate), () => this.historyMainService.getRecentlyOpened())!; } async newWindowTab(): Promise { @@ -339,32 +336,6 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH return this.windowsMainService.getWindows().length; } - async log(severity: string, args: string[]): Promise { - let consoleFn = console.log; - - switch (severity) { - case 'error': - consoleFn = console.error; - break; - case 'warn': - consoleFn = console.warn; - break; - case 'info': - consoleFn = console.info; - break; - } - - consoleFn.call(console, ...args); - } - - async showItemInFolder(resource: URI): Promise { - this.logService.trace('windowsService#showItemInFolder'); - - if (resource.scheme === Schemas.file) { - shell.showItemInFolder(resource.fsPath); - } - } - async getActiveWindowId(): Promise { return this._activeWindowId; } @@ -391,7 +362,7 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH async relaunch(options: { addArgs?: string[], removeArgs?: string[] }): Promise { this.logService.trace('windowsService#relaunch'); - this.lifecycleService.relaunch(options); + this.lifecycleMainService.relaunch(options); } async whenSharedProcessReady(): Promise { @@ -407,54 +378,6 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH } - async openAboutDialog(): Promise { - this.logService.trace('windowsService#openAboutDialog'); - - let version = app.getVersion(); - if (product.target) { - version = `${version} (${product.target} setup)`; - } - - const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION; - // {{SQL CARBON EDIT}} - const detail = nls.localize('aboutDetail', - "Version: {0}\nCommit: {1}\nDate: {2}\nVS Code {8}\nElectron: {3}\nChrome: {4}\nNode.js: {5}\nV8: {6}\nOS: {7}", - version, - product.commit || 'Unknown', - product.date || 'Unknown', - process.versions['electron'], - process.versions['chrome'], - process.versions['node'], - process.versions['v8'], - `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}`, - product.vscodeVersion - ); - - const ok = nls.localize('okButton', "OK"); - const copy = mnemonicButtonLabel(nls.localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy")); - let buttons: string[]; - if (isLinux) { - buttons = [copy, ok]; - } else { - buttons = [ok, copy]; - } - - const result = await this.windowsMainService.showMessageBox({ - title: product.nameLong, - type: 'info', - message: product.nameLong, - detail: `\n${detail}`, - buttons, - noLink: true, - defaultId: buttons.indexOf(ok), - cancelId: buttons.indexOf(ok) - }, this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow()); - - if (buttons[result.button] === copy) { - clipboard.writeText(detail); - } - } - async handleURL(uri: URI): Promise { // Catch file URLs diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index f558e667d0..47ea5ab022 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -143,7 +143,7 @@ export interface IWorkspaceFolder extends IWorkspaceFolderData { export class Workspace implements IWorkspace { private _foldersMap: TernarySearchTree = TernarySearchTree.forPaths(); - private _folders: WorkspaceFolder[]; + private _folders!: WorkspaceFolder[]; constructor( private _id: string, @@ -265,4 +265,4 @@ export function toWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], } } return result; -} \ No newline at end of file +} diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index 353de67924..149e0e9c63 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -5,7 +5,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { localize } from 'vs/nls'; -import { Event } from 'vs/base/common/event'; import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { URI, UriComponents } from 'vs/base/common/uri'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; @@ -19,9 +18,6 @@ import { toSlashes } from 'vs/base/common/extpath'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; -export const IWorkspacesMainService = createDecorator('workspacesMainService'); -export const IWorkspacesService = createDecorator('workspacesService'); - export const WORKSPACE_EXTENSION = 'code-workspace'; export const WORKSPACE_FILTER = [{ name: localize('codeWorkspace', "Code Workspace"), extensions: [WORKSPACE_EXTENSION] }]; export const UNTITLED_WORKSPACE_NAME = 'workspace.json'; @@ -95,23 +91,10 @@ export interface IUntitledWorkspaceInfo { remoteAuthority?: string; } -export interface IWorkspacesMainService extends IWorkspacesService { - _serviceBrand: undefined; - - onUntitledWorkspaceDeleted: Event; - - createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier; - - resolveLocalWorkspaceSync(path: URI): IResolvedWorkspace | null; - - isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean; - - deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void; - - getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[]; -} +export const IWorkspacesService = createDecorator('workspacesService'); export interface IWorkspacesService { + _serviceBrand: undefined; createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise; @@ -276,4 +259,4 @@ export function useSlashForPath(storedFolders: IStoredWorkspaceFolder[]): boolea return false; } return true; -} \ No newline at end of file +} diff --git a/src/vs/platform/workspaces/node/workspacesIpc.ts b/src/vs/platform/workspaces/electron-main/workspacesIpc.ts similarity index 88% rename from src/vs/platform/workspaces/node/workspacesIpc.ts rename to src/vs/platform/workspaces/electron-main/workspacesIpc.ts index 1212a505a3..17450695ae 100644 --- a/src/vs/platform/workspaces/node/workspacesIpc.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesIpc.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index 5ec16e4950..e6bca89a36 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkspacesMainService, IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { join, dirname } from 'vs/base/common/path'; import { mkdirp, writeFile, rimrafSync, readdirSync, writeFileSync } from 'vs/base/node/pfs'; @@ -18,12 +18,38 @@ import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; import { originalFSPath, isEqualOrParent, joinPath } from 'vs/base/common/resources'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; export interface IStoredWorkspace { folders: IStoredWorkspaceFolder[]; remoteAuthority?: string; } +export const IWorkspacesMainService = createDecorator('workspacesMainService'); + +export interface IWorkspacesMainService { + + _serviceBrand: undefined; + + onUntitledWorkspaceDeleted: Event; + + createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier; + + resolveLocalWorkspaceSync(path: URI): IResolvedWorkspace | null; + + isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean; + + deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void; + + getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[]; + + createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise; + + deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise; + + getWorkspaceIdentifier(workspacePath: URI): Promise; +} + export class WorkspacesMainService extends Disposable implements IWorkspacesMainService { _serviceBrand: undefined; diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts index 3981d935ca..369bb7e229 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts @@ -11,7 +11,7 @@ import * as pfs from 'vs/base/node/pfs'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; import { WorkspacesMainService, IStoredWorkspace } from 'vs/platform/workspaces/electron-main/workspacesMainService'; -import { WORKSPACE_EXTENSION, IWorkspaceIdentifier, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder, rewriteWorkspaceFileForNewLocation } from 'vs/platform/workspaces/common/workspaces'; +import { WORKSPACE_EXTENSION, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder, rewriteWorkspaceFileForNewLocation } from 'vs/platform/workspaces/common/workspaces'; import { NullLogService } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; @@ -29,16 +29,6 @@ suite('WorkspacesMainService', () => { } } - class TestWorkspacesMainService extends WorkspacesMainService { - public deleteWorkspaceCall: IWorkspaceIdentifier; - - public deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void { - this.deleteWorkspaceCall = workspace; - - super.deleteUntitledWorkspaceSync(workspace); - } - } - function createWorkspace(folders: string[], names?: string[]) { return service.createUntitledWorkspace(folders.map((folder, index) => ({ uri: URI.file(folder), name: names ? names[index] : undefined } as IWorkspaceFolderCreationData))); } @@ -50,10 +40,10 @@ suite('WorkspacesMainService', () => { const environmentService = new TestEnvironmentService(parseArgs(process.argv, OPTIONS), process.execPath); const logService = new NullLogService(); - let service: TestWorkspacesMainService; + let service: WorkspacesMainService; setup(async () => { - service = new TestWorkspacesMainService(environmentService, logService); + service = new WorkspacesMainService(environmentService, logService); // Delete any existing backups completely and then re-create it. await pfs.rimraf(untitledWorkspacesHomePath, pfs.RimRafMode.MOVE); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index d7e7145185..7caa11a98a 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -7014,6 +7014,11 @@ declare module 'vscode' { */ readonly onDidChangeVisibility: Event; + /** + * An optional human-readable message that will be rendered in the view. + */ + message?: string; + /** * Reveals the given element in the tree view. * If the tree view is not visible then the tree view is shown and element is revealed. diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 59ea920f5f..d950190072 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -820,12 +820,6 @@ declare module 'vscode' { //#region Tree View export interface TreeView { - - /** - * An optional human-readable message that will be rendered in the view. - */ - message?: string; - /** * The name of the tree view. It is set from the extension package.json and can be changed later. */ @@ -1011,16 +1005,68 @@ declare module 'vscode' { //#endregion + // #region Ben - UIKind + + /** + * Possible kinds of UI that can use extensions. + */ + export enum UIKind { + + /** + * Extensions are accessed from a desktop application. + */ + Desktop = 1, + + /** + * Extensions are accessed from a web browser. + */ + Web = 2 + } + + export namespace env { + + /** + * The UI kind property indicates from which UI extensions + * are accessed from. For example, extensions could be accessed + * from a desktop application or a web browser. + */ + export const uiKind: UIKind; + } + + //#endregion + //#region Custom editors, mjbvz export enum WebviewEditorState { + /** + * The webview editor's content cannot be modified. + * + * This disables save + */ Readonly = 1, + + /** + * The webview editor's content has not been changed but they can be modified and saved. + */ Unchanged = 2, + + /** + * The webview editor's content has been changed and can be saved. + */ Dirty = 3, } export interface WebviewEditor extends WebviewPanel { state: WebviewEditorState; + + /** + * Fired when the webview editor is saved. + * + * Both `Unchanged` and `Dirty` editors can be saved. + * + * Extensions should call `waitUntil` to signal when the save operation complete + */ + readonly onWillSave: Event<{ waitUntil: (thenable: Thenable) => void }>; } export interface WebviewEditorProvider { diff --git a/src/vs/workbench/api/browser/mainThreadConsole.ts b/src/vs/workbench/api/browser/mainThreadConsole.ts index 773b92aa8a..69c18adfde 100644 --- a/src/vs/workbench/api/browser/mainThreadConsole.ts +++ b/src/vs/workbench/api/browser/mainThreadConsole.ts @@ -6,9 +6,10 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { MainContext, MainThreadConsoleShape, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IRemoteConsoleLog, log, parse } from 'vs/base/common/console'; +import { IRemoteConsoleLog, log } from 'vs/base/common/console'; +import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; @extHostNamedCustomer(MainContext.MainThreadConsole) @@ -20,7 +21,7 @@ export class MainThreadConsole implements MainThreadConsoleShape { constructor( extHostContext: IExtHostContext, @IEnvironmentService private readonly _environmentService: IEnvironmentService, - @IWindowsService private readonly _windowsService: IWindowsService, + @ILogService private readonly _logService: ILogService, @IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService, ) { const devOpts = parseExtensionDevOptions(this._environmentService); @@ -40,7 +41,7 @@ export class MainThreadConsole implements MainThreadConsoleShape { // Log on main side if running tests from cli if (this._isExtensionDevTestFromCli) { - this._windowsService.log(entry.severity, parse(entry).args); + logRemoteEntry(this._logService, entry); } // Broadcast to other windows if we are in development mode diff --git a/src/vs/workbench/api/browser/mainThreadKeytar.ts b/src/vs/workbench/api/browser/mainThreadKeytar.ts index 14e244f87c..b2d5477529 100644 --- a/src/vs/workbench/api/browser/mainThreadKeytar.ts +++ b/src/vs/workbench/api/browser/mainThreadKeytar.ts @@ -5,7 +5,7 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { MainContext, MainThreadKeytarShape, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; -import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; +import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; @extHostNamedCustomer(MainContext.MainThreadKeytar) export class MainThreadKeytar implements MainThreadKeytarShape { diff --git a/src/vs/workbench/api/browser/mainThreadTask.ts b/src/vs/workbench/api/browser/mainThreadTask.ts index 8aa5dd3cba..55f7a21065 100644 --- a/src/vs/workbench/api/browser/mainThreadTask.ts +++ b/src/vs/workbench/api/browser/mainThreadTask.ts @@ -422,6 +422,7 @@ export class MainThreadTask implements MainThreadTaskShape { this._proxy.$OnDidEndTask(TaskExecutionDTO.from(task.getTaskExecution())); } }); + this._taskService.setJsonTasksSupported(Promise.resolve(this._proxy.$jsonTasksSupported())); } public dispose(): void { diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index f3f6f482c7..c90189c265 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -8,39 +8,42 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { startsWith } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; import * as modes from 'vs/editor/common/modes'; import { localize } from 'vs/nls'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelShowOptions, WebviewPanelViewStateData } from 'vs/workbench/api/common/extHost.protocol'; import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor'; -import { WebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; +import { IEditorInput } from 'vs/workbench/common/editor'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; +import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { ICreateWebViewShowOptions, IWebviewEditorService, WebviewInputOptions } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { extHostNamedCustomer } from '../common/extHostCustomers'; -import { CustomFileEditorInput } from 'vs/workbench/contrib/customEditor/browser/customEditorInput'; /** * Bi-directional map between webview handles and inputs. */ class WebviewHandleStore { - private readonly _handlesToInputs = new Map(); - private readonly _inputsToHandles = new Map(); + private readonly _handlesToInputs = new Map(); + private readonly _inputsToHandles = new Map(); - public add(handle: string, input: WebviewEditorInput): void { + public add(handle: string, input: WebviewInput): void { this._handlesToInputs.set(handle, input); this._inputsToHandles.set(input, handle); } - public getHandleForInput(input: WebviewEditorInput): string | undefined { + public getHandleForInput(input: WebviewInput): string | undefined { return this._inputsToHandles.get(input); } - public getInputForHandle(handle: string): WebviewEditorInput | undefined { + public getInputForHandle(handle: string): WebviewInput | undefined { return this._handlesToInputs.get(handle); } @@ -68,8 +71,6 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews 'vscode-insider', ]); - private static revivalPool = 0; - private readonly _proxy: ExtHostWebviewsShape; private readonly _webviewEditorInputs = new WebviewHandleStore(); private readonly _revivers = new Map(); @@ -94,8 +95,8 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews // This reviver's only job is to activate webview panel extensions // This should trigger the real reviver to be registered from the extension host side. this._register(_webviewEditorService.registerResolver({ - canResolve: (webview: WebviewEditorInput) => { - if (!webview.webview.state && webview.getTypeId() === WebviewEditorInput.typeId) { // TODO: The typeid check is a workaround for the CustomFileEditorInput case + canResolve: (webview: WebviewInput) => { + if (!webview.webview.state && webview.getTypeId() === WebviewInput.typeId) { // TODO: The typeid check is a workaround for the CustomFileEditorInput case return false; } @@ -206,7 +207,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews return; } - const handle = `revival-${MainThreadWebviews.revivalPool++}`; + const handle = generateUuid(); this._webviewEditorInputs.add(handle, webviewEditorInput); this.hookupWebviewEventDelegate(handle, webviewEditorInput); @@ -246,13 +247,19 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews this._editorProviders.set(viewType, this._webviewEditorService.registerResolver({ canResolve: (webviewEditorInput) => { - return webviewEditorInput.getTypeId() !== WebviewEditorInput.typeId && webviewEditorInput.viewType === viewType; + return webviewEditorInput.getTypeId() !== WebviewInput.typeId && webviewEditorInput.viewType === viewType; }, resolveWebview: async (webview) => { - const handle = `resolved-${MainThreadWebviews.revivalPool++}`; + const handle = generateUuid(); this._webviewEditorInputs.add(handle, webview); this.hookupWebviewEventDelegate(handle, webview); + if (webview instanceof CustomFileEditorInput) { + webview.onWillSave(e => { + e.waitUntil(this._proxy.$save(handle)); + }); + } + try { await this._proxy.$resolveWebviewEditor( webview.getResource(), @@ -292,7 +299,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews return viewType.replace(/^mainThreadWebview-/, ''); } - private hookupWebviewEventDelegate(handle: WebviewPanelHandle, input: WebviewEditorInput) { + private hookupWebviewEventDelegate(handle: WebviewPanelHandle, input: WebviewInput) { input.webview.onDidClickLink((uri: URI) => this.onDidClickLink(handle, uri)); input.webview.onMessage((message: any) => this._proxy.$onMessage(handle, message)); input.onDispose(() => { @@ -317,21 +324,31 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews const activeInput = this._editorService.activeControl && this._editorService.activeControl.input; const viewStates: WebviewPanelViewStateData = {}; + + const updateViewStatesForInput = (group: IEditorGroup, topLevelInput: IEditorInput, editorInput: IEditorInput) => { + if (!(editorInput instanceof WebviewInput)) { + return; + } + + editorInput.updateGroup(group.id); + + const handle = this._webviewEditorInputs.getHandleForInput(editorInput); + if (handle) { + viewStates[handle] = { + visible: topLevelInput.matches(group.activeEditor), + active: topLevelInput.matches(activeInput), + position: editorGroupToViewColumn(this._editorGroupService, group.id), + }; + } + }; + for (const group of this._editorGroupService.groups) { for (const input of group.editors) { - if (!(input instanceof WebviewEditorInput)) { - continue; - } - - input.updateGroup(group.id); - - const handle = this._webviewEditorInputs.getHandleForInput(input); - if (handle) { - viewStates[handle] = { - visible: input === group.activeEditor, - active: input === activeInput, - position: editorGroupToViewColumn(this._editorGroupService, group.id), - }; + if (input instanceof DiffEditorInput) { + updateViewStatesForInput(group, input, input.master); + updateViewStatesForInput(group, input, input.details); + } else { + updateViewStatesForInput(group, input, input); } } } @@ -348,7 +365,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews } } - private isSupportedLink(webview: WebviewEditorInput, link: URI): boolean { + private isSupportedLink(webview: WebviewInput, link: URI): boolean { if (MainThreadWebviews.standardSupportedLinkSchemes.has(link.scheme)) { return true; } @@ -358,7 +375,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews return !!webview.webview.contentOptions.enableCommandUris && link.scheme === 'command'; } - private getWebviewEditorInput(handle: WebviewPanelHandle): WebviewEditorInput { + private getWebviewEditorInput(handle: WebviewPanelHandle): WebviewInput { const webview = this.tryGetWebviewEditorInput(handle); if (!webview) { throw new Error('Unknown webview handle:' + handle); @@ -366,7 +383,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews return webview; } - private tryGetWebviewEditorInput(handle: WebviewPanelHandle): WebviewEditorInput | undefined { + private tryGetWebviewEditorInput(handle: WebviewPanelHandle): WebviewInput | undefined { return this._webviewEditorInputs.getInputForHandle(handle); } diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index f21fa19000..31ec9b9e7a 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -8,16 +8,14 @@ import { isPromiseCanceledError } from 'vs/base/common/errors'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { isNative } from 'vs/base/common/platform'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape, IWorkspaceData, ITextSearchComplete } from '../common/extHost.protocol'; @@ -25,6 +23,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { isEqualOrParent } from 'vs/base/common/resources'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { IFileService } from 'vs/platform/files/common/files'; @extHostNamedCustomer(MainContext.MainThreadWorkspace) export class MainThreadWorkspace implements MainThreadWorkspaceShape { @@ -44,10 +43,18 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { @IWindowService private readonly _windowService: IWindowService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILabelService private readonly _labelService: ILabelService, - @IEnvironmentService private readonly _environmentService: IEnvironmentService + @IEnvironmentService private readonly _environmentService: IEnvironmentService, + @IFileService fileService: IFileService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace); - this._contextService.getCompleteWorkspace().then(workspace => this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace))); + const workspace = this._contextService.getWorkspace(); + // The workspace file is provided be a unknown file system provider. It might come + // from the extension host. So initialize now knowing that `rootPath` is undefined. + if (workspace.configuration && !isNative && !fileService.canHandleResource(workspace.configuration)) { + this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace)); + } else { + this._contextService.getCompleteWorkspace().then(workspace => this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace))); + } this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose); this._contextService.onDidChangeWorkbenchState(this._onDidChangeWorkspace, this, this._toDispose); } @@ -214,19 +221,3 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { return this._windowService.resolveProxy(url); } } - -CommandsRegistry.registerCommand('_workbench.enterWorkspace', async function (accessor: ServicesAccessor, workspace: URI, disableExtensions: string[]) { - const workspaceEditingService = accessor.get(IWorkspaceEditingService); - const extensionService = accessor.get(IExtensionService); - const windowService = accessor.get(IWindowService); - - if (disableExtensions && disableExtensions.length) { - const runningExtensions = await extensionService.getExtensions(); - // If requested extension to disable is running, then reload window with given workspace - if (disableExtensions && runningExtensions.some(runningExtension => disableExtensions.some(id => ExtensionIdentifier.equals(runningExtension.identifier, id)))) { - return windowService.openWindow([{ workspaceUri: workspace }], { args: { _: [], 'disable-extension': disableExtensions } }); - } - } - - return workspaceEditingService.enterWorkspace(workspace); -}); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f7f103acc3..d5d8703885 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -15,7 +15,7 @@ import { OverviewRulerLane } from 'vs/editor/common/model'; import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration'; import { score } from 'vs/editor/common/modes/languageSelector'; import * as files from 'vs/platform/files/common/files'; -import { ExtHostContext, MainContext, ExtHostLogServiceShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostContext, MainContext, ExtHostLogServiceShape, UIKind } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostApiCommands } from 'vs/workbench/api/common/extHostApiCommands'; import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; @@ -252,6 +252,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I }, get remoteName() { return getRemoteName(initData.remote.authority); + }, + get uiKind() { + checkProposedApiEnabled(extension); + return initData.uiKind; } }; if (!initData.environment.extensionTestsLocationURI) { @@ -905,6 +909,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I CallHierarchyItem: extHostTypes.CallHierarchyItem, Decoration: extHostTypes.Decoration, WebviewEditorState: extHostTypes.WebviewEditorState, + UIKind: UIKind }; }; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 0d5ac6869d..55f777c71a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -91,6 +91,7 @@ export interface IInitData { logsLocation: URI; autoStart: boolean; remote: { isRemote: boolean; authority: string | undefined; }; + uiKind: UIKind; } export interface IConfigurationInitData extends IConfigurationData { @@ -109,6 +110,11 @@ export interface IExtHostContext extends IRPCProtocol { export interface IMainContext extends IRPCProtocol { } +export enum UIKind { + Desktop = 1, + Web = 2 +} + // --- main thread export interface MainThreadClipboardShape extends IDisposable { @@ -571,6 +577,7 @@ export interface ExtHostWebviewsShape { $onDidDisposeWebviewPanel(handle: WebviewPanelHandle): Promise; $deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; $resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise; + $save(handle: WebviewPanelHandle): Promise; } export interface MainThreadUrlsShape extends IDisposable { @@ -1191,6 +1198,7 @@ export interface ExtHostTaskShape { $OnDidEndTask(execution: tasks.TaskExecutionDTO): void; $resolveVariables(workspaceFolder: UriComponents, toResolve: { process?: { name: string; cwd?: string }, variables: string[] }): Promise<{ process?: string; variables: { [key: string]: string } }>; $getDefaultShellAndArgs(): Thenable<{ shell: string, args: string[] | string | undefined }>; + $jsonTasksSupported(): Thenable; } export interface IBreakpointDto { diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index d0302877a0..8adc9c3b16 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -3,12 +3,27 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; -import { ExtHostTaskShape } from 'vs/workbench/api/common/extHost.protocol'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import * as Objects from 'vs/base/common/objects'; +import { asPromise } from 'vs/base/common/async'; +import { Event, Emitter } from 'vs/base/common/event'; + +import { MainContext, MainThreadTaskShape, ExtHostTaskShape } from 'vs/workbench/api/common/extHost.protocol'; + +import * as types from 'vs/workbench/api/common/extHostTypes'; +import { IExtHostWorkspaceProvider, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import * as vscode from 'vscode'; -import { TaskSystemInfoDTO } from '../common/shared/tasks'; +import * as tasks from '../common/shared/tasks'; +import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Schemas } from 'vs/base/common/network'; +import * as Platform from 'vs/base/common/platform'; export interface IExtHostTask extends ExtHostTaskShape { @@ -21,10 +36,703 @@ export interface IExtHostTask extends ExtHostTaskShape { onDidEndTaskProcess: Event; registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable; - registerTaskSystem(scheme: string, info: TaskSystemInfoDTO): void; + registerTaskSystem(scheme: string, info: tasks.TaskSystemInfoDTO): void; fetchTasks(filter?: vscode.TaskFilter): Promise; executeTask(extension: IExtensionDescription, task: vscode.Task): Promise; terminateTask(execution: vscode.TaskExecution): Promise; } +export namespace TaskDefinitionDTO { + export function from(value: vscode.TaskDefinition): tasks.TaskDefinitionDTO | undefined { + if (value === undefined || value === null) { + return undefined; + } + return value; + } + export function to(value: tasks.TaskDefinitionDTO): vscode.TaskDefinition | undefined { + if (value === undefined || value === null) { + return undefined; + } + return value; + } +} + +export namespace TaskPresentationOptionsDTO { + export function from(value: vscode.TaskPresentationOptions): tasks.TaskPresentationOptionsDTO | undefined { + if (value === undefined || value === null) { + return undefined; + } + return value; + } + export function to(value: tasks.TaskPresentationOptionsDTO): vscode.TaskPresentationOptions | undefined { + if (value === undefined || value === null) { + return undefined; + } + return value; + } +} + +export namespace ProcessExecutionOptionsDTO { + export function from(value: vscode.ProcessExecutionOptions): tasks.ProcessExecutionOptionsDTO | undefined { + if (value === undefined || value === null) { + return undefined; + } + return value; + } + export function to(value: tasks.ProcessExecutionOptionsDTO): vscode.ProcessExecutionOptions | undefined { + if (value === undefined || value === null) { + return undefined; + } + return value; + } +} + +export namespace ProcessExecutionDTO { + export function is(value: tasks.ShellExecutionDTO | tasks.ProcessExecutionDTO | tasks.CustomExecution2DTO | undefined): value is tasks.ProcessExecutionDTO { + if (value) { + const candidate = value as tasks.ProcessExecutionDTO; + return candidate && !!candidate.process; + } else { + return false; + } + } + export function from(value: vscode.ProcessExecution): tasks.ProcessExecutionDTO | undefined { + if (value === undefined || value === null) { + return undefined; + } + const result: tasks.ProcessExecutionDTO = { + process: value.process, + args: value.args + }; + if (value.options) { + result.options = ProcessExecutionOptionsDTO.from(value.options); + } + return result; + } + export function to(value: tasks.ProcessExecutionDTO): types.ProcessExecution | undefined { + if (value === undefined || value === null) { + return undefined; + } + return new types.ProcessExecution(value.process, value.args, value.options); + } +} + +export namespace ShellExecutionOptionsDTO { + export function from(value: vscode.ShellExecutionOptions): tasks.ShellExecutionOptionsDTO | undefined { + if (value === undefined || value === null) { + return undefined; + } + return value; + } + export function to(value: tasks.ShellExecutionOptionsDTO): vscode.ShellExecutionOptions | undefined { + if (value === undefined || value === null) { + return undefined; + } + return value; + } +} + +export namespace ShellExecutionDTO { + export function is(value: tasks.ShellExecutionDTO | tasks.ProcessExecutionDTO | tasks.CustomExecution2DTO | undefined): value is tasks.ShellExecutionDTO { + if (value) { + const candidate = value as tasks.ShellExecutionDTO; + return candidate && (!!candidate.commandLine || !!candidate.command); + } else { + return false; + } + } + export function from(value: vscode.ShellExecution): tasks.ShellExecutionDTO | undefined { + if (value === undefined || value === null) { + return undefined; + } + const result: tasks.ShellExecutionDTO = { + }; + if (value.commandLine !== undefined) { + result.commandLine = value.commandLine; + } else { + result.command = value.command; + result.args = value.args; + } + if (value.options) { + result.options = ShellExecutionOptionsDTO.from(value.options); + } + return result; + } + export function to(value: tasks.ShellExecutionDTO): types.ShellExecution | undefined { + if (value === undefined || value === null || (value.command === undefined && value.commandLine === undefined)) { + return undefined; + } + if (value.commandLine) { + return new types.ShellExecution(value.commandLine, value.options); + } else { + return new types.ShellExecution(value.command!, value.args ? value.args : [], value.options); + } + } +} + +export namespace CustomExecution2DTO { + export function is(value: tasks.ShellExecutionDTO | tasks.ProcessExecutionDTO | tasks.CustomExecution2DTO | undefined): value is tasks.CustomExecution2DTO { + if (value) { + let candidate = value as tasks.CustomExecution2DTO; + return candidate && candidate.customExecution === 'customExecution2'; + } else { + return false; + } + } + + export function from(value: vscode.CustomExecution2): tasks.CustomExecution2DTO { + return { + customExecution: 'customExecution2' + }; + } +} + + +export namespace TaskHandleDTO { + export function from(value: types.Task): tasks.TaskHandleDTO { + let folder: UriComponents | undefined; + if (value.scope !== undefined && typeof value.scope !== 'number') { + folder = value.scope.uri; + } + return { + id: value._id!, + workspaceFolder: folder! + }; + } +} + +export namespace TaskDTO { + export function fromMany(tasks: vscode.Task[], extension: IExtensionDescription): tasks.TaskDTO[] { + if (tasks === undefined || tasks === null) { + return []; + } + const result: tasks.TaskDTO[] = []; + for (let task of tasks) { + const converted = from(task, extension); + if (converted) { + result.push(converted); + } + } + return result; + } + + export function from(value: vscode.Task, extension: IExtensionDescription): tasks.TaskDTO | undefined { + if (value === undefined || value === null) { + return undefined; + } + let execution: tasks.ShellExecutionDTO | tasks.ProcessExecutionDTO | tasks.CustomExecution2DTO | undefined; + if (value.execution instanceof types.ProcessExecution) { + execution = ProcessExecutionDTO.from(value.execution); + } else if (value.execution instanceof types.ShellExecution) { + execution = ShellExecutionDTO.from(value.execution); + } else if ((value).execution2 && (value).execution2 instanceof types.CustomExecution2) { + execution = CustomExecution2DTO.from((value).execution2); + } + + const definition: tasks.TaskDefinitionDTO | undefined = TaskDefinitionDTO.from(value.definition); + let scope: number | UriComponents; + if (value.scope) { + if (typeof value.scope === 'number') { + scope = value.scope; + } else { + scope = value.scope.uri; + } + } else { + // To continue to support the deprecated task constructor that doesn't take a scope, we must add a scope here: + scope = types.TaskScope.Workspace; + } + if (!definition || !scope) { + return undefined; + } + const group = (value.group as types.TaskGroup) ? (value.group as types.TaskGroup).id : undefined; + const result: tasks.TaskDTO = { + _id: (value as types.Task)._id!, + definition, + name: value.name, + source: { + extensionId: extension.identifier.value, + label: value.source, + scope: scope + }, + execution: execution!, + isBackground: value.isBackground, + group: group, + presentationOptions: TaskPresentationOptionsDTO.from(value.presentationOptions), + problemMatchers: value.problemMatchers, + hasDefinedMatchers: (value as types.Task).hasDefinedMatchers, + runOptions: (value).runOptions ? (value).runOptions : { reevaluateOnRerun: true }, + }; + return result; + } + export async function to(value: tasks.TaskDTO | undefined, workspace: IExtHostWorkspaceProvider): Promise { + if (value === undefined || value === null) { + return undefined; + } + let execution: types.ShellExecution | types.ProcessExecution | undefined; + if (ProcessExecutionDTO.is(value.execution)) { + execution = ProcessExecutionDTO.to(value.execution); + } else if (ShellExecutionDTO.is(value.execution)) { + execution = ShellExecutionDTO.to(value.execution); + } + const definition: vscode.TaskDefinition | undefined = TaskDefinitionDTO.to(value.definition); + let scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder | undefined; + if (value.source) { + if (value.source.scope !== undefined) { + if (typeof value.source.scope === 'number') { + scope = value.source.scope; + } else { + scope = await workspace.resolveWorkspaceFolder(URI.revive(value.source.scope)); + } + } else { + scope = types.TaskScope.Workspace; + } + } + if (!definition || !scope) { + return undefined; + } + const result = new types.Task(definition, scope, value.name!, value.source.label, execution, value.problemMatchers); + if (value.isBackground !== undefined) { + result.isBackground = value.isBackground; + } + if (value.group !== undefined) { + result.group = types.TaskGroup.from(value.group); + } + if (value.presentationOptions) { + result.presentationOptions = TaskPresentationOptionsDTO.to(value.presentationOptions)!; + } + if (value._id) { + result._id = value._id; + } + return result; + } +} + +export namespace TaskFilterDTO { + export function from(value: vscode.TaskFilter | undefined): tasks.TaskFilterDTO | undefined { + return value; + } + + export function to(value: tasks.TaskFilterDTO): vscode.TaskFilter | undefined { + if (!value) { + return undefined; + } + return Objects.assign(Object.create(null), value); + } +} + +class TaskExecutionImpl implements vscode.TaskExecution { + + constructor(private readonly _tasks: ExtHostTaskBase, readonly _id: string, private readonly _task: vscode.Task) { + } + + public get task(): vscode.Task { + return this._task; + } + + public terminate(): void { + this._tasks.terminateTask(this); + } + + public fireDidStartProcess(value: tasks.TaskProcessStartedDTO): void { + } + + public fireDidEndProcess(value: tasks.TaskProcessEndedDTO): void { + } +} + +export namespace TaskExecutionDTO { + export async function to(value: tasks.TaskExecutionDTO, tasks: ExtHostTaskBase, workspaceProvider: IExtHostWorkspaceProvider): Promise { + const task = await TaskDTO.to(value.task, workspaceProvider); + if (!task) { + throw new Error('Unexpected: Task cannot be created.'); + } + return new TaskExecutionImpl(tasks, value.id, task); + } + export function from(value: vscode.TaskExecution): tasks.TaskExecutionDTO { + return { + id: (value as TaskExecutionImpl)._id, + task: undefined + }; + } +} + +export interface HandlerData { + type: string; + provider: vscode.TaskProvider; + extension: IExtensionDescription; +} + +export abstract class ExtHostTaskBase implements ExtHostTaskShape { + readonly _serviceBrand: undefined; + + protected readonly _proxy: MainThreadTaskShape; + protected readonly _workspaceProvider: IExtHostWorkspaceProvider; + protected readonly _editorService: IExtHostDocumentsAndEditors; + protected readonly _configurationService: IExtHostConfiguration; + protected readonly _terminalService: IExtHostTerminalService; + protected _handleCounter: number; + protected _handlers: Map; + protected _taskExecutions: Map; + protected _providedCustomExecutions2: Map; + private _notProvidedCustomExecutions: Set; // Used for custom executions tasks that are created and run through executeTask. + protected _activeCustomExecutions2: Map; + private _lastStartedTask: string | undefined; + protected readonly _onDidExecuteTask: Emitter = new Emitter(); + protected readonly _onDidTerminateTask: Emitter = new Emitter(); + + protected readonly _onDidTaskProcessStarted: Emitter = new Emitter(); + protected readonly _onDidTaskProcessEnded: Emitter = new Emitter(); + + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtHostWorkspace workspaceService: IExtHostWorkspace, + @IExtHostDocumentsAndEditors editorService: IExtHostDocumentsAndEditors, + @IExtHostConfiguration configurationService: IExtHostConfiguration, + @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService + ) { + this._proxy = extHostRpc.getProxy(MainContext.MainThreadTask); + this._workspaceProvider = workspaceService; + this._editorService = editorService; + this._configurationService = configurationService; + this._terminalService = extHostTerminalService; + this._handleCounter = 0; + this._handlers = new Map(); + this._taskExecutions = new Map(); + this._providedCustomExecutions2 = new Map(); + this._notProvidedCustomExecutions = new Set(); + this._activeCustomExecutions2 = new Map(); + } + + public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable { + if (!provider) { + return new types.Disposable(() => { }); + } + const handle = this.nextHandle(); + this._handlers.set(handle, { type, provider, extension }); + this._proxy.$registerTaskProvider(handle, type); + return new types.Disposable(() => { + this._handlers.delete(handle); + this._proxy.$unregisterTaskProvider(handle); + }); + } + + public registerTaskSystem(scheme: string, info: tasks.TaskSystemInfoDTO): void { + this._proxy.$registerTaskSystem(scheme, info); + } + + public fetchTasks(filter?: vscode.TaskFilter): Promise { + return this._proxy.$fetchTasks(TaskFilterDTO.from(filter)).then(async (values) => { + const result: vscode.Task[] = []; + for (let value of values) { + const task = await TaskDTO.to(value, this._workspaceProvider); + if (task) { + result.push(task); + } + } + return result; + }); + } + + public abstract async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise; + + public get taskExecutions(): vscode.TaskExecution[] { + const result: vscode.TaskExecution[] = []; + this._taskExecutions.forEach(value => result.push(value)); + return result; + } + + public terminateTask(execution: vscode.TaskExecution): Promise { + if (!(execution instanceof TaskExecutionImpl)) { + throw new Error('No valid task execution provided'); + } + return this._proxy.$terminateTask((execution as TaskExecutionImpl)._id); + } + + public get onDidStartTask(): Event { + return this._onDidExecuteTask.event; + } + + public async $onDidStartTask(execution: tasks.TaskExecutionDTO, terminalId: number): Promise { + const execution2: vscode.CustomExecution2 | undefined = this._providedCustomExecutions2.get(execution.id); + if (execution2) { + if (this._activeCustomExecutions2.get(execution.id) !== undefined) { + throw new Error('We should not be trying to start the same custom task executions twice.'); + } + + // Clone the custom execution to keep the original untouched. This is important for multiple runs of the same task. + this._activeCustomExecutions2.set(execution.id, execution2); + this._terminalService.attachPtyToTerminal(terminalId, await execution2.callback()); + } + this._lastStartedTask = execution.id; + + this._onDidExecuteTask.fire({ + execution: await this.getTaskExecution(execution) + }); + } + + public get onDidEndTask(): Event { + return this._onDidTerminateTask.event; + } + + public async $OnDidEndTask(execution: tasks.TaskExecutionDTO): Promise { + const _execution = await this.getTaskExecution(execution); + this._taskExecutions.delete(execution.id); + this.customExecutionComplete(execution); + this._onDidTerminateTask.fire({ + execution: _execution + }); + } + + public get onDidStartTaskProcess(): Event { + return this._onDidTaskProcessStarted.event; + } + + public async $onDidStartTaskProcess(value: tasks.TaskProcessStartedDTO): Promise { + const execution = await this.getTaskExecution(value.id); + if (execution) { + this._onDidTaskProcessStarted.fire({ + execution: execution, + processId: value.processId + }); + } + } + + public get onDidEndTaskProcess(): Event { + return this._onDidTaskProcessEnded.event; + } + + public async $onDidEndTaskProcess(value: tasks.TaskProcessEndedDTO): Promise { + const execution = await this.getTaskExecution(value.id); + if (execution) { + this._onDidTaskProcessEnded.fire({ + execution: execution, + exitCode: value.exitCode + }); + } + } + + protected abstract provideTasksInternal(validTypes: { [key: string]: boolean; }, taskIdPromises: Promise[], handler: HandlerData, value: vscode.Task[] | null | undefined): { tasks: tasks.TaskDTO[], extension: IExtensionDescription }; + + public $provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Thenable { + const handler = this._handlers.get(handle); + if (!handler) { + return Promise.reject(new Error('no handler found')); + } + + // Set up a list of task ID promises that we can wait on + // before returning the provided tasks. The ensures that + // our task IDs are calculated for any custom execution tasks. + // Knowing this ID ahead of time is needed because when a task + // start event is fired this is when the custom execution is called. + // The task start event is also the first time we see the ID from the main + // thread, which is too late for us because we need to save an map + // from an ID to the custom execution function. (Kind of a cart before the horse problem). + const taskIdPromises: Promise[] = []; + const fetchPromise = asPromise(() => handler.provider.provideTasks(CancellationToken.None)).then(value => { + return this.provideTasksInternal(validTypes, taskIdPromises, handler, value); + }); + + return new Promise((resolve) => { + fetchPromise.then((result) => { + Promise.all(taskIdPromises).then(() => { + resolve(result); + }); + }); + }); + } + + protected abstract async resolveTaskInternal(resolvedTaskDTO: tasks.TaskDTO): Promise; + + public async $resolveTask(handle: number, taskDTO: tasks.TaskDTO): Promise { + const handler = this._handlers.get(handle); + if (!handler) { + return Promise.reject(new Error('no handler found')); + } + + if (taskDTO.definition.type !== handler.type) { + throw new Error(`Unexpected: Task of type [${taskDTO.definition.type}] cannot be resolved by provider of type [${handler.type}].`); + } + + const task = await TaskDTO.to(taskDTO, this._workspaceProvider); + if (!task) { + throw new Error('Unexpected: Task cannot be resolved.'); + } + + const resolvedTask = await handler.provider.resolveTask(task, CancellationToken.None); + if (!resolvedTask) { + return undefined; // {{SQL CARBON EDIT}} strict-null-check + } + + const resolvedTaskDTO: tasks.TaskDTO | undefined = TaskDTO.from(resolvedTask, handler.extension); + if (!resolvedTaskDTO) { + throw new Error('Unexpected: Task cannot be resolved.'); + } + + if (resolvedTask.definition !== task.definition) { + throw new Error('Unexpected: The resolved task definition must be the same object as the original task definition. The task definition cannot be changed.'); + } + + if (CustomExecution2DTO.is(resolvedTaskDTO.execution)) { + await this.addCustomExecution2(resolvedTaskDTO, resolvedTask, true); + } + + return await this.resolveTaskInternal(resolvedTaskDTO); + } + + public abstract async $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }>; + + public abstract $getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }>; + + private nextHandle(): number { + return this._handleCounter++; + } + + protected async addCustomExecution2(taskDTO: tasks.TaskDTO, task: vscode.Task2, isProvided: boolean): Promise { + const taskId = await this._proxy.$createTaskId(taskDTO); + if (!isProvided && !this._providedCustomExecutions2.has(taskId)) { + this._notProvidedCustomExecutions.add(taskId); + } + this._providedCustomExecutions2.set(taskId, (task).execution2); + } + + protected async getTaskExecution(execution: tasks.TaskExecutionDTO | string, task?: vscode.Task): Promise { + if (typeof execution === 'string') { + const taskExecution = this._taskExecutions.get(execution); + if (!taskExecution) { + throw new Error('Unexpected: The specified task is missing an execution'); + } + return taskExecution; + } + + let result: TaskExecutionImpl | undefined = this._taskExecutions.get(execution.id); + if (result) { + return result; + } + const taskToCreate = task ? task : await TaskDTO.to(execution.task, this._workspaceProvider); + if (!taskToCreate) { + throw new Error('Unexpected: Task does not exist.'); + } + const createdResult: TaskExecutionImpl = new TaskExecutionImpl(this, execution.id, taskToCreate); + this._taskExecutions.set(execution.id, createdResult); + return createdResult; + } + + private customExecutionComplete(execution: tasks.TaskExecutionDTO): void { + const extensionCallback2: vscode.CustomExecution2 | undefined = this._activeCustomExecutions2.get(execution.id); + if (extensionCallback2) { + this._activeCustomExecutions2.delete(execution.id); + } + + // Technically we don't really need to do this, however, if an extension + // is executing a task through "executeTask" over and over again + // with different properties in the task definition, then the map of executions + // could grow indefinitely, something we don't want. + if (this._notProvidedCustomExecutions.has(execution.id) && (this._lastStartedTask !== execution.id)) { + this._providedCustomExecutions2.delete(execution.id); + this._notProvidedCustomExecutions.delete(execution.id); + } + let iterator = this._notProvidedCustomExecutions.values(); + let iteratorResult = iterator.next(); + while (!iteratorResult.done) { + if (!this._activeCustomExecutions2.has(iteratorResult.value) && (this._lastStartedTask !== iteratorResult.value)) { + this._providedCustomExecutions2.delete(iteratorResult.value); + this._notProvidedCustomExecutions.delete(iteratorResult.value); + } + iteratorResult = iterator.next(); + } + } + + public abstract async $jsonTasksSupported(): Promise; +} + +export class WorkerExtHostTask extends ExtHostTaskBase { + constructor( + @IExtHostRpcService extHostRpc: IExtHostRpcService, + @IExtHostInitDataService initData: IExtHostInitDataService, + @IExtHostWorkspace workspaceService: IExtHostWorkspace, + @IExtHostDocumentsAndEditors editorService: IExtHostDocumentsAndEditors, + @IExtHostConfiguration configurationService: IExtHostConfiguration, + @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService + ) { + super(extHostRpc, initData, workspaceService, editorService, configurationService, extHostTerminalService); + if (initData.remote.isRemote && initData.remote.authority) { + this.registerTaskSystem(Schemas.vscodeRemote, { + scheme: Schemas.vscodeRemote, + authority: initData.remote.authority, + platform: Platform.PlatformToString(Platform.Platform.Web) + }); + } + } + + public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise { + const dto = TaskDTO.from(task, extension); + if (dto === undefined) { + return Promise.reject(new Error('Task is not valid')); + } + + // If this task is a custom execution, then we need to save it away + // in the provided custom execution map that is cleaned up after the + // task is executed. + if (CustomExecution2DTO.is(dto.execution)) { + await this.addCustomExecution2(dto, task, false); + } else { + throw new Error('Not implemented'); + } + + return this._proxy.$executeTask(dto).then(value => this.getTaskExecution(value, task)); + } + + protected provideTasksInternal(validTypes: { [key: string]: boolean; }, taskIdPromises: Promise[], handler: HandlerData, value: vscode.Task[] | null | undefined): { tasks: tasks.TaskDTO[], extension: IExtensionDescription } { + const taskDTOs: tasks.TaskDTO[] = []; + if (value) { + for (let task of value) { + if (!task.definition || !validTypes[task.definition.type]) { + console.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); + } + + const taskDTO: tasks.TaskDTO | undefined = TaskDTO.from(task, handler.extension); + if (taskDTO && CustomExecution2DTO.is(taskDTO.execution)) { + taskDTOs.push(taskDTO); + // The ID is calculated on the main thread task side, so, let's call into it here. + // We need the task id's pre-computed for custom task executions because when OnDidStartTask + // is invoked, we have to be able to map it back to our data. + taskIdPromises.push(this.addCustomExecution2(taskDTO, task, true)); + } else { + console.warn('Only custom execution tasks supported.'); + } + } + } + return { + tasks: taskDTOs, + extension: handler.extension + }; + } + + protected async resolveTaskInternal(resolvedTaskDTO: tasks.TaskDTO): Promise { + if (CustomExecution2DTO.is(resolvedTaskDTO.execution)) { + return resolvedTaskDTO; + } else { + console.warn('Only custom execution tasks supported.'); + } + return undefined; + } + + public async $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }> { + const result = { + process: undefined as string, + variables: Object.create(null) + }; + return result; + } + + public $getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }> { + throw new Error('Not implemented'); + } + + public async $jsonTasksSupported(): Promise { + return false; + } +} + export const IExtHostTask = createDecorator('IExtHostTask'); diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index e9a66e62a9..a3d167b312 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -41,8 +41,7 @@ export class ExtHostWebview implements vscode.Webview { public get cspSource(): string { return this._initData.webviewCspSource - .replace('{{uuid}}', this._handle) - .replace('{{commit}}', this._initData.commit || '211fa02efe8c041fd7baa8ec3dce199d5185aa44'); + .replace('{{uuid}}', this._handle); } public get html(): string { @@ -224,6 +223,18 @@ export class ExtHostWebviewEditor implements vscode.WebviewEditor { this._proxy.$setState(this._handle, typeConverters.WebviewEditorState.from(newState)); } + private readonly _onWillSave = new Emitter<{ waitUntil: (thenable: Thenable) => void }>(); + public readonly onWillSave = this._onWillSave.event; + + async _save(): Promise { + const waitingOn: Thenable[] = []; + this._onWillSave.fire({ + waitUntil: (thenable: Thenable): void => { waitingOn.push(thenable); }, + }); + const result = await Promise.all(waitingOn); + return result.every(x => x); + } + public postMessage(message: any): Promise { this.assertNotDisposed(); return this._proxy.$postMessage(this._handle, message); @@ -422,6 +433,13 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { return Promise.resolve(provider.resolveWebviewEditor(URI.revive(resource), revivedPanel)); } + async $save(handle: WebviewPanelHandle): Promise { + const panel = this.getWebviewPanel(handle); + if (panel) { + return panel._save(); + } + return false; + } } function convertWebviewOptions( diff --git a/src/vs/workbench/api/common/shared/webview.ts b/src/vs/workbench/api/common/shared/webview.ts index 4f61c3c04a..9837cdfc15 100644 --- a/src/vs/workbench/api/common/shared/webview.ts +++ b/src/vs/workbench/api/common/shared/webview.ts @@ -7,7 +7,6 @@ import { URI } from 'vs/base/common/uri'; import * as vscode from 'vscode'; export interface WebviewInitData { - readonly commit?: string; readonly webviewResourceRoot: string; readonly webviewCspSource: string; } @@ -18,7 +17,6 @@ export function asWebviewUri( resource: vscode.Uri, ): vscode.Uri { const uri = initData.webviewResourceRoot - .replace('{{commit}}', initData.commit || '211fa02efe8c041fd7baa8ec3dce199d5185aa44') .replace('{{resource}}', resource.toString().replace(/^\S+?:/, '')) .replace('{{uuid}}', uuid); return URI.parse(uri); diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 3c327050ba..07c6082d59 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -6,375 +6,23 @@ import * as path from 'vs/base/common/path'; import { URI, UriComponents } from 'vs/base/common/uri'; -import * as Objects from 'vs/base/common/objects'; -import { asPromise } from 'vs/base/common/async'; -import { Event, Emitter } from 'vs/base/common/event'; import { win32 } from 'vs/base/node/processes'; - - -import { MainContext, MainThreadTaskShape, ExtHostTaskShape } from 'vs/workbench/api/common/extHost.protocol'; - import * as types from 'vs/workbench/api/common/extHostTypes'; -import { IExtHostWorkspaceProvider, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; +import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import * as vscode from 'vscode'; -import { - TaskDefinitionDTO, TaskExecutionDTO, TaskPresentationOptionsDTO, - ProcessExecutionOptionsDTO, ProcessExecutionDTO, - ShellExecutionOptionsDTO, ShellExecutionDTO, - CustomExecution2DTO, - TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, TaskSetDTO -} from '../common/shared/tasks'; +import * as tasks from '../common/shared/tasks'; import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService'; import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { ExtHostTaskBase, TaskHandleDTO, TaskDTO, CustomExecution2DTO, HandlerData } from 'vs/workbench/api/common/extHostTask'; import { Schemas } from 'vs/base/common/network'; -namespace TaskDefinitionDTO { - export function from(value: vscode.TaskDefinition): TaskDefinitionDTO | undefined { - if (value === undefined || value === null) { - return undefined; - } - return value; - } - export function to(value: TaskDefinitionDTO): vscode.TaskDefinition | undefined { - if (value === undefined || value === null) { - return undefined; - } - return value; - } -} - -namespace TaskPresentationOptionsDTO { - export function from(value: vscode.TaskPresentationOptions): TaskPresentationOptionsDTO | undefined { - if (value === undefined || value === null) { - return undefined; - } - return value; - } - export function to(value: TaskPresentationOptionsDTO): vscode.TaskPresentationOptions | undefined { - if (value === undefined || value === null) { - return undefined; - } - return value; - } -} - -namespace ProcessExecutionOptionsDTO { - export function from(value: vscode.ProcessExecutionOptions): ProcessExecutionOptionsDTO | undefined { - if (value === undefined || value === null) { - return undefined; - } - return value; - } - export function to(value: ProcessExecutionOptionsDTO): vscode.ProcessExecutionOptions | undefined { - if (value === undefined || value === null) { - return undefined; - } - return value; - } -} - -namespace ProcessExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO | undefined): value is ProcessExecutionDTO { - if (value) { - const candidate = value as ProcessExecutionDTO; - return candidate && !!candidate.process; - } else { - return false; - } - } - export function from(value: vscode.ProcessExecution): ProcessExecutionDTO | undefined { - if (value === undefined || value === null) { - return undefined; - } - const result: ProcessExecutionDTO = { - process: value.process, - args: value.args - }; - if (value.options) { - result.options = ProcessExecutionOptionsDTO.from(value.options); - } - return result; - } - export function to(value: ProcessExecutionDTO): types.ProcessExecution | undefined { - if (value === undefined || value === null) { - return undefined; - } - return new types.ProcessExecution(value.process, value.args, value.options); - } -} - -namespace ShellExecutionOptionsDTO { - export function from(value: vscode.ShellExecutionOptions): ShellExecutionOptionsDTO | undefined { - if (value === undefined || value === null) { - return undefined; - } - return value; - } - export function to(value: ShellExecutionOptionsDTO): vscode.ShellExecutionOptions | undefined { - if (value === undefined || value === null) { - return undefined; - } - return value; - } -} - -namespace ShellExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO | undefined): value is ShellExecutionDTO { - if (value) { - const candidate = value as ShellExecutionDTO; - return candidate && (!!candidate.commandLine || !!candidate.command); - } else { - return false; - } - } - export function from(value: vscode.ShellExecution): ShellExecutionDTO | undefined { - if (value === undefined || value === null) { - return undefined; - } - const result: ShellExecutionDTO = { - }; - if (value.commandLine !== undefined) { - result.commandLine = value.commandLine; - } else { - result.command = value.command; - result.args = value.args; - } - if (value.options) { - result.options = ShellExecutionOptionsDTO.from(value.options); - } - return result; - } - export function to(value: ShellExecutionDTO): types.ShellExecution | undefined { - if (value === undefined || value === null || (value.command === undefined && value.commandLine === undefined)) { - return undefined; - } - if (value.commandLine) { - return new types.ShellExecution(value.commandLine, value.options); - } else { - return new types.ShellExecution(value.command!, value.args ? value.args : [], value.options); - } - } -} - -namespace CustomExecution2DTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO | undefined): value is CustomExecution2DTO { - if (value) { - let candidate = value as CustomExecution2DTO; - return candidate && candidate.customExecution === 'customExecution2'; - } else { - return false; - } - } - - export function from(value: vscode.CustomExecution2): CustomExecution2DTO { - return { - customExecution: 'customExecution2' - }; - } -} - -namespace TaskHandleDTO { - export function from(value: types.Task): TaskHandleDTO { - let folder: UriComponents | undefined; - if (value.scope !== undefined && typeof value.scope !== 'number') { - folder = value.scope.uri; - } - return { - id: value._id!, - workspaceFolder: folder! - }; - } -} - -namespace TaskDTO { - - export function fromMany(tasks: vscode.Task[], extension: IExtensionDescription): TaskDTO[] { - if (tasks === undefined || tasks === null) { - return []; - } - const result: TaskDTO[] = []; - for (let task of tasks) { - const converted = from(task, extension); - if (converted) { - result.push(converted); - } - } - return result; - } - - export function from(value: vscode.Task, extension: IExtensionDescription): TaskDTO | undefined { - if (value === undefined || value === null) { - return undefined; - } - let execution: ShellExecutionDTO | ProcessExecutionDTO | CustomExecution2DTO | undefined; - if (value.execution instanceof types.ProcessExecution) { - execution = ProcessExecutionDTO.from(value.execution); - } else if (value.execution instanceof types.ShellExecution) { - execution = ShellExecutionDTO.from(value.execution); - } else if ((value).execution2 && (value).execution2 instanceof types.CustomExecution2) { - execution = CustomExecution2DTO.from((value).execution2); - } - - const definition: TaskDefinitionDTO | undefined = TaskDefinitionDTO.from(value.definition); - let scope: number | UriComponents; - if (value.scope) { - if (typeof value.scope === 'number') { - scope = value.scope; - } else { - scope = value.scope.uri; - } - } else { - // To continue to support the deprecated task constructor that doesn't take a scope, we must add a scope here: - scope = types.TaskScope.Workspace; - } - if (!definition || !scope) { - return undefined; - } - const group = (value.group as types.TaskGroup) ? (value.group as types.TaskGroup).id : undefined; - const result: TaskDTO = { - _id: (value as types.Task)._id!, - definition, - name: value.name, - source: { - extensionId: extension.identifier.value, - label: value.source, - scope: scope - }, - execution: execution!, - isBackground: value.isBackground, - group: group, - presentationOptions: TaskPresentationOptionsDTO.from(value.presentationOptions), - problemMatchers: value.problemMatchers, - hasDefinedMatchers: (value as types.Task).hasDefinedMatchers, - runOptions: (value).runOptions ? (value).runOptions : { reevaluateOnRerun: true }, - }; - return result; - } - export async function to(value: TaskDTO | undefined, workspace: IExtHostWorkspaceProvider): Promise { - if (value === undefined || value === null) { - return undefined; - } - let execution: types.ShellExecution | types.ProcessExecution | undefined; - if (ProcessExecutionDTO.is(value.execution)) { - execution = ProcessExecutionDTO.to(value.execution); - } else if (ShellExecutionDTO.is(value.execution)) { - execution = ShellExecutionDTO.to(value.execution); - } - const definition: vscode.TaskDefinition | undefined = TaskDefinitionDTO.to(value.definition); - let scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder | undefined; - if (value.source) { - if (value.source.scope !== undefined) { - if (typeof value.source.scope === 'number') { - scope = value.source.scope; - } else { - scope = await workspace.resolveWorkspaceFolder(URI.revive(value.source.scope)); - } - } else { - scope = types.TaskScope.Workspace; - } - } - if (!definition || !scope) { - return undefined; - } - const result = new types.Task(definition, scope, value.name!, value.source.label, execution, value.problemMatchers); - if (value.isBackground !== undefined) { - result.isBackground = value.isBackground; - } - if (value.group !== undefined) { - result.group = types.TaskGroup.from(value.group); - } - if (value.presentationOptions) { - result.presentationOptions = TaskPresentationOptionsDTO.to(value.presentationOptions)!; - } - if (value._id) { - result._id = value._id; - } - return result; - } -} - -namespace TaskFilterDTO { - export function from(value: vscode.TaskFilter | undefined): TaskFilterDTO | undefined { - return value; - } - - export function to(value: TaskFilterDTO): vscode.TaskFilter | undefined { - if (!value) { - return undefined; - } - return Objects.assign(Object.create(null), value); - } -} - -class TaskExecutionImpl implements vscode.TaskExecution { - - constructor(private readonly _tasks: ExtHostTask, readonly _id: string, private readonly _task: vscode.Task) { - } - - public get task(): vscode.Task { - return this._task; - } - - public terminate(): void { - this._tasks.terminateTask(this); - } - - public fireDidStartProcess(value: TaskProcessStartedDTO): void { - } - - public fireDidEndProcess(value: TaskProcessEndedDTO): void { - } -} - -namespace TaskExecutionDTO { - export async function to(value: TaskExecutionDTO, tasks: ExtHostTask, workspaceProvider: IExtHostWorkspaceProvider): Promise { - const task = await TaskDTO.to(value.task, workspaceProvider); - if (!task) { - throw new Error('Unexpected: Task cannot be created.'); - } - return new TaskExecutionImpl(tasks, value.id, task); - } - export function from(value: vscode.TaskExecution): TaskExecutionDTO { - return { - id: (value as TaskExecutionImpl)._id, - task: undefined - }; - } -} - -interface HandlerData { - type: string; - provider: vscode.TaskProvider; - extension: IExtensionDescription; -} - -export class ExtHostTask implements ExtHostTaskShape { - - readonly _serviceBrand: undefined; - - private readonly _proxy: MainThreadTaskShape; - private readonly _workspaceProvider: IExtHostWorkspaceProvider; - private readonly _editorService: IExtHostDocumentsAndEditors; - private readonly _configurationService: IExtHostConfiguration; - private readonly _terminalService: IExtHostTerminalService; - private _handleCounter: number; - private _handlers: Map; - private _taskExecutions: Map; - private _providedCustomExecutions2: Map; - private _activeCustomExecutions2: Map; - - private readonly _onDidExecuteTask: Emitter = new Emitter(); - private readonly _onDidTerminateTask: Emitter = new Emitter(); - - private readonly _onDidTaskProcessStarted: Emitter = new Emitter(); - private readonly _onDidTaskProcessEnded: Emitter = new Emitter(); +export class ExtHostTask extends ExtHostTaskBase { constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @@ -384,17 +32,7 @@ export class ExtHostTask implements ExtHostTaskShape { @IExtHostConfiguration configurationService: IExtHostConfiguration, @IExtHostTerminalService extHostTerminalService: IExtHostTerminalService ) { - this._proxy = extHostRpc.getProxy(MainContext.MainThreadTask); - this._workspaceProvider = workspaceService; - this._editorService = editorService; - this._configurationService = configurationService; - this._terminalService = extHostTerminalService; - this._handleCounter = 0; - this._handlers = new Map(); - this._taskExecutions = new Map(); - this._providedCustomExecutions2 = new Map(); - this._activeCustomExecutions2 = new Map(); - + super(extHostRpc, initData, workspaceService, editorService, configurationService, extHostTerminalService); if (initData.remote.isRemote && initData.remote.authority) { this.registerTaskSystem(Schemas.vscodeRemote, { scheme: Schemas.vscodeRemote, @@ -404,36 +42,6 @@ export class ExtHostTask implements ExtHostTaskShape { } } - public registerTaskProvider(extension: IExtensionDescription, type: string, provider: vscode.TaskProvider): vscode.Disposable { - if (!provider) { - return new types.Disposable(() => { }); - } - const handle = this.nextHandle(); - this._handlers.set(handle, { type, provider, extension }); - this._proxy.$registerTaskProvider(handle, type); - return new types.Disposable(() => { - this._handlers.delete(handle); - this._proxy.$unregisterTaskProvider(handle); - }); - } - - public registerTaskSystem(scheme: string, info: TaskSystemInfoDTO): void { - this._proxy.$registerTaskSystem(scheme, info); - } - - public fetchTasks(filter?: vscode.TaskFilter): Promise { - return this._proxy.$fetchTasks(TaskFilterDTO.from(filter)).then(async (values) => { - const result: vscode.Task[] = []; - for (let value of values) { - const task = await TaskDTO.to(value, this._workspaceProvider); - if (task) { - result.push(task); - } - } - return result; - }); - } - public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise { const tTask = (task as types.Task); // We have a preserved ID. So the task didn't change. @@ -449,175 +57,42 @@ export class ExtHostTask implements ExtHostTaskShape { // in the provided custom execution map that is cleaned up after the // task is executed. if (CustomExecution2DTO.is(dto.execution)) { - await this.addCustomExecution2(dto, task); + await this.addCustomExecution2(dto, task, false); } return this._proxy.$executeTask(dto).then(value => this.getTaskExecution(value, task)); } } - public get taskExecutions(): vscode.TaskExecution[] { - const result: vscode.TaskExecution[] = []; - this._taskExecutions.forEach(value => result.push(value)); - return result; - } + protected provideTasksInternal(validTypes: { [key: string]: boolean; }, taskIdPromises: Promise[], handler: HandlerData, value: vscode.Task[] | null | undefined): { tasks: tasks.TaskDTO[], extension: IExtensionDescription } { + const taskDTOs: tasks.TaskDTO[] = []; + if (value) { + for (let task of value) { + if (!task.definition || !validTypes[task.definition.type]) { + console.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); + } - public terminateTask(execution: vscode.TaskExecution): Promise { - if (!(execution instanceof TaskExecutionImpl)) { - throw new Error('No valid task execution provided'); - } - return this._proxy.$terminateTask((execution as TaskExecutionImpl)._id); - } + const taskDTO: tasks.TaskDTO | undefined = TaskDTO.from(task, handler.extension); + if (taskDTO) { + taskDTOs.push(taskDTO); - public get onDidStartTask(): Event { - return this._onDidExecuteTask.event; - } - - public async $onDidStartTask(execution: TaskExecutionDTO, terminalId: number): Promise { - const execution2: vscode.CustomExecution2 | undefined = this._providedCustomExecutions2.get(execution.id); - if (execution2) { - if (this._activeCustomExecutions2.get(execution.id) !== undefined) { - throw new Error('We should not be trying to start the same custom task executions twice.'); - } - - // Clone the custom execution to keep the original untouched. This is important for multiple runs of the same task. - this._activeCustomExecutions2.set(execution.id, execution2); - this._terminalService.attachPtyToTerminal(terminalId, await execution2.callback()); - } - - this._onDidExecuteTask.fire({ - execution: await this.getTaskExecution(execution) - }); - } - - public get onDidEndTask(): Event { - return this._onDidTerminateTask.event; - } - - public async $OnDidEndTask(execution: TaskExecutionDTO): Promise { - const _execution = await this.getTaskExecution(execution); - this._taskExecutions.delete(execution.id); - this.customExecutionComplete(execution); - this._onDidTerminateTask.fire({ - execution: _execution - }); - } - - public get onDidStartTaskProcess(): Event { - return this._onDidTaskProcessStarted.event; - } - - public async $onDidStartTaskProcess(value: TaskProcessStartedDTO): Promise { - const execution = await this.getTaskExecution(value.id); - if (execution) { - this._onDidTaskProcessStarted.fire({ - execution: execution, - processId: value.processId - }); - } - } - - public get onDidEndTaskProcess(): Event { - return this._onDidTaskProcessEnded.event; - } - - public async $onDidEndTaskProcess(value: TaskProcessEndedDTO): Promise { - const execution = await this.getTaskExecution(value.id); - if (execution) { - this._onDidTaskProcessEnded.fire({ - execution: execution, - exitCode: value.exitCode - }); - } - } - - public $provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Thenable { - const handler = this._handlers.get(handle); - if (!handler) { - return Promise.reject(new Error('no handler found')); - } - - // Set up a list of task ID promises that we can wait on - // before returning the provided tasks. The ensures that - // our task IDs are calculated for any custom execution tasks. - // Knowing this ID ahead of time is needed because when a task - // start event is fired this is when the custom execution is called. - // The task start event is also the first time we see the ID from the main - // thread, which is too late for us because we need to save an map - // from an ID to the custom execution function. (Kind of a cart before the horse problem). - const taskIdPromises: Promise[] = []; - const fetchPromise = asPromise(() => handler.provider.provideTasks(CancellationToken.None)).then(value => { - const taskDTOs: TaskDTO[] = []; - if (value) { - for (let task of value) { - if (!task.definition || !validTypes[task.definition.type]) { - console.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); - } - - const taskDTO: TaskDTO | undefined = TaskDTO.from(task, handler.extension); - if (taskDTO) { - taskDTOs.push(taskDTO); - - if (CustomExecution2DTO.is(taskDTO.execution)) { - // The ID is calculated on the main thread task side, so, let's call into it here. - // We need the task id's pre-computed for custom task executions because when OnDidStartTask - // is invoked, we have to be able to map it back to our data. - taskIdPromises.push(this.addCustomExecution2(taskDTO, task)); - } + if (CustomExecution2DTO.is(taskDTO.execution)) { + // The ID is calculated on the main thread task side, so, let's call into it here. + // We need the task id's pre-computed for custom task executions because when OnDidStartTask + // is invoked, we have to be able to map it back to our data. + taskIdPromises.push(this.addCustomExecution2(taskDTO, task, true)); } } } - return { - tasks: taskDTOs, - extension: handler.extension - }; - }); - - return new Promise((resolve) => { - fetchPromise.then((result) => { - Promise.all(taskIdPromises).then(() => { - resolve(result); - }); - }); - }); + } + return { + tasks: taskDTOs, + extension: handler.extension + }; } - // {{SQL CARBON EDIT}} disable debug related method - public async $resolveTask(handle: number, taskDTO: TaskDTO): Promise { - /*const handler = this._handlers.get(handle); - if (!handler) { - return Promise.reject(new Error('no handler found')); - } - - if (taskDTO.definition.type !== handler.type) { - throw new Error(`Unexpected: Task of type [${taskDTO.definition.type}] cannot be resolved by provider of type [${handler.type}].`); - } - - const task = await TaskDTO.to(taskDTO, this._workspaceProvider); - if (!task) { - throw new Error('Unexpected: Task cannot be resolved.'); - } - - const resolvedTask = await handler.provider.resolveTask(task, CancellationToken.None); - if (!resolvedTask) { - return; - } - - const resolvedTaskDTO: TaskDTO | undefined = TaskDTO.from(resolvedTask, handler.extension); - if (!resolvedTaskDTO) { - throw new Error('Unexpected: Task cannot be resolved.'); - } - - if (resolvedTask.definition !== task.definition) { - throw new Error('Unexpected: The resolved task definition must be the same object as the original task definition. The task definition cannot be changed.'); - } - - if (CustomExecution2DTO.is(resolvedTaskDTO.execution)) { - await this.addCustomExecution2(resolvedTaskDTO, resolvedTask); - } - - return resolvedTaskDTO;*/ - return undefined; + protected async resolveTaskInternal(resolvedTaskDTO: tasks.TaskDTO): Promise { + return resolvedTaskDTO; } public async $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }> { @@ -666,53 +141,7 @@ export class ExtHostTask implements ExtHostTaskShape { return this._terminalService.$requestDefaultShellAndArgs(true); } - private nextHandle(): number { - return this._handleCounter++; - } - - private async addCustomExecution2(taskDTO: TaskDTO, task: vscode.Task2): Promise { - const taskId = await this._proxy.$createTaskId(taskDTO); - this._providedCustomExecutions2.set(taskId, (task).execution2); - } - - private async getTaskExecution(execution: TaskExecutionDTO | string, task?: vscode.Task): Promise { - if (typeof execution === 'string') { - const taskExecution = this._taskExecutions.get(execution); - if (!taskExecution) { - throw new Error('Unexpected: The specified task is missing an execution'); - } - return taskExecution; - } - - let result: TaskExecutionImpl | undefined = this._taskExecutions.get(execution.id); - if (result) { - return result; - } - const taskToCreate = task ? task : await TaskDTO.to(execution.task, this._workspaceProvider); - if (!taskToCreate) { - throw new Error('Unexpected: Task does not exist.'); - } - const createdResult: TaskExecutionImpl = new TaskExecutionImpl(this, execution.id, taskToCreate); - this._taskExecutions.set(execution.id, createdResult); - return createdResult; - } - - private customExecutionComplete(execution: TaskExecutionDTO): void { - const extensionCallback2: vscode.CustomExecution2 | undefined = this._activeCustomExecutions2.get(execution.id); - if (extensionCallback2) { - this._activeCustomExecutions2.delete(execution.id); - } - - const lastCustomExecution = this._providedCustomExecutions2.get(execution.id); - // Technically we don't really need to do this, however, if an extension - // is executing a task through "executeTask" over and over again - // with different properties in the task definition, then this list - // could grow indefinitely, something we don't want. - this._providedCustomExecutions2.clear(); - // We do still need to hang on to the last custom execution so that the - // Rerun Task command doesn't choke when it tries to rerun a custom execution - if (lastCustomExecution) { - this._providedCustomExecutions2.set(execution.id, lastCustomExecution); - } + public async $jsonTasksSupported(): Promise { + return true; } } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 7c0ac35128..dd0e36a8de 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/common/product'; import * as os from 'os'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as platform from 'vs/base/common/platform'; @@ -181,7 +181,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { envFromConfig, this._variableResolver, isWorkspaceShellAllowed, - pkg.version, + product.version, terminalConfig.get<'auto' | 'off' | 'on'>('detectLocale', 'auto'), baseEnv ); diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index a9b1a8a358..48469cbcc3 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -27,7 +27,7 @@ import { clamp } from 'vs/base/common/numbers'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -export class InspectContextKeysAction extends Action { +class InspectContextKeysAction extends Action { static readonly ID = 'workbench.action.inspectContextKeys'; static LABEL = nls.localize('inspect context keys', "Inspect Context Keys"); @@ -91,7 +91,7 @@ export class InspectContextKeysAction extends Action { } } -export class ToggleScreencastModeAction extends Action { +class ToggleScreencastModeAction extends Action { static readonly ID = 'workbench.action.toggleScreencastMode'; static LABEL = nls.localize('toggle screencast mode', "Toggle Screencast Mode"); @@ -195,7 +195,7 @@ export class ToggleScreencastModeAction extends Action { } } -export class LogStorageAction extends Action { +class LogStorageAction extends Action { static readonly ID = 'workbench.action.logStorage'; static LABEL = nls.localize({ key: 'logStorage', comment: ['A developer only action to log the contents of the storage for the current window.'] }, "Log Storage Database Contents"); @@ -224,8 +224,6 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(InspectContextKeysActi registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleScreencastModeAction, ToggleScreencastModeAction.ID, ToggleScreencastModeAction.LABEL), 'Developer: Toggle Screencast Mode', developerCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage Database Contents', developerCategory); -// --- Menu Registration - // Screencast Mode const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ diff --git a/src/vs/workbench/browser/actions/helpActions.ts b/src/vs/workbench/browser/actions/helpActions.ts new file mode 100644 index 0000000000..7c901cfb84 --- /dev/null +++ b/src/vs/workbench/browser/actions/helpActions.ts @@ -0,0 +1,388 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Action } from 'vs/base/common/actions'; +import * as nls from 'vs/nls'; +import product from 'vs/platform/product/common/product'; +import { isMacintosh, isLinux, language } from 'vs/base/common/platform'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { IProductService } from 'vs/platform/product/common/productService'; + +class KeybindingsReferenceAction extends Action { + + static readonly ID = 'workbench.action.keybindingsReference'; + static readonly LABEL = nls.localize('keybindingsReference', "Keyboard Shortcuts Reference"); + static readonly AVAILABLE = !!(isLinux ? product.keyboardShortcutsUrlLinux : isMacintosh ? product.keyboardShortcutsUrlMac : product.keyboardShortcutsUrlWin); + + constructor( + id: string, + label: string, + @IOpenerService private readonly openerService: IOpenerService, + @IProductService private readonly productService: IProductService + ) { + super(id, label); + } + + run(): Promise { + const url = isLinux ? this.productService.keyboardShortcutsUrlLinux : isMacintosh ? this.productService.keyboardShortcutsUrlMac : this.productService.keyboardShortcutsUrlWin; + if (url) { + this.openerService.open(URI.parse(url)); + } + + return Promise.resolve(); + } +} + +class OpenDocumentationUrlAction extends Action { + + static readonly ID = 'workbench.action.openDocumentationUrl'; + static readonly LABEL = nls.localize('openDocumentationUrl', "Documentation"); + static readonly AVAILABLE = !!product.documentationUrl; + + constructor( + id: string, + label: string, + @IOpenerService private readonly openerService: IOpenerService, + @IProductService private readonly productService: IProductService + ) { + super(id, label); + } + + run(): Promise { + if (this.productService.documentationUrl) { + this.openerService.open(URI.parse(this.productService.documentationUrl)); + } + + return Promise.resolve(); + } +} + +class OpenIntroductoryVideosUrlAction extends Action { + + static readonly ID = 'workbench.action.openIntroductoryVideosUrl'; + static readonly LABEL = nls.localize('openIntroductoryVideosUrl', "Introductory Videos"); + static readonly AVAILABLE = !!product.introductoryVideosUrl; + + constructor( + id: string, + label: string, + @IOpenerService private readonly openerService: IOpenerService, + @IProductService private readonly productService: IProductService + ) { + super(id, label); + } + + run(): Promise { + if (this.productService.introductoryVideosUrl) { + this.openerService.open(URI.parse(this.productService.introductoryVideosUrl)); + } + + return Promise.resolve(); + } +} + +class OpenTipsAndTricksUrlAction extends Action { + + static readonly ID = 'workbench.action.openTipsAndTricksUrl'; + static readonly LABEL = nls.localize('openTipsAndTricksUrl', "Tips and Tricks"); + static readonly AVAILABLE = !!product.tipsAndTricksUrl; + + constructor( + id: string, + label: string, + @IOpenerService private readonly openerService: IOpenerService, + @IProductService private readonly productService: IProductService + ) { + super(id, label); + } + + run(): Promise { + if (this.productService.tipsAndTricksUrl) { + this.openerService.open(URI.parse(this.productService.tipsAndTricksUrl)); + } + + return Promise.resolve(); + } +} + +class OpenNewsletterSignupUrlAction extends Action { + + static readonly ID = 'workbench.action.openNewsletterSignupUrl'; + static readonly LABEL = nls.localize('newsletterSignup', "Signup for the VS Code Newsletter"); + static readonly AVAILABLE = !!product.newsletterSignupUrl; + + constructor( + id: string, + label: string, + @IOpenerService private readonly openerService: IOpenerService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IProductService private readonly productService: IProductService + ) { + super(id, label); + } + + async run(): Promise { + const info = await this.telemetryService.getTelemetryInfo(); + + this.openerService.open(URI.parse(`${this.productService.newsletterSignupUrl}?machineId=${encodeURIComponent(info.machineId)}`)); + } +} + +class OpenTwitterUrlAction extends Action { + + static readonly ID = 'workbench.action.openTwitterUrl'; + static readonly LABEL = nls.localize('openTwitterUrl', "Join Us on Twitter"); + static readonly AVAILABLE = !!product.twitterUrl; + + constructor( + id: string, + label: string, + @IOpenerService private readonly openerService: IOpenerService, + @IProductService private readonly productService: IProductService + ) { + super(id, label); + } + + run(): Promise { + if (this.productService.twitterUrl) { + this.openerService.open(URI.parse(this.productService.twitterUrl)); + } + + return Promise.resolve(); + } +} + +class OpenRequestFeatureUrlAction extends Action { + + static readonly ID = 'workbench.action.openRequestFeatureUrl'; + static readonly LABEL = nls.localize('openUserVoiceUrl', "Search Feature Requests"); + static readonly AVAILABLE = !!product.requestFeatureUrl; + + constructor( + id: string, + label: string, + @IOpenerService private readonly openerService: IOpenerService, + @IProductService private readonly productService: IProductService + ) { + super(id, label); + } + + run(): Promise { + if (this.productService.requestFeatureUrl) { + this.openerService.open(URI.parse(this.productService.requestFeatureUrl)); + } + + return Promise.resolve(); + } +} + +class OpenLicenseUrlAction extends Action { + + static readonly ID = 'workbench.action.openLicenseUrl'; + static readonly LABEL = nls.localize('openLicenseUrl', "View License"); + static readonly AVAILABLE = !!product.licenseUrl; + + constructor( + id: string, + label: string, + @IOpenerService private readonly openerService: IOpenerService, + @IProductService private readonly productService: IProductService + ) { + super(id, label); + } + + run(): Promise { + if (this.productService.licenseUrl) { + if (language) { + const queryArgChar = this.productService.licenseUrl.indexOf('?') > 0 ? '&' : '?'; + this.openerService.open(URI.parse(`${this.productService.licenseUrl}${queryArgChar}lang=${language}`)); + } else { + this.openerService.open(URI.parse(this.productService.licenseUrl)); + } + } + + return Promise.resolve(); + } +} + +class OpenPrivacyStatementUrlAction extends Action { + + static readonly ID = 'workbench.action.openPrivacyStatementUrl'; + static readonly LABEL = nls.localize('openPrivacyStatement', "Privacy Statement"); + static readonly AVAILABE = !!product.privacyStatementUrl; + + constructor( + id: string, + label: string, + @IOpenerService private readonly openerService: IOpenerService, + @IProductService private readonly productService: IProductService + ) { + super(id, label); + } + + run(): Promise { + if (this.productService.privacyStatementUrl) { + if (language) { + const queryArgChar = this.productService.privacyStatementUrl.indexOf('?') > 0 ? '&' : '?'; + this.openerService.open(URI.parse(`${this.productService.privacyStatementUrl}${queryArgChar}lang=${language}`)); + } else { + this.openerService.open(URI.parse(this.productService.privacyStatementUrl)); + } + } + + return Promise.resolve(); + } +} + +// --- Actions Registration + +const registry = Registry.as(Extensions.WorkbenchActions); +const helpCategory = nls.localize('help', "Help"); + +if (KeybindingsReferenceAction.AVAILABLE) { + registry.registerWorkbenchAction(new SyncActionDescriptor(KeybindingsReferenceAction, KeybindingsReferenceAction.ID, KeybindingsReferenceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_R) }), 'Help: Keyboard Shortcuts Reference', helpCategory); +} + +if (OpenDocumentationUrlAction.AVAILABLE) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDocumentationUrlAction, OpenDocumentationUrlAction.ID, OpenDocumentationUrlAction.LABEL), 'Help: Documentation', helpCategory); +} + +if (OpenIntroductoryVideosUrlAction.AVAILABLE) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenIntroductoryVideosUrlAction, OpenIntroductoryVideosUrlAction.ID, OpenIntroductoryVideosUrlAction.LABEL), 'Help: Introductory Videos', helpCategory); +} + +if (OpenTipsAndTricksUrlAction.AVAILABLE) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTipsAndTricksUrlAction, OpenTipsAndTricksUrlAction.ID, OpenTipsAndTricksUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); +} + +if (OpenNewsletterSignupUrlAction.AVAILABLE) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNewsletterSignupUrlAction, OpenNewsletterSignupUrlAction.ID, OpenNewsletterSignupUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); +} + +if (OpenTwitterUrlAction.AVAILABLE) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTwitterUrlAction, OpenTwitterUrlAction.ID, OpenTwitterUrlAction.LABEL), 'Help: Join Us on Twitter', helpCategory); +} + +if (OpenRequestFeatureUrlAction.AVAILABLE) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory); +} + +if (OpenLicenseUrlAction.AVAILABLE) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory); +} + +if (OpenPrivacyStatementUrlAction.AVAILABE) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPrivacyStatementUrlAction, OpenPrivacyStatementUrlAction.ID, OpenPrivacyStatementUrlAction.LABEL), 'Help: Privacy Statement', helpCategory); +} + +// --- Menu Registration + +// Help + +if (OpenDocumentationUrlAction.AVAILABLE) { + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '1_welcome', + command: { + id: OpenDocumentationUrlAction.ID, + title: nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation") + }, + order: 3 + }); +} +/* // {{SQL CARBON EDIT}} - Disable unused menu item +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '1_welcome', + command: { + id: 'update.showCurrentReleaseNotes', + title: nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes") + }, + order: 4 +}); + +// Reference +if (KeybindingsReferenceAction.AVAILABLE) { + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '2_reference', + command: { + id: KeybindingsReferenceAction.ID, + title: nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference") + }, + order: 1 + }); +} + +if (OpenIntroductoryVideosUrlAction.AVAILABLE) { + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '2_reference', + command: { + id: OpenIntroductoryVideosUrlAction.ID, + title: nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos") + }, + order: 2 + }); +} + +if (OpenTipsAndTricksUrlAction.AVAILABLE) { + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '2_reference', + command: { + id: OpenTipsAndTricksUrlAction.ID, + title: nls.localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "Tips and Tri&&cks") + }, + order: 3 + }); +} + +// Feedback +if (OpenTwitterUrlAction.AVAILABLE) { + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '3_feedback', + command: { + id: OpenTwitterUrlAction.ID, + title: nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join Us on Twitter") + }, + order: 1 + }); +} + +if (OpenRequestFeatureUrlAction.AVAILABLE) { + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '3_feedback', + command: { + id: OpenRequestFeatureUrlAction.ID, + title: nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests") + }, + order: 2 + }); +}*/ + +// Legal +if (OpenLicenseUrlAction.AVAILABLE) { + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '4_legal', + command: { + id: OpenLicenseUrlAction.ID, + title: nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License") + }, + order: 1 + }); +} + +if (OpenPrivacyStatementUrlAction.AVAILABE) { + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '4_legal', + command: { + id: OpenPrivacyStatementUrlAction.ID, + title: nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "Privac&&y Statement") + }, + order: 2 + }); +} diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 014d6e8a8a..dc75450968 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -12,7 +12,6 @@ import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/ import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; @@ -142,15 +141,6 @@ export class ToggleEditorLayoutAction extends Action { } } -CommandsRegistry.registerCommand('_workbench.editor.setGroupOrientation', function (accessor: ServicesAccessor, args: [GroupOrientation]) { - const editorGroupService = accessor.get(IEditorGroupsService); - const [orientation] = args; - - editorGroupService.setGroupOrientation(orientation); - - return Promise.resolve(); -}); - const group = viewCategory; registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_0 } }), 'View: Toggle Vertical/Horizontal Editor Layout', group); diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 283d570fe7..1b38d90ee7 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -7,7 +7,8 @@ import 'vs/css!./media/actions'; import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { IWindowService, IURIToOpen, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -162,7 +163,7 @@ export class OpenRecentAction extends BaseOpenRecentAction { } } -export class QuickOpenRecentAction extends BaseOpenRecentAction { +class QuickOpenRecentAction extends BaseOpenRecentAction { static readonly ID = 'workbench.action.quickOpenRecent'; static readonly LABEL = nls.localize('quickOpenRecent', "Quick Open Recent..."); @@ -186,7 +187,7 @@ export class QuickOpenRecentAction extends BaseOpenRecentAction { } } -export class ToggleFullScreenAction extends Action { +class ToggleFullScreenAction extends Action { static readonly ID = 'workbench.action.toggleFullScreen'; static LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen"); @@ -226,7 +227,7 @@ export class ReloadWindowAction extends Action { } } -export class ShowAboutDialogAction extends Action { +class ShowAboutDialogAction extends Action { static readonly ID = 'workbench.action.showAboutDialog'; static readonly LABEL = nls.localize('about', "About"); @@ -234,13 +235,13 @@ export class ShowAboutDialogAction extends Action { constructor( id: string, label: string, - @IWindowsService private readonly windowsService: IWindowsService + @IDialogService private readonly dialogService: IDialogService ) { super(id, label); } run(): Promise { - return this.windowsService.openAboutDialog(); + return this.dialogService.about(); } } diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 7cefca23a2..1c6e1f1840 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -5,24 +5,18 @@ import { Action } from 'vs/base/common/actions'; import * as nls from 'vs/nls'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ICommandService, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { Schemas } from 'vs/base/common/network'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; -import { toResource } from 'vs/workbench/common/editor'; -import { URI } from 'vs/base/common/uri'; -import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { WorkbenchStateContext } from 'vs/workbench/browser/contextkeys'; +import { MenuRegistry, MenuId, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { WorkbenchStateContext, SupportsWorkspacesContext } from 'vs/workbench/browser/contextkeys'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; export class OpenFileAction extends Action { @@ -42,36 +36,6 @@ export class OpenFileAction extends Action { } } -export namespace OpenLocalFileCommand { - export const ID = 'workbench.action.files.openLocalFile'; - export const LABEL = nls.localize('openLocalFile', "Open Local File..."); - - export function handler(): ICommandHandler { - return accessor => { - const dialogService = accessor.get(IFileDialogService); - return dialogService.pickFileAndOpen({ forceNewWindow: false, availableFileSystems: [Schemas.file] }); - }; - } -} - -export namespace SaveLocalFileCommand { - export const ID = 'workbench.action.files.saveLocalFile'; - export const LABEL = nls.localize('saveLocalFile', "Save Local File..."); - - export function handler(): ICommandHandler { - return accessor => { - const textFileService = accessor.get(ITextFileService); - const editorService = accessor.get(IEditorService); - let resource: URI | undefined = toResource(editorService.activeEditor); - const options: ISaveOptions = { force: true, availableFileSystems: [Schemas.file] }; - if (resource) { - return textFileService.saveAs(resource, undefined, options); - } - return Promise.resolve(undefined); - }; - } -} - export class OpenFolderAction extends Action { static readonly ID = 'workbench.action.files.openFolder'; @@ -90,19 +54,6 @@ export class OpenFolderAction extends Action { } } -export namespace OpenLocalFolderCommand { - export const ID = 'workbench.action.files.openLocalFolder'; - export const LABEL = nls.localize('openLocalFolder', "Open Local Folder..."); - - export function handler(): ICommandHandler { - return accessor => { - const dialogService = accessor.get(IFileDialogService); - return dialogService.pickFolderAndOpen({ forceNewWindow: false, availableFileSystems: [Schemas.file] }); - }; - } -} - - export class OpenFileFolderAction extends Action { static readonly ID = 'workbench.action.files.openFileFolder'; @@ -121,16 +72,46 @@ export class OpenFileFolderAction extends Action { } } -export namespace OpenLocalFileFolderCommand { +export class OpenWorkspaceAction extends Action { - export const ID = 'workbench.action.files.openLocalFileFolder'; - export const LABEL = nls.localize('openLocalFileFolder', "Open Local..."); + static readonly ID = 'workbench.action.openWorkspace'; + static LABEL = nls.localize('openWorkspaceAction', "Open Workspace..."); - export function handler(): ICommandHandler { - return accessor => { - const dialogService = accessor.get(IFileDialogService); - return dialogService.pickFileFolderAndOpen({ forceNewWindow: false, availableFileSystems: [Schemas.file] }); - }; + constructor( + id: string, + label: string, + @IFileDialogService private readonly dialogService: IFileDialogService + ) { + super(id, label); + } + + run(event?: any, data?: ITelemetryData): Promise { + return this.dialogService.pickWorkspaceAndOpen({ telemetryExtraData: data }); + } +} + +export class OpenWorkspaceConfigFileAction extends Action { + + static readonly ID = 'workbench.action.openWorkspaceConfigFile'; + static readonly LABEL = nls.localize('openWorkspaceConfigFile', "Open Workspace Configuration File"); + + constructor( + id: string, + label: string, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IEditorService private readonly editorService: IEditorService + ) { + super(id, label); + + this.enabled = !!this.workspaceContextService.getWorkspace().configuration; + } + + run(): Promise { + const configuration = this.workspaceContextService.getWorkspace().configuration; + if (configuration) { + return this.editorService.openEditor({ resource: configuration }); + } + return Promise.resolve(); } } @@ -182,141 +163,30 @@ export class GlobalRemoveRootFolderAction extends Action { } } -export class SaveWorkspaceAsAction extends Action { +// --- Actions Registration - static readonly ID = 'workbench.action.saveWorkspaceAs'; - static LABEL = nls.localize('saveWorkspaceAsAction', "Save Workspace As..."); +const registry = Registry.as(Extensions.WorkbenchActions); +const workspacesCategory = nls.localize('workspaces', "Workspaces"); - constructor( - id: string, - label: string, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService - - ) { - super(id, label); - } - - async run(): Promise { - const configPathUri = await this.workspaceEditingService.pickNewWorkspacePath(); - if (configPathUri) { - switch (this.contextService.getWorkbenchState()) { - case WorkbenchState.EMPTY: - case WorkbenchState.FOLDER: - const folders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri })); - return this.workspaceEditingService.createAndEnterWorkspace(folders, configPathUri); - case WorkbenchState.WORKSPACE: - return this.workspaceEditingService.saveAndEnterWorkspace(configPathUri); - } - } - } -} - -export class OpenWorkspaceAction extends Action { - - static readonly ID = 'workbench.action.openWorkspace'; - static LABEL = nls.localize('openWorkspaceAction', "Open Workspace..."); - - constructor( - id: string, - label: string, - @IFileDialogService private readonly dialogService: IFileDialogService - ) { - super(id, label); - } - - run(event?: any, data?: ITelemetryData): Promise { - return this.dialogService.pickWorkspaceAndOpen({ telemetryExtraData: data }); - } -} - -export class CloseWorkspaceAction extends Action { - - static readonly ID = 'workbench.action.closeFolder'; - static LABEL = nls.localize('closeWorkspace', "Close Workspace"); - - constructor( - id: string, - label: string, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @INotificationService private readonly notificationService: INotificationService, - @IWindowService private readonly windowService: IWindowService - ) { - super(id, label); - } - - run(): Promise { - if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { - this.notificationService.info(nls.localize('noWorkspaceOpened', "There is currently no workspace opened in this instance to close.")); - - return Promise.resolve(undefined); - } - - return this.windowService.closeWorkspace(); - } -} - -export class OpenWorkspaceConfigFileAction extends Action { - - static readonly ID = 'workbench.action.openWorkspaceConfigFile'; - static readonly LABEL = nls.localize('openWorkspaceConfigFile', "Open Workspace Configuration File"); - - constructor( - id: string, - label: string, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IEditorService private readonly editorService: IEditorService - ) { - super(id, label); - - this.enabled = !!this.workspaceContextService.getWorkspace().configuration; - } - - run(): Promise { - const configuration = this.workspaceContextService.getWorkspace().configuration; - if (configuration) { - return this.editorService.openEditor({ resource: configuration }); - } - return Promise.resolve(); - } -} - -export class DuplicateWorkspaceInNewWindowAction extends Action { - - static readonly ID = 'workbench.action.duplicateWorkspaceInNewWindow'; - static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate Workspace in New Window"); - - constructor( - id: string, - label: string, - @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, - @IWindowService private readonly windowService: IWindowService, - @IWorkspacesService private readonly workspacesService: IWorkspacesService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService - ) { - super(id, label); - } - - async run(): Promise { - const folders = this.workspaceContextService.getWorkspace().folders; - const remoteAuthority = this.environmentService.configuration.remoteAuthority; - - const newWorkspace = await this.workspacesService.createUntitledWorkspace(folders, remoteAuthority); - await this.workspaceEditingService.copyWorkspaceSettings(newWorkspace); - - return this.windowService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true }); - } -} +registry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory, SupportsWorkspacesContext); +registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory, SupportsWorkspacesContext); // --- Menu Registration -const workspacesCategory = nls.localize('workspaces', "Workspaces"); - CommandsRegistry.registerCommand(OpenWorkspaceConfigFileAction.ID, serviceAccessor => { serviceAccessor.get(IInstantiationService).createInstance(OpenWorkspaceConfigFileAction, OpenWorkspaceConfigFileAction.ID, OpenWorkspaceConfigFileAction.LABEL).run(); }); +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '3_workspace', + command: { + id: ADD_ROOT_FOLDER_COMMAND_ID, + title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") + }, + order: 1, + when: SupportsWorkspacesContext +}); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: OpenWorkspaceConfigFileAction.ID, diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index b84d724bd6..1498390c80 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -141,8 +141,10 @@ export class WorkbenchContextKeysHandler extends Disposable { // Development IsDevelopmentContext.bindTo(this.contextKeyService).set(!this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment); - // File Pickers - SupportsWorkspacesContext.bindTo(this.contextKeyService); + // Workspaces Support + // - web: only if already in workspace state + // - desktop: always + SupportsWorkspacesContext.bindTo(this.contextKeyService).set(isWeb ? this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE : true); // Editors this.activeEditorContext = ActiveEditorContext.bindTo(this.contextKeyService); diff --git a/src/vs/workbench/browser/legacyLayout.ts b/src/vs/workbench/browser/legacyLayout.ts index 3239b25572..56df927d4b 100644 --- a/src/vs/workbench/browser/legacyLayout.ts +++ b/src/vs/workbench/browser/legacyLayout.ts @@ -43,20 +43,20 @@ export class WorkbenchLegacyLayout extends Disposable implements IVerticalSashLa private static readonly sashYHeightSettingsKey = 'workbench.panel.height'; private static readonly panelSizeBeforeMaximizedKey = 'workbench.panel.sizeBeforeMaximized'; - private workbenchSize: Dimension; + private workbenchSize!: Dimension; private sashXOne: Sash; private sashXTwo: Sash; private sashY: Sash; - private _sidebarWidth: number; - private sidebarHeight: number; - private titlebarHeight: number; - private statusbarHeight: number; - private panelSizeBeforeMaximized: number; - private panelMaximized: boolean; - private _panelHeight: number; - private _panelWidth: number; + private _sidebarWidth!: number; + private sidebarHeight!: number; + private titlebarHeight!: number; + private statusbarHeight!: number; + private panelSizeBeforeMaximized!: number; + private panelMaximized!: boolean; + private _panelHeight!: number; + private _panelWidth!: number; constructor( private parent: HTMLElement, @@ -413,7 +413,7 @@ export class WorkbenchLegacyLayout extends Disposable implements IVerticalSashLa this.titlebarHeight = isTitlebarHidden ? 0 : this.partLayoutInfo.titlebar.height / (isMacintosh || !menubarVisibility || menubarVisibility === 'hidden' ? getZoomFactor() : 1); // adjust for zoom prevention this.sidebarHeight = this.workbenchSize.height - this.statusbarHeight - this.titlebarHeight; - let sidebarSize = new Dimension(this.sidebarWidth, this.sidebarHeight); + let sidebarSize = { width: this.sidebarWidth, height: this.sidebarHeight }; // Activity Bar let activityBarSize = new Dimension(this.activitybarWidth, sidebarSize.height); @@ -463,7 +463,7 @@ export class WorkbenchLegacyLayout extends Disposable implements IVerticalSashLa this.storageService.store(WorkbenchLegacyLayout.panelSizeBeforeMaximizedKey, this.panelSizeBeforeMaximized, StorageScope.GLOBAL); - const panelDimension = new Dimension(panelWidth, panelHeight); + const panelDimension = { width: panelWidth, height: panelHeight }; // Editor let editorSize = { diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 258c439c10..b7bb971a62 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -14,7 +14,7 @@ .windows { font-family: "Segoe WPC", "Segoe UI", sans-serif; } .windows:lang(zh-Hans) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; } .windows:lang(zh-Hant) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; } -.windows:lang(ja) { font-family: "Segoe WPC", "Segoe UI", "Meiryo", sans-serif; } +.windows:lang(ja) { font-family: "Segoe WPC", "Segoe UI", "Yu Gothic UI", "Meiryo UI", sans-serif; } .windows:lang(ko) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; } .linux { font-family: "Ubuntu", "Droid Sans", sans-serif; } @@ -23,7 +23,7 @@ .linux:lang(ja) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; } .linux:lang(ko) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } -.mac { --monaco-monospace-font: Monaco, Menlo, Inconsolata, "Courier New", monospace; } +.mac { --monaco-monospace-font: "SF Mono", Monaco, Menlo, Inconsolata, "Courier New", monospace; } .windows { --monaco-monospace-font: Consolas, Inconsolata, "Courier New", monospace; } .linux { --monaco-monospace-font: "Droid Sans Mono", Inconsolata, "Courier New", monospace, "Droid Sans Fallback"; } diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index acf79e48df..fc7c9078fa 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -156,13 +156,14 @@ class PartLayout { titleSize = new Dimension(0, 0); } - // Content Size: Width (Fill), Height (Variable) - const contentSize = new Dimension(width, height - titleSize.height); - + let contentWidth = width; if (this.options && typeof this.options.borderWidth === 'function') { - contentSize.width -= this.options.borderWidth(); // adjust for border size + contentWidth -= this.options.borderWidth(); // adjust for border size } + // Content Size: Width (Fill), Height (Variable) + const contentSize = new Dimension(contentWidth, height - titleSize.height); + // Content if (this.contentArea) { size(this.contentArea, contentSize.width, contentSize.height); diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-down-dark.svg b/src/vs/workbench/browser/parts/panel/media/chevron-down-dark.svg deleted file mode 100644 index a1df6a8d44..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-down-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-down-hc.svg b/src/vs/workbench/browser/parts/panel/media/chevron-down-hc.svg deleted file mode 100644 index 4f2ec14692..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-down-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-down-light.svg b/src/vs/workbench/browser/parts/panel/media/chevron-down-light.svg deleted file mode 100644 index e60e357f57..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-down-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-left-dark.svg b/src/vs/workbench/browser/parts/panel/media/chevron-left-dark.svg deleted file mode 100644 index dbc37784a7..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-left-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-left-hc.svg b/src/vs/workbench/browser/parts/panel/media/chevron-left-hc.svg deleted file mode 100644 index 3767c20051..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-left-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-left-light.svg b/src/vs/workbench/browser/parts/panel/media/chevron-left-light.svg deleted file mode 100644 index 2bd4c96832..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-left-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-right-dark.svg b/src/vs/workbench/browser/parts/panel/media/chevron-right-dark.svg deleted file mode 100644 index f518fc1632..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-right-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-right-hc.svg b/src/vs/workbench/browser/parts/panel/media/chevron-right-hc.svg deleted file mode 100644 index 40ba72b708..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-right-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-right-light.svg b/src/vs/workbench/browser/parts/panel/media/chevron-right-light.svg deleted file mode 100644 index 0d746558a4..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-right-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-up-dark.svg b/src/vs/workbench/browser/parts/panel/media/chevron-up-dark.svg deleted file mode 100644 index 5b9da8932e..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-up-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-up-hc.svg b/src/vs/workbench/browser/parts/panel/media/chevron-up-hc.svg deleted file mode 100644 index 13bad53736..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-up-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-up-light.svg b/src/vs/workbench/browser/parts/panel/media/chevron-up-light.svg deleted file mode 100644 index 5498cda5bc..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-up-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/close-dark.svg b/src/vs/workbench/browser/parts/panel/media/close-dark.svg deleted file mode 100644 index e0475f7b85..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/close-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/close-hc.svg b/src/vs/workbench/browser/parts/panel/media/close-hc.svg deleted file mode 100644 index 64618b6176..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/close-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/close-light.svg b/src/vs/workbench/browser/parts/panel/media/close-light.svg deleted file mode 100644 index 3bd4467469..0000000000 --- a/src/vs/workbench/browser/parts/panel/media/close-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 3538f3ba12..67f564ba2a 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -123,69 +123,3 @@ min-width: 110px; margin-right: 10px; } - -/* Close */ -.monaco-workbench .hide-panel-action { - background: url('close-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .hide-panel-action { - background: url('close-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .hide-panel-action { - background: url('close-hc.svg') center center no-repeat; -} - - -/* Up */ -.monaco-workbench .maximize-panel-action { - background-image: url('chevron-up-light.svg'); -} - -.vs-dark .monaco-workbench .maximize-panel-action { - background-image: url('chevron-up-dark.svg'); -} - -.hc-black .monaco-workbench .maximize-panel-action { - background-image: url('chevron-up-hc.svg'); -} - -/* Down */ -.monaco-workbench .minimize-panel-action { - background-image: url('chevron-down-light.svg'); -} - -.vs-dark .monaco-workbench .minimize-panel-action { - background-image: url('chevron-down-dark.svg'); -} - -.hc-black .monaco-workbench .minimize-panel-action { - background-image: url('chevron-down-hc.svg'); -} - -/* Left */ -.monaco-workbench .panel.right .maximize-panel-action { - background-image: url('chevron-left-light.svg'); -} - -.vs-dark .monaco-workbench .panel.right .maximize-panel-action { - background-image: url('chevron-left-dark.svg'); -} - -.hc-black .monaco-workbench .panel.right .maximize-panel-action { - background-image: url('chevron-left-hc.svg'); -} - -/* Right */ -.monaco-workbench .panel.right .minimize-panel-action { - background-image: url('chevron-right-light.svg'); -} - -.vs-dark .monaco-workbench .panel.right .minimize-panel-action { - background-image: url('chevron-right-dark.svg'); -} - -.hc-black .monaco-workbench .panel.right .minimize-panel-action { - background-image: url('chevron-right-hc.svg'); -} diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 0b385e41f2..1c8fc26b2b 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -28,7 +28,7 @@ export class ClosePanelAction extends Action { name: string, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { - super(id, name, 'hide-panel-action'); + super(id, name, 'codicon-close'); } run(): Promise { @@ -141,11 +141,11 @@ export class ToggleMaximizedPanelAction extends Action { @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IEditorGroupsService editorGroupsService: IEditorGroupsService ) { - super(id, label, layoutService.isPanelMaximized() ? 'minimize-panel-action' : 'maximize-panel-action'); + super(id, label, layoutService.isPanelMaximized() ? 'codicon-chevron-down' : 'codicon-chevron-up'); this.toDispose.add(editorGroupsService.onDidLayout(() => { const maximized = this.layoutService.isPanelMaximized(); - this.class = maximized ? 'minimize-panel-action' : 'maximize-panel-action'; + this.class = maximized ? 'codicon-chevron-down' : 'codicon-chevron-up'; this.label = maximized ? ToggleMaximizedPanelAction.RESTORE_LABEL : ToggleMaximizedPanelAction.MAXIMIZE_LABEL; })); } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 5d1783a8e8..ce82f73376 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -4,18 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/titlebarpart'; -import { dirname, posix } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { Part } from 'vs/workbench/browser/part'; import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService'; import { getZoomFactor } from 'vs/base/browser/browser'; -import { IWindowService, IWindowsService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { IWindowService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IAction, Action } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -29,7 +28,7 @@ import { trim } from 'vs/base/common/strings'; import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { template, getBaseLabel } from 'vs/base/common/labels'; +import { template } from 'vs/base/common/labels'; import { ILabelService } from 'vs/platform/label/common/label'; import { Event, Emitter } from 'vs/base/common/event'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -37,6 +36,9 @@ import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/bro import { RunOnceScheduler } from 'vs/base/common/async'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Schemas } from 'vs/base/common/network'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export class TitlebarPart extends Part implements ITitleService { @@ -71,7 +73,6 @@ export class TitlebarPart extends Part implements ITitleService { private lastLayoutDimensions: Dimension; private pendingTitle: string; - private representedFileName: string; private isInactive: boolean; @@ -80,11 +81,12 @@ export class TitlebarPart extends Part implements ITitleService { private titleUpdater: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); + private contextMenu: IMenu; + constructor( @IContextMenuService private readonly contextMenuService: IContextMenuService, @IWindowService private readonly windowService: IWindowService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IWindowsService private readonly windowsService: IWindowsService, @IEditorService private readonly editorService: IEditorService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -92,10 +94,14 @@ export class TitlebarPart extends Part implements ITitleService { @IThemeService themeService: IThemeService, @ILabelService private readonly labelService: ILabelService, @IStorageService storageService: IStorageService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); + this.contextMenu = this._register(menuService.createMenu(MenuId.TitleBarContext, contextKeyService)); + this.registerListeners(); } @@ -181,9 +187,6 @@ export class TitlebarPart extends Part implements ITitleService { // Apply to window this.windowService.setRepresentedFilename(path); - - // Keep for context menu - this.representedFileName = path; } private doUpdateTitle(): void { @@ -254,6 +257,7 @@ export class TitlebarPart extends Part implements ITitleService { * {folderName}: e.g. myFolder * {folderPath}: e.g. /Users/Development/myFolder * {appName}: e.g. VS Code + * {remoteName}: e.g. SSH * {dirty}: indicator * {separator}: conditional separator */ @@ -294,6 +298,7 @@ export class TitlebarPart extends Part implements ITitleService { const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : ''; const dirty = editor && editor.isDirty() ? TitlebarPart.TITLE_DIRTY : ''; const appName = this.environmentService.appNameLong; + const remoteName = this.environmentService.configuration.remoteAuthority; const separator = TitlebarPart.TITLE_SEPARATOR; const titleTemplate = this.configurationService.getValue('window.title'); @@ -310,6 +315,7 @@ export class TitlebarPart extends Part implements ITitleService { folderPath, dirty, appName, + remoteName, separator: { label: separator } }); } @@ -376,7 +382,6 @@ export class TitlebarPart extends Part implements ITitleService { if (!isMacintosh && !isWeb) { this.windowControls = append(this.element, $('div.window-controls-container')); - // Minimize const minimizeIconContainer = append(this.windowControls, $('div.window-icon-bg')); const minimizeIcon = append(minimizeIconContainer, $('div.window-icon')); @@ -503,44 +508,16 @@ export class TitlebarPart extends Part implements ITitleService { const event = new StandardMouseEvent(e); const anchor = { x: event.posx, y: event.posy }; - // Show menu - const actions = this.getContextMenuActions(); - if (actions.length) { - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, - getActions: () => actions, - onHide: () => actions.forEach(a => a.dispose()) - }); - } - } - - private getContextMenuActions(): IAction[] { + // Fill in contributed actions const actions: IAction[] = []; + const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, undefined, actions, this.contextMenuService); - if (this.representedFileName) { - const segments = this.representedFileName.split(posix.sep); - for (let i = segments.length; i > 0; i--) { - const isFile = (i === segments.length); - - let pathOffset = i; - if (!isFile) { - pathOffset++; // for segments which are not the file name we want to open the folder - } - - const path = segments.slice(0, pathOffset).join(posix.sep); - - let label: string; - if (!isFile) { - label = getBaseLabel(dirname(path)); - } else { - label = getBaseLabel(path); - } - - actions.push(new ShowItemInFolderAction(path, label || posix.sep, this.windowsService)); - } - } - - return actions; + // Show it + this.contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => actions, + onHide: () => dispose(actionsDisposable) + }); } private adjustTitleMarginToCenter(): void { @@ -605,17 +582,6 @@ export class TitlebarPart extends Part implements ITitleService { } } -class ShowItemInFolderAction extends Action { - - constructor(private path: string, label: string, private windowsService: IWindowsService) { - super('showItemInFolder.action.id', label); - } - - run(): Promise { - return this.windowsService.showItemInFolder(URI.file(this.path)); - } -} - registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (titlebarActiveFg) { diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index b95a18365c..b716a9afac 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -768,6 +768,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle }; templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false }); diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index a7e694a04b..20ca2d89cf 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -6,11 +6,17 @@ import 'vs/css!./media/style'; import { registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; -import { foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; +import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + // Icon defaults + const iconForegroundColor = theme.getColor(iconForeground); + if (iconForegroundColor) { + collector.addRule(`.monaco-workbench .codicon { color: ${iconForegroundColor}; }`); + } + // Foreground const windowForeground = theme.getColor(foreground); if (windowForeground) { @@ -136,4 +142,5 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } `); } + }); diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 13c9d55d50..7e608ee6d7 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -13,8 +13,8 @@ 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'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IProductService } from 'vs/platform/product/common/product'; -import product from 'vs/platform/product/browser/product'; +import { IProductService } from 'vs/platform/product/common/productService'; +import product from 'vs/platform/product/common/product'; import { RemoteAgentService } from 'vs/workbench/services/remote/browser/remoteAgentServiceImpl'; import { RemoteAuthorityResolverService } from 'vs/platform/remote/browser/remoteAuthorityResolverService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; @@ -39,7 +39,7 @@ import { joinPath } from 'vs/base/common/resources'; import { BrowserStorageService } from 'vs/platform/storage/browser/storageService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { getThemeTypeSelector, DARK, HIGH_CONTRAST, LIGHT } from 'vs/platform/theme/common/themeService'; -import { InMemoryUserDataProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider'; +import { InMemoryFileSystemProvider } from 'vs/workbench/services/userData/common/inMemoryUserDataProvider'; import { registerWindowDriver } from 'vs/platform/driver/browser/driver'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; import { FileLogService } from 'vs/platform/log/common/fileLogService'; @@ -75,6 +75,11 @@ class CodeRendererMain extends Disposable { // Layout this._register(addDisposableListener(window, EventType.RESIZE, () => workbench.layout())); + // Prevent the back/forward gestures in macOS + this._register(addDisposableListener(this.domElement, EventType.WHEEL, (e) => { + e.preventDefault(); + }, { passive: false })); + // Workbench Lifecycle this._register(workbench.onBeforeShutdown(event => { if (services.storageService.hasPendingUpdate) { @@ -222,7 +227,7 @@ class CodeRendererMain extends Disposable { // User data if (!this.configuration.userDataProvider) { - this.configuration.userDataProvider = this._register(new InMemoryUserDataProvider()); + this.configuration.userDataProvider = this._register(new InMemoryFileSystemProvider()); } fileService.registerProvider(Schemas.userData, this.configuration.userDataProvider); } @@ -261,12 +266,12 @@ class CodeRendererMain extends Disposable { // Multi-root workspace if (this.configuration.workspaceUri) { - return { id: hash(URI.revive(this.configuration.workspaceUri).toString()).toString(16), configPath: URI.revive(this.configuration.workspaceUri) }; + return { id: hash(this.configuration.workspaceUri.toString()).toString(16), configPath: this.configuration.workspaceUri }; } // Single-folder workspace if (this.configuration.folderUri) { - return { id: hash(URI.revive(this.configuration.folderUri).toString()).toString(16), folder: URI.revive(this.configuration.folderUri) }; + return { id: hash(this.configuration.folderUri.toString()).toString(16), folder: this.configuration.folderUri }; } return { id: 'empty-window' }; diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index bbb8eebb02..3a88612de4 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -11,7 +11,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IURIToOpen, IMessageBoxResult, IWindowsService, IOpenSettings, IWindowSettings } from 'vs/platform/windows/common/windows'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IRecentlyOpened, IRecent, isRecentFile, isRecentFolder } from 'vs/platform/history/common/history'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -23,11 +23,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/common/historyStorage'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IProductService } from 'vs/platform/product/common/product'; -import Severity from 'vs/base/common/severity'; -import { localize } from 'vs/nls'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; //#region Window @@ -246,7 +241,7 @@ export class SimpleWindowService extends Disposable implements IWindowService { for (let i = 0; i < _uris.length; i++) { const uri = _uris[i]; if ('folderUri' in uri) { - const newAddress = `${document.location.origin}/?folder=${uri.folderUri.path}`; + const newAddress = `${document.location.origin}${document.location.pathname}?folder=${uri.folderUri.path}`; if (openFolderInNewWindow) { window.open(newAddress); } else { @@ -254,7 +249,7 @@ export class SimpleWindowService extends Disposable implements IWindowService { } } if ('workspaceUri' in uri) { - const newAddress = `${document.location.origin}/?workspace=${uri.workspaceUri.path}`; + const newAddress = `${document.location.origin}${document.location.pathname}?workspace=${uri.workspaceUri.path}`; if (openFolderInNewWindow) { window.open(newAddress); } else { @@ -323,8 +318,6 @@ registerSingleton(IWindowService, SimpleWindowService); export class SimpleWindowsService implements IWindowsService { _serviceBrand: undefined; - windowCount = 1; - readonly onWindowOpen: Event = Event.None; readonly onWindowFocus: Event = Event.None; readonly onWindowBlur: Event = Event.None; @@ -332,13 +325,6 @@ export class SimpleWindowsService implements IWindowsService { readonly onWindowUnmaximize: Event = Event.None; readonly onRecentlyOpenedChange: Event = Event.None; - constructor( - @IDialogService private readonly dialogService: IDialogService, - @IProductService private readonly productService: IProductService, - @IClipboardService private readonly clipboardService: IClipboardService - ) { - } - isFocused(_windowId: number): Promise { return Promise.resolve(true); } @@ -466,49 +452,6 @@ export class SimpleWindowsService implements IWindowsService { } openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { - - // we pass the "ParsedArgs" as query parameters of the URL - - let newAddress = `${document.location.origin}/?`; - let gotFolder = false; - - const addQueryParameter = (key: string, value: string) => { - const lastChar = newAddress.charAt(newAddress.length - 1); - if (lastChar !== '?' && lastChar !== '&') { - newAddress += '&'; - } - newAddress += `${key}=${encodeURIComponent(value)}`; - }; - - const f = args['folder-uri']; - if (f) { - const u = URI.parse(f[0]); - gotFolder = true; - addQueryParameter('folder', u.path); - } - if (!gotFolder) { - // request empty window - addQueryParameter('ew', 'true'); - } - - const ep = args['extensionDevelopmentPath']; - if (ep) { - let u = ep[0]; - addQueryParameter('edp', u); - } - - const di = args['debugId']; - if (di) { - addQueryParameter('di', di); - } - - const ibe = args['inspect-brk-extensions']; - if (ibe) { - addQueryParameter('ibe', ibe); - } - - window.open(newAddress); - return Promise.resolve(); } @@ -516,18 +459,6 @@ export class SimpleWindowsService implements IWindowsService { return Promise.resolve([]); } - getWindowCount(): Promise { - return Promise.resolve(this.windowCount); - } - - log(_severity: string, _args: string[]): Promise { - return Promise.resolve(); - } - - showItemInFolder(_path: URI): Promise { - return Promise.resolve(); - } - newWindowTab(): Promise { return Promise.resolve(); } @@ -585,22 +516,6 @@ export class SimpleWindowsService implements IWindowsService { throw new Error('not implemented'); } - async openAboutDialog(): Promise { - const detail = localize('aboutDetail', - "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", - this.productService.version || 'Unknown', - this.productService.commit || 'Unknown', - this.productService.date || 'Unknown', - navigator.userAgent - ); - - const { choice } = await this.dialogService.show(Severity.Info, this.productService.nameLong, [localize('copy', "Copy"), localize('ok', "OK")], { detail }); - - if (choice === 0) { - this.clipboardService.writeText(detail); - } - } - resolveProxy(windowId: number, url: string): Promise { return Promise.resolve(undefined); } @@ -609,28 +524,3 @@ export class SimpleWindowsService implements IWindowsService { registerSingleton(IWindowsService, SimpleWindowsService); //#endregion - -//#region Workspaces - -export class SimpleWorkspacesService implements IWorkspacesService { - - _serviceBrand: undefined; - - createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } - - deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise { - return Promise.resolve(undefined); - } - - getWorkspaceIdentifier(workspacePath: URI): Promise { - // @ts-ignore - return Promise.resolve(undefined); - } -} - -registerSingleton(IWorkspacesService, SimpleWorkspacesService); - -//#endregion diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 68492759aa..423fa2dcf3 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -6,7 +6,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as nls from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'; +import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform'; // Configuration (function registerConfiguration(): void { @@ -263,6 +263,7 @@ import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform' nls.localize('rootName', "`\${rootName}`: name of the workspace (e.g. myFolder or myWorkspace)."), nls.localize('rootPath', "`\${rootPath}`: file path of the workspace (e.g. /Users/Development/myWorkspace)."), nls.localize('appName', "`\${appName}`: e.g. VS Code."), + nls.localize('remoteName', "`\${remoteName}`: e.g. SSH"), nls.localize('dirty', "`\${dirty}`: a dirty indicator if the active editor is dirty."), nls.localize('separator', "`\${separator}`: a conditional separator (\" - \") that only shows when surrounded by variables with values or static text.") ].join('\n- '); // intentionally concatenated to not produce a string that is too long for translations @@ -275,7 +276,18 @@ import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform' 'properties': { 'window.title': { 'type': 'string', - 'default': isMacintosh ? '${activeEditorShort}${separator}${rootName}' : '${dirty}${activeEditorShort}${separator}${rootName}${separator}${appName}', + 'default': (() => { + if (isMacintosh && isNative) { + return '${activeEditorShort}${separator}${rootName}'; // macOS has native dirty indicator + } + + const base = '${dirty}${activeEditorShort}${separator}${rootName}${separator}${appName}'; + if (isWeb) { + return base + '${separator}${remoteName}'; // Web: always show remote indicator + } + + return base; + })(), 'markdownDescription': windowTitleDescription }, 'window.menuBarVisibility': { diff --git a/src/vs/workbench/contrib/cli/node/cli.contribution.ts b/src/vs/workbench/contrib/cli/node/cli.contribution.ts index 9fa727e74c..cf2e492a58 100644 --- a/src/vs/workbench/contrib/cli/node/cli.contribution.ts +++ b/src/vs/workbench/contrib/cli/node/cli.contribution.ts @@ -14,7 +14,7 @@ import { Action } from 'vs/base/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 182b391c00..9fb3a1bb99 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -4,19 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import { memoize } from 'vs/base/common/decorators'; +import { Emitter } from 'vs/base/common/event'; import { UnownedDisposable } from 'vs/base/common/lifecycle'; import { basename } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; +import { WebviewEditorState } from 'vs/editor/common/modes'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IEditorInput, Verbosity } from 'vs/workbench/common/editor'; +import { ConfirmResult, IEditorInput, Verbosity } from 'vs/workbench/common/editor'; import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; -import { WebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; +import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { IWebviewEditorService } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { WebviewEditorState } from 'vs/editor/common/modes'; +import { promptSave } from 'vs/workbench/services/textfile/browser/textFileService'; -export class CustomFileEditorInput extends WebviewEditorInput { +export class CustomFileEditorInput extends WebviewInput { public static typeId = 'workbench.editors.webviewEditor'; @@ -30,12 +33,10 @@ export class CustomFileEditorInput extends WebviewEditorInput { viewType: string, id: string, webview: UnownedDisposable, - @ILabelService - private readonly labelService: ILabelService, - @IWebviewEditorService - private readonly _webviewEditorService: IWebviewEditorService, - @IExtensionService - private readonly _extensionService: IExtensionService + @ILabelService private readonly labelService: ILabelService, + @IWebviewEditorService private readonly _webviewEditorService: IWebviewEditorService, + @IExtensionService private readonly _extensionService: IExtensionService, + @IDialogService private readonly dialogService: IDialogService, ) { super(id, viewType, '', undefined, webview); this._editorResource = resource; @@ -77,7 +78,7 @@ export class CustomFileEditorInput extends WebviewEditorInput { return this.labelService.getUriLabel(this.getResource()); } - getTitle(verbosity?: Verbosity): string { + public getTitle(verbosity?: Verbosity): string { switch (verbosity) { case Verbosity.SHORT: return this.shortTitle; @@ -106,4 +107,26 @@ export class CustomFileEditorInput extends WebviewEditorInput { public isDirty() { return this._state === WebviewEditorState.Dirty; } + + public async confirmSave(): Promise { + if (!this.isDirty()) { + return ConfirmResult.DONT_SAVE; + } + return promptSave(this.dialogService, [this.getResource()]); + } + + public async save(): Promise { + if (!this.isDirty) { + return true; + } + const waitingOn: Promise[] = []; + this._onWillSave.fire({ + waitUntil: (thenable: Promise): void => { waitingOn.push(thenable); }, + }); + const result = await Promise.all(waitingOn); + return result.every(x => x); + } + + private readonly _onWillSave = this._register(new Emitter<{ waitUntil: (thenable: Thenable) => void }>()); + public readonly onWillSave = this._onWillSave.event; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 4f25896b21..9882920c37 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -6,7 +6,8 @@ import { coalesce, distinct } from 'vs/base/common/arrays'; import * as glob from 'vs/base/common/glob'; import { UnownedDisposable } from 'vs/base/common/lifecycle'; -import { basename } from 'vs/base/common/resources'; +import { Schemas } from 'vs/base/common/network'; +import { basename, DataUri } from 'vs/base/common/resources'; import { withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; @@ -16,7 +17,8 @@ import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/ed import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor'; +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint'; import { CustomEditorDiscretion, CustomEditorInfo, CustomEditorSelector, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; @@ -25,10 +27,48 @@ import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsSe import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { CustomFileEditorInput } from './customEditorInput'; +const defaultEditorId = 'default'; + +const defaultEditorInfo: CustomEditorInfo = { + id: defaultEditorId, + displayName: nls.localize('promptOpenWith.defaultEditor', "Default built-in editor"), + selector: [ + { filenamePattern: '*' } + ], + discretion: CustomEditorDiscretion.default, +}; + +export class CustomEditorStore { + private readonly contributedEditors = new Map(); + + public clear() { + this.contributedEditors.clear(); + } + + public get(viewType: string): CustomEditorInfo | undefined { + return viewType === defaultEditorId + ? defaultEditorInfo + : this.contributedEditors.get(viewType); + } + + public add(info: CustomEditorInfo): void { + if (info.id === defaultEditorId || this.contributedEditors.has(info.id)) { + console.log(`Custom editor with id '${info.id}' already registered`); + return; + } + this.contributedEditors.set(info.id, info); + } + + public getContributedEditors(resource: URI): readonly CustomEditorInfo[] { + return Array.from(this.contributedEditors.values()).filter(customEditor => + customEditor.selector.some(selector => matches(selector, resource))); + } +} + export class CustomEditorService implements ICustomEditorService { _serviceBrand: any; - private readonly customEditors = new Map(); + private readonly editors = new CustomEditorStore(); constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @@ -38,9 +78,11 @@ export class CustomEditorService implements ICustomEditorService { @IWebviewService private readonly webviewService: IWebviewService, ) { webviewEditorsExtensionPoint.setHandler(extensions => { + this.editors.clear(); + for (const extension of extensions) { for (const webviewEditorContribution of extension.value) { - this.customEditors.set(webviewEditorContribution.viewType, { + this.editors.add({ id: webviewEditorContribution.viewType, displayName: webviewEditorContribution.displayName, selector: webviewEditorContribution.selector || [], @@ -52,15 +94,14 @@ export class CustomEditorService implements ICustomEditorService { } public getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[] { - return Array.from(this.customEditors.values()).filter(customEditor => - customEditor.selector.some(selector => matches(selector, resource))); + return this.editors.getContributedEditors(resource); } public getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[] { const rawAssociations = this.configurationService.getValue(customEditorsAssociationsKey) || []; return coalesce(rawAssociations .filter(association => matches(association, resource)) - .map(association => this.customEditors.get(association.viewType))); + .map(association => this.editors.get(association.viewType))); } public async promptOpenWith( @@ -69,34 +110,23 @@ export class CustomEditorService implements ICustomEditorService { group?: IEditorGroup, ): Promise { const customEditors = distinct([ + defaultEditorInfo, ...this.getUserConfiguredCustomEditors(resource), ...this.getContributedCustomEditors(resource), ], editor => editor.id); - const defaultEditorId = 'default'; - const pick = await this.quickInputService.pick([ - { - label: nls.localize('promptOpenWith.defaultEditor', "Default built-in editor"), - id: defaultEditorId, - }, - ...customEditors.map((editorDescriptor): IQuickPickItem => ({ + const pick = await this.quickInputService.pick( + customEditors.map((editorDescriptor): IQuickPickItem => ({ label: editorDescriptor.displayName, id: editorDescriptor.id, - })) - ], { + })), { placeHolder: nls.localize('promptOpenWith.placeHolder', "Select editor to use for '{0}'...", basename(resource)), }); - if (!pick) { + if (!pick || !pick.id) { return undefined; // {{SQL CARBON EDIT}} strict-null-check } - - if (pick.id === defaultEditorId) { - const fileInput = this.instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); - return this.openEditorForResource(resource, fileInput, { ...options, ignoreOverrides: true }, group); - } else { - return this.openWith(resource, pick.id!, options, group); - } + return this.openWith(resource, pick.id, options, group); } public openWith( @@ -105,17 +135,31 @@ export class CustomEditorService implements ICustomEditorService { options?: ITextEditorOptions, group?: IEditorGroup, ): Promise { - if (!this.customEditors.has(viewType)) { + if (viewType === defaultEditorId) { + const fileInput = this.instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); + return this.openEditorForResource(resource, fileInput, { ...options, ignoreOverrides: true }, group); + } + + if (!this.editors.get(viewType)) { return this.promptOpenWith(resource, options, group); } + const input = this.createInput(resource, viewType, group); + return this.openEditorForResource(resource, input, options, group); + } + + public createInput( + resource: URI, + viewType: string, + group: IEditorGroup | undefined + ): CustomFileEditorInput { const id = generateUuid(); const webview = this.webviewService.createWebviewEditorOverlay(id, {}, {}); const input = this.instantiationService.createInstance(CustomFileEditorInput, resource, viewType, id, new UnownedDisposable(webview)); if (group) { input.updateGroup(group!.id); } - return this.openEditorForResource(resource, input, options, group); + return input; } private async openEditorForResource( @@ -159,6 +203,42 @@ export class CustomEditorContribution implements IWorkbenchContribution { return undefined; // {{SQL CARBON EDIT}} strict-null-check } + if (editor instanceof DiffEditorInput) { + const getCustomEditorOverrideForSubInput = (subInput: IEditorInput): EditorInput | undefined => { + if (subInput instanceof CustomFileEditorInput) { + return undefined; // {{SQL CARBON EDIT}} strict-null-check + } + const resource = subInput.getResource(); + if (!resource) { + return undefined; // {{SQL CARBON EDIT}} strict-null-check + } + + const editors = distinct([ + ...this.customEditorService.getUserConfiguredCustomEditors(resource), + ...this.customEditorService.getContributedCustomEditors(resource), + ], editor => editor.id); + + // Always prefer the first editor in the diff editor case + return editors.length + ? this.customEditorService.createInput(resource, editors[0].id, group) + : undefined; + }; + + const modifiedOverride = getCustomEditorOverrideForSubInput(editor.modifiedInput); + const originalOverride = getCustomEditorOverrideForSubInput(editor.originalInput); + + if (modifiedOverride || originalOverride) { + return { + override: (async () => { + const input = new DiffEditorInput(editor.getName(), editor.getDescription(), originalOverride || editor.originalInput, modifiedOverride || editor.modifiedInput); + return this.editorService.openEditor(input, { ...options, ignoreOverrides: true }, group); + })(), + }; + } + + return undefined; + } + const resource = editor.getResource(); if (!resource) { return undefined; // {{SQL CARBON EDIT}} strict-null-check @@ -214,6 +294,15 @@ export class CustomEditorContribution implements IWorkbenchContribution { } function matches(selector: CustomEditorSelector, resource: URI): boolean { + if (resource.scheme === Schemas.data) { + const metadata = DataUri.parseMetaData(resource); + const mime = metadata.get(DataUri.META_DATA_MIME); + if (!selector.mime || !mime) { + return false; + } + return glob.match(selector.mime, mime.toLowerCase()); + } + if (!selector.filenamePattern && !selector.scheme) { return false; } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 9298b62789..b827fb333d 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -6,10 +6,9 @@ import { URI } from 'vs/base/common/uri'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEditor } from 'vs/workbench/common/editor'; +import { EditorInput, IEditor } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; - export const ICustomEditorService = createDecorator('customEditorService'); export interface ICustomEditorService { @@ -18,6 +17,8 @@ export interface ICustomEditorService { getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[]; getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[]; + createInput(resource: URI, viewType: string, group: IEditorGroup | undefined): EditorInput; + openWith(resource: URI, customEditorViewType: string, options?: ITextEditorOptions, group?: IEditorGroup): Promise; promptOpenWith(resource: URI, options?: ITextEditorOptions, group?: IEditorGroup): Promise; } @@ -30,6 +31,7 @@ export const enum CustomEditorDiscretion { export interface CustomEditorSelector { readonly scheme?: string; readonly filenamePattern?: string; + readonly mime?: string; } export interface CustomEditorInfo { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts new file mode 100644 index 0000000000..ffe6edfd96 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -0,0 +1,567 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as env from 'vs/base/common/platform'; +import * as dom from 'vs/base/browser/dom'; +import { URI as uri } from 'vs/base/common/uri'; +import severity from 'vs/base/common/severity'; +import { IAction, Action } from 'vs/base/common/actions'; +import { Range } from 'vs/editor/common/core/range'; +import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IContentWidget, IActiveCodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { RemoveBreakpointAction } from 'vs/workbench/contrib/debug/browser/debugActions'; +import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, BreakpointWidgetContext, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution, IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug'; +import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; +import { ContextSubMenu } from 'vs/base/browser/contextmenu'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { BreakpointWidget } from 'vs/workbench/contrib/debug/browser/breakpointWidget'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { getBreakpointMessageAndClassName } from 'vs/workbench/contrib/debug/browser/breakpointsView'; +import { generateUuid } from 'vs/base/common/uuid'; +import { memoize } from 'vs/base/common/decorators'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { distinct } from 'vs/base/common/arrays'; +import { RunOnceScheduler } from 'vs/base/common/async'; + +const $ = dom.$; + +interface IBreakpointDecoration { + decorationId: string; + breakpoint: IBreakpoint; + range: Range; + inlineWidget?: InlineBreakpointWidget; +} + +const breakpointHelperDecoration: IModelDecorationOptions = { + glyphMarginClassName: 'debug-breakpoint-hint', + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges +}; + +function createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArray, debugService: IDebugService): { range: Range; options: IModelDecorationOptions; }[] { + const result: { range: Range; options: IModelDecorationOptions; }[] = []; + breakpoints.forEach((breakpoint) => { + if (breakpoint.lineNumber <= model.getLineCount()) { + const column = model.getLineFirstNonWhitespaceColumn(breakpoint.lineNumber); + const range = model.validateRange( + breakpoint.column ? new Range(breakpoint.lineNumber, breakpoint.column, breakpoint.lineNumber, breakpoint.column + 1) + : new Range(breakpoint.lineNumber, column, breakpoint.lineNumber, column + 1) // Decoration has to have a width #20688 + ); + + result.push({ + options: getBreakpointDecorationOptions(model, breakpoint, debugService), + range + }); + } + }); + + return result; +} + +function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoint, debugService: IDebugService): IModelDecorationOptions { + const { className, message } = getBreakpointMessageAndClassName(debugService, breakpoint); + let glyphMarginHoverMessage: MarkdownString | undefined; + + if (message) { + if (breakpoint.condition || breakpoint.hitCondition) { + const modeId = model.getLanguageIdentifier().language; + glyphMarginHoverMessage = new MarkdownString().appendCodeblock(modeId, message); + } else { + glyphMarginHoverMessage = new MarkdownString().appendText(message); + } + } + + return { + glyphMarginClassName: className, + glyphMarginHoverMessage, + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + beforeContentClassName: breakpoint.column ? `debug-breakpoint-placeholder` : undefined + }; +} + +async function createCandidateDecorations(model: ITextModel, breakpointDecorations: IBreakpointDecoration[], debugService: IDebugService): Promise<{ range: Range; options: IModelDecorationOptions; breakpoint: IBreakpoint | undefined }[]> { + const lineNumbers = distinct(breakpointDecorations.map(bpd => bpd.range.startLineNumber)); + const result: { range: Range; options: IModelDecorationOptions; breakpoint: IBreakpoint | undefined }[] = []; + const session = debugService.getViewModel().focusedSession; + if (session && session.capabilities.supportsBreakpointLocationsRequest) { + await Promise.all(lineNumbers.map(async lineNumber => { + const positions = await session.breakpointsLocations(model.uri, lineNumber); + if (positions.length > 1) { + // Do not render candidates if there is only one, since it is already covered by the line breakpoint + positions.forEach(p => { + const range = new Range(p.lineNumber, p.column, p.lineNumber, p.column + 1); + const breakpointAtPosition = breakpointDecorations.filter(bpd => bpd.range.equalsRange(range)).pop(); + if (breakpointAtPosition && breakpointAtPosition.inlineWidget) { + // Space already occupied, do not render candidate. + return; + } + result.push({ + range, + options: { + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + beforeContentClassName: `debug-breakpoint-placeholder` + }, + breakpoint: breakpointAtPosition ? breakpointAtPosition.breakpoint : undefined + }); + }); + } + })); + } + + return result; +} + + +class BreakpointEditorContribution implements IBreakpointEditorContribution { + + private breakpointHintDecoration: string[] = []; + private breakpointWidget: BreakpointWidget | undefined; + private breakpointWidgetVisible: IContextKey; + private toDispose: IDisposable[] = []; + private ignoreDecorationsChangedEvent = false; + private ignoreBreakpointsChangeEvent = false; + private breakpointDecorations: IBreakpointDecoration[] = []; + private candidateDecorations: { decorationId: string, inlineWidget: InlineBreakpointWidget }[] = []; + private setDecorationsScheduler: RunOnceScheduler; + + constructor( + private readonly editor: ICodeEditor, + @IDebugService private readonly debugService: IDebugService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IDialogService private readonly dialogService: IDialogService, + ) { + this.breakpointWidgetVisible = CONTEXT_BREAKPOINT_WIDGET_VISIBLE.bindTo(contextKeyService); + this.registerListeners(); + this.setDecorationsScheduler = new RunOnceScheduler(() => this.setDecorations(), 30); + } + + getId(): string { + return BREAKPOINT_EDITOR_CONTRIBUTION_ID; + } + + private registerListeners(): void { + this.toDispose.push(this.editor.onMouseDown(async (e: IEditorMouseEvent) => { + const data = e.target.detail as IMarginData; + const model = this.editor.getModel(); + if (!e.target.position || !model || e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || data.isAfterLines || !this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { + return; + } + const canSetBreakpoints = this.debugService.getConfigurationManager().canSetBreakpointsIn(model); + const lineNumber = e.target.position.lineNumber; + const uri = model.uri; + + if (e.event.rightButton || (env.isMacintosh && e.event.leftButton && e.event.ctrlKey)) { + if (!canSetBreakpoints) { + return; + } + + const anchor = { x: e.event.posx, y: e.event.posy }; + const breakpoints = this.debugService.getModel().getBreakpoints({ lineNumber, uri }); + + this.contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => this.getContextMenuActions(breakpoints, uri, lineNumber), + getActionsContext: () => breakpoints.length ? breakpoints[0] : undefined + }); + } else { + const breakpoints = this.debugService.getModel().getBreakpoints({ uri, lineNumber }); + + if (breakpoints.length) { + // Show the dialog if there is a potential condition to be accidently lost. + // Do not show dialog on linux due to electron issue freezing the mouse #50026 + if (!env.isLinux && breakpoints.some(bp => !!bp.condition || !!bp.logMessage || !!bp.hitCondition)) { + const logPoint = breakpoints.every(bp => !!bp.logMessage); + const breakpointType = logPoint ? nls.localize('logPoint', "Logpoint") : nls.localize('breakpoint', "Breakpoint"); + const disable = breakpoints.some(bp => bp.enabled); + + const enabling = nls.localize('breakpointHasConditionDisabled', + "This {0} has a {1} that will get lost on remove. Consider enabling the {0} instead.", + breakpointType.toLowerCase(), + logPoint ? nls.localize('message', "message") : nls.localize('condition', "condition") + ); + const disabling = nls.localize('breakpointHasConditionEnabled', + "This {0} has a {1} that will get lost on remove. Consider disabling the {0} instead.", + breakpointType.toLowerCase(), + logPoint ? nls.localize('message', "message") : nls.localize('condition', "condition") + ); + + const { choice } = await this.dialogService.show(severity.Info, disable ? disabling : enabling, [ + nls.localize('removeLogPoint', "Remove {0}", breakpointType), + nls.localize('disableLogPoint', "{0} {1}", disable ? nls.localize('disable', "Disable") : nls.localize('enable', "Enable"), breakpointType), + nls.localize('cancel', "Cancel") + ], { cancelId: 2 }); + + if (choice === 0) { + breakpoints.forEach(bp => this.debugService.removeBreakpoints(bp.getId())); + } + if (choice === 1) { + breakpoints.forEach(bp => this.debugService.enableOrDisableBreakpoints(!disable, bp)); + } + } else { + breakpoints.forEach(bp => this.debugService.removeBreakpoints(bp.getId())); + } + } else if (canSetBreakpoints) { + this.debugService.addBreakpoints(uri, [{ lineNumber }], `debugEditorGutter`); + } + } + })); + + this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => { + let showBreakpointHintAtLineNumber = -1; + const model = this.editor.getModel(); + if (model && e.target.position && e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) && + this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { + const data = e.target.detail as IMarginData; + if (!data.isAfterLines) { + showBreakpointHintAtLineNumber = e.target.position.lineNumber; + } + } + this.ensureBreakpointHintDecoration(showBreakpointHintAtLineNumber); + })); + this.toDispose.push(this.editor.onMouseLeave((e: IEditorMouseEvent) => { + this.ensureBreakpointHintDecoration(-1); + })); + + this.toDispose.push(this.editor.onDidChangeModel(async () => { + this.closeBreakpointWidget(); + await this.setDecorations(); + })); + this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(async () => { + if (!this.ignoreBreakpointsChangeEvent && !this.setDecorationsScheduler.isScheduled()) { + this.setDecorationsScheduler.schedule(); + } + })); + this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.onModelDecorationsChanged())); + } + + private getContextMenuActions(breakpoints: ReadonlyArray, uri: uri, lineNumber: number, column?: number): Array { + const actions: Array = []; + if (breakpoints.length === 1) { + const breakpointType = breakpoints[0].logMessage ? nls.localize('logPoint', "Logpoint") : nls.localize('breakpoint', "Breakpoint"); + actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, nls.localize('removeBreakpoint', "Remove {0}", breakpointType), this.debugService)); + actions.push(new Action( + 'workbench.debug.action.editBreakpointAction', + nls.localize('editBreakpoint', "Edit {0}...", breakpointType), + undefined, + true, + () => Promise.resolve(this.showBreakpointWidget(breakpoints[0].lineNumber, breakpoints[0].column)) + )); + + actions.push(new Action( + `workbench.debug.viewlet.action.toggleBreakpoint`, + breakpoints[0].enabled ? nls.localize('disableBreakpoint', "Disable {0}", breakpointType) : nls.localize('enableBreakpoint', "Enable {0}", breakpointType), + undefined, + true, + () => this.debugService.enableOrDisableBreakpoints(!breakpoints[0].enabled, breakpoints[0]) + )); + } else if (breakpoints.length > 1) { + const sorted = breakpoints.slice().sort((first, second) => (first.column && second.column) ? first.column - second.column : 1); + actions.push(new ContextSubMenu(nls.localize('removeBreakpoints', "Remove Breakpoints"), sorted.map(bp => new Action( + 'removeInlineBreakpoint', + bp.column ? nls.localize('removeInlineBreakpointOnColumn', "Remove Inline Breakpoint on Column {0}", bp.column) : nls.localize('removeLineBreakpoint', "Remove Line Breakpoint"), + undefined, + true, + () => this.debugService.removeBreakpoints(bp.getId()) + )))); + + actions.push(new ContextSubMenu(nls.localize('editBreakpoints', "Edit Breakpoints"), sorted.map(bp => + new Action('editBreakpoint', + bp.column ? nls.localize('editInlineBreakpointOnColumn', "Edit Inline Breakpoint on Column {0}", bp.column) : nls.localize('editLineBrekapoint', "Edit Line Breakpoint"), + undefined, + true, + () => Promise.resolve(this.showBreakpointWidget(bp.lineNumber, bp.column)) + ) + ))); + + actions.push(new ContextSubMenu(nls.localize('enableDisableBreakpoints', "Enable/Disable Breakpoints"), sorted.map(bp => new Action( + bp.enabled ? 'disableColumnBreakpoint' : 'enableColumnBreakpoint', + bp.enabled ? (bp.column ? nls.localize('disableInlineColumnBreakpoint', "Disable Inline Breakpoint on Column {0}", bp.column) : nls.localize('disableBreakpointOnLine', "Disable Line Breakpoint")) + : (bp.column ? nls.localize('enableBreakpoints', "Enable Inline Breakpoint on Column {0}", bp.column) : nls.localize('enableBreakpointOnLine', "Enable Line Breakpoint")), + undefined, + true, + () => this.debugService.enableOrDisableBreakpoints(!bp.enabled, bp) + )))); + } else { + actions.push(new Action( + 'addBreakpoint', + nls.localize('addBreakpoint', "Add Breakpoint"), + undefined, + true, + () => this.debugService.addBreakpoints(uri, [{ lineNumber, column }], `debugEditorContextMenu`) + )); + actions.push(new Action( + 'addConditionalBreakpoint', + nls.localize('addConditionalBreakpoint', "Add Conditional Breakpoint..."), + undefined, + true, + () => Promise.resolve(this.showBreakpointWidget(lineNumber, column)) + )); + actions.push(new Action( + 'addLogPoint', + nls.localize('addLogPoint', "Add Logpoint..."), + undefined, + true, + () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.LOG_MESSAGE)) + )); + } + + return actions; + } + + private marginFreeFromNonDebugDecorations(line: number): boolean { + const decorations = this.editor.getLineDecorations(line); + if (decorations) { + for (const { options } of decorations) { + if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf('debug') === -1) { + return false; + } + } + } + + return true; + } + + private ensureBreakpointHintDecoration(showBreakpointHintAtLineNumber: number): void { + const newDecoration: IModelDeltaDecoration[] = []; + if (showBreakpointHintAtLineNumber !== -1) { + newDecoration.push({ + options: breakpointHelperDecoration, + range: { + startLineNumber: showBreakpointHintAtLineNumber, + startColumn: 1, + endLineNumber: showBreakpointHintAtLineNumber, + endColumn: 1 + } + }); + } + + this.breakpointHintDecoration = this.editor.deltaDecorations(this.breakpointHintDecoration, newDecoration); + } + + private async setDecorations(): Promise { + if (!this.editor.hasModel()) { + return; + } + + const activeCodeEditor = this.editor; + const model = activeCodeEditor.getModel(); + const breakpoints = this.debugService.getModel().getBreakpoints({ uri: model.uri }); + const desiredBreakpointDecorations = createBreakpointDecorations(model, breakpoints, this.debugService); + + try { + this.ignoreDecorationsChangedEvent = true; + + // Set breakpoint decorations + const decorationIds = activeCodeEditor.deltaDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId), desiredBreakpointDecorations); + this.breakpointDecorations.forEach(bpd => { + if (bpd.inlineWidget) { + bpd.inlineWidget.dispose(); + } + }); + this.breakpointDecorations = decorationIds.map((decorationId, index) => { + let inlineWidget: InlineBreakpointWidget | undefined = undefined; + const breakpoint = breakpoints[index]; + if (breakpoint.column) { + inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, desiredBreakpointDecorations[index].options.glyphMarginClassName, breakpoint, this.debugService, this.contextMenuService, () => this.getContextMenuActions([breakpoint], activeCodeEditor.getModel().uri, breakpoint.lineNumber, breakpoint.column)); + } + + return { + decorationId, + breakpoint, + range: desiredBreakpointDecorations[index].range, + inlineWidget + }; + }); + + } finally { + this.ignoreDecorationsChangedEvent = false; + } + + // Set breakpoint candidate decorations + const desiredCandidateDecorations = await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, this.debugService); + const candidateDecorationIds = this.editor.deltaDecorations(this.candidateDecorations.map(c => c.decorationId), desiredCandidateDecorations); + this.candidateDecorations.forEach(candidate => { + candidate.inlineWidget.dispose(); + }); + this.candidateDecorations = candidateDecorationIds.map((decorationId, index) => { + const candidate = desiredCandidateDecorations[index]; + const cssClass = candidate.breakpoint ? undefined : 'debug-breakpoint-disabled'; + const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, cssClass, candidate.breakpoint, this.debugService, this.contextMenuService, () => this.getContextMenuActions([], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn)); + + return { + decorationId, + inlineWidget + }; + }); + } + + private async onModelDecorationsChanged(): Promise { + if (this.breakpointDecorations.length === 0 || this.ignoreDecorationsChangedEvent || !this.editor.hasModel()) { + // I have no decorations + return; + } + let somethingChanged = false; + const model = this.editor.getModel(); + this.breakpointDecorations.forEach(breakpointDecoration => { + if (somethingChanged) { + return; + } + const newBreakpointRange = model.getDecorationRange(breakpointDecoration.decorationId); + if (newBreakpointRange && (!breakpointDecoration.range.equalsRange(newBreakpointRange))) { + somethingChanged = true; + } + }); + if (!somethingChanged) { + // nothing to do, my decorations did not change. + return; + } + + const data = new Map(); + for (let i = 0, len = this.breakpointDecorations.length; i < len; i++) { + const breakpointDecoration = this.breakpointDecorations[i]; + const decorationRange = model.getDecorationRange(breakpointDecoration.decorationId); + // check if the line got deleted. + if (decorationRange) { + // since we know it is collapsed, it cannot grow to multiple lines + if (breakpointDecoration.breakpoint) { + data.set(breakpointDecoration.breakpoint.getId(), { + lineNumber: decorationRange.startLineNumber, + column: breakpointDecoration.breakpoint.column ? decorationRange.startColumn : undefined, + }); + } + } + } + + try { + this.ignoreBreakpointsChangeEvent = true; + await this.debugService.updateBreakpoints(model.uri, data, true); + } finally { + this.ignoreBreakpointsChangeEvent = false; + } + } + + // breakpoint widget + showBreakpointWidget(lineNumber: number, column: number | undefined, context?: BreakpointWidgetContext): void { + if (this.breakpointWidget) { + this.breakpointWidget.dispose(); + } + + this.breakpointWidget = this.instantiationService.createInstance(BreakpointWidget, this.editor, lineNumber, column, context); + this.breakpointWidget.show({ lineNumber, column: 1 }); + this.breakpointWidgetVisible.set(true); + } + + closeBreakpointWidget(): void { + if (this.breakpointWidget) { + this.breakpointWidget.dispose(); + this.breakpointWidget = undefined; + this.breakpointWidgetVisible.reset(); + this.editor.focus(); + } + } + + dispose(): void { + if (this.breakpointWidget) { + this.breakpointWidget.dispose(); + } + this.editor.deltaDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId), []); + dispose(this.toDispose); + } +} + +class InlineBreakpointWidget implements IContentWidget, IDisposable { + + // editor.IContentWidget.allowEditorOverflow + allowEditorOverflow = false; + suppressMouseDown = true; + + private domNode!: HTMLElement; + private range: Range | null; + private toDispose: IDisposable[] = []; + + constructor( + private readonly editor: IActiveCodeEditor, + private readonly decorationId: string, + cssClass: string | null | undefined, + private readonly breakpoint: IBreakpoint | undefined, + private readonly debugService: IDebugService, + private readonly contextMenuService: IContextMenuService, + private readonly getContextMenuActions: () => ReadonlyArray + ) { + this.range = this.editor.getModel().getDecorationRange(decorationId); + this.toDispose.push(this.editor.onDidChangeModelDecorations(() => { + const model = this.editor.getModel(); + const range = model.getDecorationRange(this.decorationId); + if (this.range && !this.range.equalsRange(range)) { + this.range = range; + this.editor.layoutContentWidget(this); + } + })); + this.create(cssClass); + + this.editor.addContentWidget(this); + this.editor.layoutContentWidget(this); + } + + private create(cssClass: string | null | undefined): void { + this.domNode = $('.inline-breakpoint-widget'); + if (cssClass) { + this.domNode.classList.add(cssClass); + } + this.toDispose.push(dom.addDisposableListener(this.domNode, dom.EventType.CLICK, async e => { + if (this.breakpoint) { + await this.debugService.removeBreakpoints(this.breakpoint.getId()); + } else { + await this.debugService.addBreakpoints(this.editor.getModel().uri, [{ lineNumber: this.range!.startLineNumber, column: this.range!.startColumn }], 'debugEditorInlineWidget'); + } + })); + this.toDispose.push(dom.addDisposableListener(this.domNode, dom.EventType.CONTEXT_MENU, async e => { + const event = new StandardMouseEvent(e); + const anchor = { x: event.posx, y: event.posy }; + + this.contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => this.getContextMenuActions(), + getActionsContext: () => this.breakpoint + }); + })); + } + + @memoize + getId(): string { + return generateUuid(); + } + + getDomNode(): HTMLElement { + return this.domNode; + } + + getPosition(): IContentWidgetPosition | null { + if (!this.range) { + return null; + } + // Workaround: since the content widget can not be placed before the first column we need to force the left position + dom.toggleClass(this.domNode, 'line-start', this.range.startColumn === 1); + + return { + position: { lineNumber: this.range.startLineNumber, column: this.range.startColumn - 1 }, + preference: [ContentWidgetPositionPreference.EXACT] + }; + } + + dispose(): void { + this.editor.removeContentWidget(this); + dispose(this.toDispose); + } +} + +registerEditorContribution(BreakpointEditorContribution); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 651d9dde79..b79c78c460 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -13,7 +13,7 @@ import { Position, IPosition } from 'vs/editor/common/core/position'; import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, DEBUG_SCHEME, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, CONTEXT_IN_BREAKPOINT_WIDGET, IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, DEBUG_SCHEME, CONTEXT_IN_BREAKPOINT_WIDGET, IBreakpointUpdateData, IBreakpointEditorContribution, BREAKPOINT_EDITOR_CONTRIBUTION_ID } from 'vs/workbench/contrib/debug/common/debug'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -37,7 +37,7 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { onUnexpectedError } from 'vs/base/common/errors'; const $ = dom.$; -const IPrivateBreakpointWidgetService = createDecorator('privateBreakopintWidgetService'); +const IPrivateBreakpointWidgetService = createDecorator('privateBreakpointWidgetService'); export interface IPrivateBreakpointWidgetService { _serviceBrand: undefined; close(success: boolean): void; @@ -55,7 +55,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private logMessageInput = ''; private breakpoint: IBreakpoint | undefined; - constructor(editor: ICodeEditor, private lineNumber: number, private context: Context, + constructor(editor: ICodeEditor, private lineNumber: number, private column: number | undefined, private context: Context, @IContextViewService private readonly contextViewService: IContextViewService, @IDebugService private readonly debugService: IDebugService, @IThemeService private readonly themeService: IThemeService, @@ -70,7 +70,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi const model = this.editor.getModel(); if (model) { const uri = model.uri; - const breakpoints = this.debugService.getModel().getBreakpoints({ lineNumber: this.lineNumber, uri }); + const breakpoints = this.debugService.getModel().getBreakpoints({ lineNumber: this.lineNumber, column: this.column, uri }); this.breakpoint = breakpoints.length ? breakpoints[0] : undefined; } @@ -130,12 +130,12 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } } - show(rangeOrPos: IRange | IPosition, heightInLines: number) { + show(rangeOrPos: IRange | IPosition): void { const lineNum = this.input.getModel().getLineCount(); super.show(rangeOrPos, lineNum + 1); } - fitHeightToContent() { + fitHeightToContent(): void { const lineNum = this.input.getModel().getLineCount(); this._relayout(lineNum + 1); } @@ -293,6 +293,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi if (model) { this.debugService.addBreakpoints(model.uri, [{ lineNumber: this.lineNumber, + column: this.column, enabled: true, condition, hitCondition, @@ -348,7 +349,7 @@ class CloseBreakpointWidgetCommand extends EditorCommand { } runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { - const debugContribution = editor.getContribution(EDITOR_CONTRIBUTION_ID); + const debugContribution = editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID); if (debugContribution) { // if focus is in outer editor we need to use the debug contribution to close return debugContribution.closeBreakpointWidget(); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 1eca288468..67b08c6236 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import * as resources from 'vs/base/common/resources'; import * as dom from 'vs/base/browser/dom'; import { IAction, Action } from 'vs/base/common/actions'; -import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, EDITOR_CONTRIBUTION_ID, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, IDebugEditorContribution } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution } from 'vs/workbench/contrib/debug/common/debug'; import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAllBreakpointsAction, RemoveBreakpointAction, EnableAllBreakpointsAction, DisableAllBreakpointsAction, ReapplyBreakpointsAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -167,7 +167,7 @@ export class BreakpointsView extends ViewletPanel { if (editor) { const codeEditor = editor.getControl(); if (isCodeEditor(codeEditor)) { - codeEditor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(element.lineNumber, element.column); + codeEditor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(element.lineNumber, element.column); } } }); @@ -180,7 +180,7 @@ export class BreakpointsView extends ViewletPanel { actions.push(new Separator()); } - actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, nls.localize('removeBreakpoint', "Remove {0}", breakpointType), this.debugService, this.keybindingService)); + actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, nls.localize('removeBreakpoint', "Remove {0}", breakpointType), this.debugService)); if (this.debugService.getModel().getBreakpoints().length + this.debugService.getModel().getFunctionBreakpoints().length > 1) { actions.push(new RemoveAllBreakpointsAction(RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL, this.debugService, this.keybindingService)); diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index ba455c72fa..a8337ee6a7 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -24,7 +24,6 @@ import { } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { DebugEditorModelManager } from 'vs/workbench/contrib/debug/browser/debugEditorModelManager'; import { StartAction, AddFunctionBreakpointAction, ConfigureAction, DisableAllBreakpointsAction, EnableAllBreakpointsAction, RemoveAllBreakpointsAction, RunAction, ReapplyBreakpointsAction, SelectAndStartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import * as service from 'vs/workbench/contrib/debug/browser/debugService'; @@ -49,6 +48,7 @@ import { VariablesView } from 'vs/workbench/contrib/debug/browser/variablesView' import { ClearReplAction, Repl } from 'vs/workbench/contrib/debug/browser/repl'; import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; +import { DebugCallStackContribution } from 'vs/workbench/contrib/debug/browser/debugCallStackContribution'; class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; @@ -121,7 +121,7 @@ const registry = Registry.as(WorkbenchActionRegistryEx registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDebugPanelAction, OpenDebugPanelAction.ID, OpenDebugPanelAction.LABEL, openPanelKb), 'View: Debug Console', nls.localize('view', "View")); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDebugViewletAction, OpenDebugViewletAction.ID, OpenDebugViewletAction.LABEL, openViewletKb), 'View: Show Debug', nls.localize('view', "View")); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugEditorModelManager, LifecyclePhase.Restored); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugCallStackContribution, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugToolBar, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugContentProvider, LifecyclePhase.Eventually); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(StatusBarColorProvider, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index f9ed48bf59..b223636c23 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -181,12 +181,12 @@ export class SelectAndStartAction extends AbstractDebugAction { } } -export class RemoveBreakpointAction extends AbstractDebugAction { +export class RemoveBreakpointAction extends Action { static readonly ID = 'workbench.debug.viewlet.action.removeBreakpoint'; static LABEL = nls.localize('removeBreakpoint', "Remove Breakpoint"); - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action remove', debugService, keybindingService); + constructor(id: string, label: string, @IDebugService private readonly debugService: IDebugService) { + super(id, label, 'debug-action remove'); } public run(breakpoint: IBreakpoint): Promise { diff --git a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts new file mode 100644 index 0000000000..2d94e97335 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts @@ -0,0 +1,182 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Constants } from 'vs/editor/common/core/uint'; +import { Range } from 'vs/editor/common/core/range'; +import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/model'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IDebugService, State } from 'vs/workbench/contrib/debug/common/debug'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { localize } from 'vs/nls'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; + +interface IDebugEditorModelData { + model: ITextModel; + currentStackDecorations: string[]; + topStackFrameRange: Range | undefined; +} + +const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; + +export class DebugCallStackContribution implements IWorkbenchContribution { + private modelDataMap = new Map(); + private toDispose: IDisposable[] = []; + + constructor( + @IModelService private readonly modelService: IModelService, + @IDebugService private readonly debugService: IDebugService, + ) { + this.registerListeners(); + } + + private registerListeners(): void { + this.toDispose.push(this.modelService.onModelAdded(this.onModelAdded, this)); + this.modelService.getModels().forEach(model => this.onModelAdded(model)); + this.toDispose.push(this.modelService.onModelRemoved(this.onModelRemoved, this)); + + this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => this.onFocusStackFrame())); + this.toDispose.push(this.debugService.onDidChangeState(state => { + if (state === State.Inactive) { + this.modelDataMap.forEach(modelData => { + modelData.topStackFrameRange = undefined; + }); + } + })); + } + + private onModelAdded(model: ITextModel): void { + const modelUriStr = model.uri.toString(); + const currentStackDecorations = model.deltaDecorations([], this.createCallStackDecorations(modelUriStr)); + + this.modelDataMap.set(modelUriStr, { + model: model, + currentStackDecorations: currentStackDecorations, + topStackFrameRange: undefined + }); + } + + private onModelRemoved(model: ITextModel): void { + const modelUriStr = model.uri.toString(); + const data = this.modelDataMap.get(modelUriStr); + if (data) { + this.modelDataMap.delete(modelUriStr); + } + } + + private onFocusStackFrame(): void { + this.modelDataMap.forEach((modelData, uri) => { + modelData.currentStackDecorations = modelData.model.deltaDecorations(modelData.currentStackDecorations, this.createCallStackDecorations(uri)); + }); + } + + private createCallStackDecorations(modelUriStr: string): IModelDeltaDecoration[] { + const result: IModelDeltaDecoration[] = []; + const stackFrame = this.debugService.getViewModel().focusedStackFrame; + if (!stackFrame || stackFrame.source.uri.toString() !== modelUriStr) { + return result; + } + + // only show decorations for the currently focused thread. + const columnUntilEOLRange = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, Constants.MAX_SAFE_SMALL_INTEGER); + const range = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, stackFrame.range.startColumn + 1); + + // compute how to decorate the editor. Different decorations are used if this is a top stack frame, focused stack frame, + // an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line). + const callStack = stackFrame.thread.getCallStack(); + if (callStack && callStack.length && stackFrame === callStack[0]) { + result.push({ + options: DebugCallStackContribution.TOP_STACK_FRAME_MARGIN, + range + }); + + result.push({ + options: DebugCallStackContribution.TOP_STACK_FRAME_DECORATION, + range: columnUntilEOLRange + }); + + const modelData = this.modelDataMap.get(modelUriStr); + if (modelData) { + if (modelData.topStackFrameRange && modelData.topStackFrameRange.startLineNumber === stackFrame.range.startLineNumber && modelData.topStackFrameRange.startColumn !== stackFrame.range.startColumn) { + result.push({ + options: DebugCallStackContribution.TOP_STACK_FRAME_INLINE_DECORATION, + range: columnUntilEOLRange + }); + } + modelData.topStackFrameRange = columnUntilEOLRange; + } + } else { + result.push({ + options: DebugCallStackContribution.FOCUSED_STACK_FRAME_MARGIN, + range + }); + + result.push({ + options: DebugCallStackContribution.FOCUSED_STACK_FRAME_DECORATION, + range: columnUntilEOLRange + }); + } + + return result; + } + + // editor decorations + + static readonly STICKINESS = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; + // we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement. + private static TOP_STACK_FRAME_MARGIN: IModelDecorationOptions = { + glyphMarginClassName: 'debug-top-stack-frame', + stickiness + }; + + private static FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = { + glyphMarginClassName: 'debug-focused-stack-frame', + stickiness + }; + + private static TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = { + isWholeLine: true, + inlineClassName: 'debug-remove-token-colors', + className: 'debug-top-stack-frame-line', + stickiness + }; + + private static TOP_STACK_FRAME_INLINE_DECORATION: IModelDecorationOptions = { + beforeContentClassName: 'debug-top-stack-frame-column' + }; + + private static FOCUSED_STACK_FRAME_DECORATION: IModelDecorationOptions = { + isWholeLine: true, + inlineClassName: 'debug-remove-token-colors', + className: 'debug-focused-stack-frame-line', + stickiness + }; + + dispose(): void { + this.modelDataMap.forEach(modelData => { + modelData.model.deltaDecorations(modelData.currentStackDecorations, []); + }); + this.toDispose = dispose(this.toDispose); + + this.modelDataMap.clear(); + } +} + +registerThemingParticipant((theme, collector) => { + const topStackFrame = theme.getColor(topStackFrameColor); + if (topStackFrame) { + collector.addRule(`.monaco-editor .view-overlays .debug-top-stack-frame-line { background: ${topStackFrame}; }`); + collector.addRule(`.monaco-editor .view-overlays .debug-top-stack-frame-line { background: ${topStackFrame}; }`); + } + + const focusedStackFrame = theme.getColor(focusedStackFrameColor); + if (focusedStackFrame) { + collector.addRule(`.monaco-editor .view-overlays .debug-focused-stack-frame-line { background: ${focusedStackFrame}; }`); + } +}); + +const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#fff600' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.')); +const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#cee7ce' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.')); diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index d936aeec52..6c391d61ce 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -9,7 +9,7 @@ import { Range } from 'vs/editor/common/core/range'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ServicesAccessor, registerEditorAction, EditorAction, IActionOptions } from 'vs/editor/browser/editorExtensions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, REPL_ID, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, REPL_ID, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint, BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution } from 'vs/workbench/contrib/debug/common/debug'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -75,7 +75,7 @@ class ConditionalBreakpointAction extends EditorAction { const position = editor.getPosition(); if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { - editor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, position.column); + editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, undefined); } } } @@ -97,7 +97,7 @@ class LogPointAction extends EditorAction { const position = editor.getPosition(); if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { - editor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, position.column, BreakpointWidgetContext.LOG_MESSAGE); + editor.getContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, BreakpointWidgetContext.LOG_MESSAGE); } } } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index d2148dc75d..d182250281 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -5,13 +5,9 @@ import * as nls from 'vs/nls'; import { RunOnceScheduler } from 'vs/base/common/async'; -import * as lifecycle from 'vs/base/common/lifecycle'; import * as env from 'vs/base/common/platform'; -import { URI as uri } from 'vs/base/common/uri'; import { visit } from 'vs/base/common/json'; -import severity from 'vs/base/common/severity'; import { Constants } from 'vs/editor/common/core/uint'; -import { IAction, Action } from 'vs/base/common/actions'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardTokenType } from 'vs/editor/common/modes'; @@ -19,32 +15,25 @@ import { DEFAULT_WORD_REGEXP } from 'vs/editor/common/model/wordHelper'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { IDecorationOptions } from 'vs/editor/common/editorCommon'; -import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { Range } from 'vs/editor/common/core/range'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { RemoveBreakpointAction } from 'vs/workbench/contrib/debug/browser/debugActions'; -import { IDebugEditorContribution, IDebugService, State, IBreakpoint, EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo, BreakpointWidgetContext } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugEditorContribution, IDebugService, State, EDITOR_CONTRIBUTION_ID, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo } from 'vs/workbench/contrib/debug/common/debug'; import { ExceptionWidget } from 'vs/workbench/contrib/debug/browser/exceptionWidget'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { Position } from 'vs/editor/common/core/position'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; import { first } from 'vs/base/common/arrays'; -import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ContextSubMenu } from 'vs/base/browser/contextmenu'; import { memoize } from 'vs/base/common/decorators'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { getHover } from 'vs/editor/contrib/hover/getHover'; import { IEditorHoverOptions, EditorOption } from 'vs/editor/common/config/editorOptions'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { BreakpointWidget } from 'vs/workbench/contrib/debug/browser/breakpointWidget'; import { DebugHoverWidget } from 'vs/workbench/contrib/debug/browser/debugHover'; +import { ITextModel } from 'vs/editor/common/model'; +import { getHover } from 'vs/editor/contrib/hover/getHover'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; const HOVER_DELAY = 300; const LAUNCH_JSON_REGEX = /launch\.json$/; @@ -53,17 +42,14 @@ const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We const MAX_INLINE_DECORATOR_LENGTH = 150; // Max string length of each inline decorator when debugging. If exceeded ... is added const MAX_TOKENIZATION_LINE_LEN = 500; // If line is too long, then inline values for the line are skipped -export class DebugEditorContribution implements IDebugEditorContribution { +class DebugEditorContribution implements IDebugEditorContribution { - private toDispose: lifecycle.IDisposable[]; + private toDispose: IDisposable[]; private hoverWidget: DebugHoverWidget; private nonDebugHoverPosition: Position | undefined; private hoverRange: Range | null = null; private mouseDown = false; - private breakpointHintDecoration: string[]; - private breakpointWidget: BreakpointWidget | undefined; - private breakpointWidgetVisible: IContextKey; private wordToLineNumbersMap: Map | undefined; private exceptionWidget: ExceptionWidget | undefined; @@ -73,182 +59,21 @@ export class DebugEditorContribution implements IDebugEditorContribution { constructor( private editor: ICodeEditor, @IDebugService private readonly debugService: IDebugService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IContextKeyService contextKeyService: IContextKeyService, @ICommandService private readonly commandService: ICommandService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IKeybindingService private readonly keybindingService: IKeybindingService, - @IDialogService private readonly dialogService: IDialogService, ) { - this.breakpointHintDecoration = []; this.hoverWidget = this.instantiationService.createInstance(DebugHoverWidget, this.editor); this.toDispose = []; this.registerListeners(); - this.breakpointWidgetVisible = CONTEXT_BREAKPOINT_WIDGET_VISIBLE.bindTo(contextKeyService); this.updateConfigurationWidgetVisibility(); this.codeEditorService.registerDecorationType(INLINE_VALUE_DECORATION_KEY, {}); this.toggleExceptionWidget(); } - private getContextMenuActions(breakpoints: ReadonlyArray, uri: uri, lineNumber: number): Array { - const actions: Array = []; - if (breakpoints.length === 1) { - const breakpointType = breakpoints[0].logMessage ? nls.localize('logPoint', "Logpoint") : nls.localize('breakpoint', "Breakpoint"); - actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, nls.localize('removeBreakpoint', "Remove {0}", breakpointType), this.debugService, this.keybindingService)); - actions.push(new Action( - 'workbench.debug.action.editBreakpointAction', - nls.localize('editBreakpoint', "Edit {0}...", breakpointType), - undefined, - true, - () => Promise.resolve(this.editor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(breakpoints[0].lineNumber, breakpoints[0].column)) - )); - - actions.push(new Action( - `workbench.debug.viewlet.action.toggleBreakpoint`, - breakpoints[0].enabled ? nls.localize('disableBreakpoint', "Disable {0}", breakpointType) : nls.localize('enableBreakpoint', "Enable {0}", breakpointType), - undefined, - true, - () => this.debugService.enableOrDisableBreakpoints(!breakpoints[0].enabled, breakpoints[0]) - )); - } else if (breakpoints.length > 1) { - const sorted = breakpoints.slice().sort((first, second) => (first.column && second.column) ? first.column - second.column : 1); - actions.push(new ContextSubMenu(nls.localize('removeBreakpoints', "Remove Breakpoints"), sorted.map(bp => new Action( - 'removeInlineBreakpoint', - bp.column ? nls.localize('removeInlineBreakpointOnColumn', "Remove Inline Breakpoint on Column {0}", bp.column) : nls.localize('removeLineBreakpoint', "Remove Line Breakpoint"), - undefined, - true, - () => this.debugService.removeBreakpoints(bp.getId()) - )))); - - actions.push(new ContextSubMenu(nls.localize('editBreakpoints', "Edit Breakpoints"), sorted.map(bp => - new Action('editBreakpoint', - bp.column ? nls.localize('editInlineBreakpointOnColumn', "Edit Inline Breakpoint on Column {0}", bp.column) : nls.localize('editLineBrekapoint', "Edit Line Breakpoint"), - undefined, - true, - () => Promise.resolve(this.editor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(bp.lineNumber, bp.column)) - ) - ))); - - actions.push(new ContextSubMenu(nls.localize('enableDisableBreakpoints', "Enable/Disable Breakpoints"), sorted.map(bp => new Action( - bp.enabled ? 'disableColumnBreakpoint' : 'enableColumnBreakpoint', - bp.enabled ? (bp.column ? nls.localize('disableInlineColumnBreakpoint', "Disable Inline Breakpoint on Column {0}", bp.column) : nls.localize('disableBreakpointOnLine', "Disable Line Breakpoint")) - : (bp.column ? nls.localize('enableBreakpoints', "Enable Inline Breakpoint on Column {0}", bp.column) : nls.localize('enableBreakpointOnLine', "Enable Line Breakpoint")), - undefined, - true, - () => this.debugService.enableOrDisableBreakpoints(!bp.enabled, bp) - )))); - } else { - actions.push(new Action( - 'addBreakpoint', - nls.localize('addBreakpoint', "Add Breakpoint"), - undefined, - true, - () => this.debugService.addBreakpoints(uri, [{ lineNumber }], `debugEditorContextMenu`) - )); - actions.push(new Action( - 'addConditionalBreakpoint', - nls.localize('addConditionalBreakpoint', "Add Conditional Breakpoint..."), - undefined, - true, - () => Promise.resolve(this.editor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(lineNumber, undefined)) - )); - actions.push(new Action( - 'addLogPoint', - nls.localize('addLogPoint', "Add Logpoint..."), - undefined, - true, - () => Promise.resolve(this.editor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(lineNumber, undefined, BreakpointWidgetContext.LOG_MESSAGE)) - )); - } - - return actions; - } - private registerListeners(): void { - this.toDispose.push(this.editor.onMouseDown(async (e: IEditorMouseEvent) => { - const data = e.target.detail as IMarginData; - const model = this.editor.getModel(); - if (!e.target.position || !model || e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || data.isAfterLines || !this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { - return; - } - const canSetBreakpoints = this.debugService.getConfigurationManager().canSetBreakpointsIn(model); - const lineNumber = e.target.position.lineNumber; - const uri = model.uri; - - if (e.event.rightButton || (env.isMacintosh && e.event.leftButton && e.event.ctrlKey)) { - if (!canSetBreakpoints) { - return; - } - - const anchor = { x: e.event.posx, y: e.event.posy }; - const breakpoints = this.debugService.getModel().getBreakpoints({ lineNumber, uri }); - - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, - getActions: () => this.getContextMenuActions(breakpoints, uri, lineNumber), - getActionsContext: () => breakpoints.length ? breakpoints[0] : undefined - }); - } else { - const breakpoints = this.debugService.getModel().getBreakpoints({ uri, lineNumber }); - - if (breakpoints.length) { - // Show the dialog if there is a potential condition to be accidently lost. - // Do not show dialog on linux due to electron issue freezing the mouse #50026 - if (!env.isLinux && breakpoints.some(bp => !!bp.condition || !!bp.logMessage || !!bp.hitCondition)) { - const logPoint = breakpoints.every(bp => !!bp.logMessage); - const breakpointType = logPoint ? nls.localize('logPoint', "Logpoint") : nls.localize('breakpoint', "Breakpoint"); - const disable = breakpoints.some(bp => bp.enabled); - - const enabling = nls.localize('breakpointHasConditionDisabled', - "This {0} has a {1} that will get lost on remove. Consider enabling the {0} instead.", - breakpointType.toLowerCase(), - logPoint ? nls.localize('message', "message") : nls.localize('condition', "condition") - ); - const disabling = nls.localize('breakpointHasConditionEnabled', - "This {0} has a {1} that will get lost on remove. Consider disabling the {0} instead.", - breakpointType.toLowerCase(), - logPoint ? nls.localize('message', "message") : nls.localize('condition', "condition") - ); - - const { choice } = await this.dialogService.show(severity.Info, disable ? disabling : enabling, [ - nls.localize('removeLogPoint', "Remove {0}", breakpointType), - nls.localize('disableLogPoint', "{0} {1}", disable ? nls.localize('disable', "Disable") : nls.localize('enable', "Enable"), breakpointType), - nls.localize('cancel', "Cancel") - ], { cancelId: 2 }); - - if (choice === 0) { - breakpoints.forEach(bp => this.debugService.removeBreakpoints(bp.getId())); - } - if (choice === 1) { - breakpoints.forEach(bp => this.debugService.enableOrDisableBreakpoints(!disable, bp)); - } - } else { - breakpoints.forEach(bp => this.debugService.removeBreakpoints(bp.getId())); - } - } else if (canSetBreakpoints) { - this.debugService.addBreakpoints(uri, [{ lineNumber }], `debugEditorGutter`); - } - } - })); - - this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => { - let showBreakpointHintAtLineNumber = -1; - const model = this.editor.getModel(); - if (model && e.target.position && e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) && - this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { - const data = e.target.detail as IMarginData; - if (!data.isAfterLines) { - showBreakpointHintAtLineNumber = e.target.position.lineNumber; - } - } - this.ensureBreakpointHintDecoration(showBreakpointHintAtLineNumber); - })); - this.toDispose.push(this.editor.onMouseLeave((e: IEditorMouseEvent) => { - this.ensureBreakpointHintDecoration(-1); - })); this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(e => this.onFocusStackFrame(e.stackFrame))); // hover listeners & hover widget @@ -279,7 +104,6 @@ export class DebugEditorContribution implements IDebugEditorContribution { if (model) { this._applyHoverConfiguration(model, stackFrame); } - this.closeBreakpointWidget(); this.toggleExceptionWidget(); this.hideHoverWidget(); this.updateConfigurationWidgetVisibility(); @@ -317,11 +141,11 @@ export class DebugEditorContribution implements IDebugEditorContribution { } } - public getId(): string { + getId(): string { return EDITOR_CONTRIBUTION_ID; } - public showHover(range: Range, focus: boolean): Promise { + showHover(range: Range, focus: boolean): Promise { const sf = this.debugService.getViewModel().focusedStackFrame; const model = this.editor.getModel(); if (sf && model && sf.source.uri.toString() === model.uri.toString()) { @@ -331,36 +155,6 @@ export class DebugEditorContribution implements IDebugEditorContribution { return Promise.resolve(); } - private marginFreeFromNonDebugDecorations(line: number): boolean { - const decorations = this.editor.getLineDecorations(line); - if (decorations) { - for (const { options } of decorations) { - if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf('debug') === -1) { - return false; - } - } - } - - return true; - } - - private ensureBreakpointHintDecoration(showBreakpointHintAtLineNumber: number): void { - const newDecoration: IModelDeltaDecoration[] = []; - if (showBreakpointHintAtLineNumber !== -1) { - newDecoration.push({ - options: DebugEditorContribution.BREAKPOINT_HELPER_DECORATION, - range: { - startLineNumber: showBreakpointHintAtLineNumber, - startColumn: 1, - endLineNumber: showBreakpointHintAtLineNumber, - endColumn: 1 - } - }); - } - - this.breakpointHintDecoration = this.editor.deltaDecorations(this.breakpointHintDecoration, newDecoration); - } - private onFocusStackFrame(sf: IStackFrame | undefined): void { const model = this.editor.getModel(); if (model) { @@ -464,29 +258,8 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.hideHoverWidget(); } } - // end hover business - // breakpoint widget - public showBreakpointWidget(lineNumber: number, column: number, context?: BreakpointWidgetContext): void { - if (this.breakpointWidget) { - this.breakpointWidget.dispose(); - } - - this.breakpointWidget = this.instantiationService.createInstance(BreakpointWidget, this.editor, lineNumber, context); - this.breakpointWidget.show({ lineNumber, column: 1 }, 2); - this.breakpointWidgetVisible.set(true); - } - - public closeBreakpointWidget(): void { - if (this.breakpointWidget) { - this.breakpointWidget.dispose(); - this.breakpointWidget = undefined; - this.breakpointWidgetVisible.reset(); - this.editor.focus(); - } - } - // exception widget private toggleExceptionWidget(): void { // Toggles exception widget based on the state of the current editor model and debug stack frame @@ -547,7 +320,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { } } - public addLaunchConfiguration(): Promise { + addLaunchConfiguration(): Promise { /* __GDPR__ "debug/addLaunchConfiguration" : {} */ @@ -594,11 +367,6 @@ export class DebugEditorContribution implements IDebugEditorContribution { return insertLine(configurationsArrayPosition).then(() => this.commandService.executeCommand('editor.action.triggerSuggest')); } - private static BREAKPOINT_HELPER_DECORATION: IModelDecorationOptions = { - glyphMarginClassName: 'debug-breakpoint-hint', - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges - }; - // Inline Decorations @memoize @@ -768,17 +536,14 @@ export class DebugEditorContribution implements IDebugEditorContribution { return this.wordToLineNumbersMap; } - public dispose(): void { - if (this.breakpointWidget) { - this.breakpointWidget.dispose(); - } + dispose(): void { if (this.hoverWidget) { this.hoverWidget.dispose(); } if (this.configurationWidget) { this.configurationWidget.dispose(); } - this.toDispose = lifecycle.dispose(this.toDispose); + this.toDispose = dispose(this.toDispose); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts deleted file mode 100644 index 2091d69865..0000000000 --- a/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts +++ /dev/null @@ -1,334 +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 lifecycle from 'vs/base/common/lifecycle'; -import { Constants } from 'vs/editor/common/core/uint'; -import { Range } from 'vs/editor/common/core/range'; -import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/model'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IDebugService, IBreakpoint, State, IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { MarkdownString } from 'vs/base/common/htmlContent'; -import { getBreakpointMessageAndClassName } from 'vs/workbench/contrib/debug/browser/breakpointsView'; -import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { registerColor } from 'vs/platform/theme/common/colorRegistry'; -import { localize } from 'vs/nls'; -import { onUnexpectedError } from 'vs/base/common/errors'; - -interface IBreakpointDecoration { - decorationId: string; - modelId: string; - range: Range; -} - -interface IDebugEditorModelData { - model: ITextModel; - toDispose: lifecycle.IDisposable[]; - breakpointDecorations: IBreakpointDecoration[]; - currentStackDecorations: string[]; - topStackFrameRange: Range | undefined; -} - -export class DebugEditorModelManager implements IWorkbenchContribution { - static readonly ID = 'breakpointManager'; - static readonly STICKINESS = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; - private modelDataMap: Map; - private toDispose: lifecycle.IDisposable[]; - private ignoreDecorationsChangedEvent = false; - - constructor( - @IModelService private readonly modelService: IModelService, - @IDebugService private readonly debugService: IDebugService, - ) { - this.modelDataMap = new Map(); - this.toDispose = []; - this.registerListeners(); - } - - public dispose(): void { - this.modelDataMap.forEach(modelData => { - lifecycle.dispose(modelData.toDispose); - modelData.model.deltaDecorations(modelData.breakpointDecorations.map(bpd => bpd.decorationId), []); - modelData.model.deltaDecorations(modelData.currentStackDecorations, []); - }); - this.toDispose = lifecycle.dispose(this.toDispose); - - this.modelDataMap.clear(); - } - - private registerListeners(): void { - this.toDispose.push(this.modelService.onModelAdded(this.onModelAdded, this)); - this.modelService.getModels().forEach(model => this.onModelAdded(model)); - this.toDispose.push(this.modelService.onModelRemoved(this.onModelRemoved, this)); - - this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); - this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => this.onFocusStackFrame())); - this.toDispose.push(this.debugService.onDidChangeState(state => { - if (state === State.Inactive) { - this.modelDataMap.forEach(modelData => { - modelData.topStackFrameRange = undefined; - }); - } - })); - } - - private onModelAdded(model: ITextModel): void { - const modelUriStr = model.uri.toString(); - const breakpoints = this.debugService.getModel().getBreakpoints({ uri: model.uri }); - - const currentStackDecorations = model.deltaDecorations([], this.createCallStackDecorations(modelUriStr)); - const desiredDecorations = this.createBreakpointDecorations(model, breakpoints); - const breakpointDecorationIds = model.deltaDecorations([], desiredDecorations); - const toDispose: lifecycle.IDisposable[] = [model.onDidChangeDecorations((e) => this.onModelDecorationsChanged(modelUriStr))]; - - this.modelDataMap.set(modelUriStr, { - model: model, - toDispose: toDispose, - breakpointDecorations: breakpointDecorationIds.map((decorationId, index) => ({ decorationId, modelId: breakpoints[index].getId(), range: desiredDecorations[index].range })), - currentStackDecorations: currentStackDecorations, - topStackFrameRange: undefined - }); - } - - private onModelRemoved(model: ITextModel): void { - const modelUriStr = model.uri.toString(); - const data = this.modelDataMap.get(modelUriStr); - if (data) { - lifecycle.dispose(data.toDispose); - this.modelDataMap.delete(modelUriStr); - } - } - - // call stack management. Represent data coming from the debug service. - - private onFocusStackFrame(): void { - this.modelDataMap.forEach((modelData, uri) => { - modelData.currentStackDecorations = modelData.model.deltaDecorations(modelData.currentStackDecorations, this.createCallStackDecorations(uri)); - }); - } - - private createCallStackDecorations(modelUriStr: string): IModelDeltaDecoration[] { - const result: IModelDeltaDecoration[] = []; - const stackFrame = this.debugService.getViewModel().focusedStackFrame; - if (!stackFrame || stackFrame.source.uri.toString() !== modelUriStr) { - return result; - } - - // only show decorations for the currently focused thread. - const columnUntilEOLRange = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, Constants.MAX_SAFE_SMALL_INTEGER); - const range = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, stackFrame.range.startColumn + 1); - - // compute how to decorate the editor. Different decorations are used if this is a top stack frame, focused stack frame, - // an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line). - const callStack = stackFrame.thread.getCallStack(); - if (callStack && callStack.length && stackFrame === callStack[0]) { - result.push({ - options: DebugEditorModelManager.TOP_STACK_FRAME_MARGIN, - range - }); - - result.push({ - options: DebugEditorModelManager.TOP_STACK_FRAME_DECORATION, - range: columnUntilEOLRange - }); - - const modelData = this.modelDataMap.get(modelUriStr); - if (modelData) { - if (modelData.topStackFrameRange && modelData.topStackFrameRange.startLineNumber === stackFrame.range.startLineNumber && modelData.topStackFrameRange.startColumn !== stackFrame.range.startColumn) { - result.push({ - options: DebugEditorModelManager.TOP_STACK_FRAME_INLINE_DECORATION, - range: columnUntilEOLRange - }); - } - modelData.topStackFrameRange = columnUntilEOLRange; - } - } else { - result.push({ - options: DebugEditorModelManager.FOCUSED_STACK_FRAME_MARGIN, - range - }); - - result.push({ - options: DebugEditorModelManager.FOCUSED_STACK_FRAME_DECORATION, - range: columnUntilEOLRange - }); - } - - return result; - } - - // breakpoints management. Represent data coming from the debug service and also send data back. - private onModelDecorationsChanged(modelUrlStr: string): void { - const modelData = this.modelDataMap.get(modelUrlStr); - if (!modelData || modelData.breakpointDecorations.length === 0 || this.ignoreDecorationsChangedEvent) { - // I have no decorations - return; - } - let somethingChanged = false; - modelData.breakpointDecorations.forEach(breakpointDecoration => { - if (somethingChanged) { - return; - } - const newBreakpointRange = modelData.model.getDecorationRange(breakpointDecoration.decorationId); - if (newBreakpointRange && (!breakpointDecoration.range.equalsRange(newBreakpointRange))) { - somethingChanged = true; - } - }); - if (!somethingChanged) { - // nothing to do, my decorations did not change. - return; - } - - const data = new Map(); - const breakpoints = this.debugService.getModel().getBreakpoints(); - const modelUri = modelData.model.uri; - for (let i = 0, len = modelData.breakpointDecorations.length; i < len; i++) { - const breakpointDecoration = modelData.breakpointDecorations[i]; - const decorationRange = modelData.model.getDecorationRange(breakpointDecoration.decorationId); - // check if the line got deleted. - if (decorationRange) { - const breakpoint = breakpoints.filter(bp => bp.getId() === breakpointDecoration.modelId).pop(); - // since we know it is collapsed, it cannot grow to multiple lines - if (breakpoint) { - data.set(breakpoint.getId(), { - lineNumber: decorationRange.startLineNumber, - column: breakpoint.column ? decorationRange.startColumn : undefined, - }); - } - } - } - - this.debugService.updateBreakpoints(modelUri, data, true).then(undefined, onUnexpectedError); - } - - private onBreakpointsChange(): void { - const breakpointsMap = new Map(); - this.debugService.getModel().getBreakpoints().forEach(bp => { - const uriStr = bp.uri.toString(); - const breakpoints = breakpointsMap.get(uriStr); - if (breakpoints) { - breakpoints.push(bp); - } else { - breakpointsMap.set(uriStr, [bp]); - } - }); - - breakpointsMap.forEach((bps, uri) => { - const data = this.modelDataMap.get(uri); - if (data) { - this.updateBreakpoints(data, breakpointsMap.get(uri)!); - } - }); - this.modelDataMap.forEach((modelData, uri) => { - if (!breakpointsMap.has(uri)) { - this.updateBreakpoints(modelData, []); - } - }); - } - - private updateBreakpoints(modelData: IDebugEditorModelData, newBreakpoints: IBreakpoint[]): void { - const desiredDecorations = this.createBreakpointDecorations(modelData.model, newBreakpoints); - try { - this.ignoreDecorationsChangedEvent = true; - const breakpointDecorationIds = modelData.model.deltaDecorations(modelData.breakpointDecorations.map(bpd => bpd.decorationId), desiredDecorations); - modelData.breakpointDecorations = breakpointDecorationIds.map((decorationId, index) => ({ - decorationId, - modelId: newBreakpoints[index].getId(), - range: desiredDecorations[index].range - })); - } finally { - this.ignoreDecorationsChangedEvent = false; - } - } - - private createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArray): { range: Range; options: IModelDecorationOptions; }[] { - const result: { range: Range; options: IModelDecorationOptions; }[] = []; - breakpoints.forEach((breakpoint) => { - if (breakpoint.lineNumber <= model.getLineCount()) { - const column = model.getLineFirstNonWhitespaceColumn(breakpoint.lineNumber); - const range = model.validateRange( - breakpoint.column ? new Range(breakpoint.lineNumber, breakpoint.column, breakpoint.lineNumber, breakpoint.column + 1) - : new Range(breakpoint.lineNumber, column, breakpoint.lineNumber, column + 1) // Decoration has to have a width #20688 - ); - - result.push({ - options: this.getBreakpointDecorationOptions(breakpoint), - range - }); - } - }); - - return result; - } - - private getBreakpointDecorationOptions(breakpoint: IBreakpoint): IModelDecorationOptions { - const { className, message } = getBreakpointMessageAndClassName(this.debugService, breakpoint); - let glyphMarginHoverMessage: MarkdownString | undefined; - - if (message) { - if (breakpoint.condition || breakpoint.hitCondition) { - const modelData = this.modelDataMap.get(breakpoint.uri.toString()); - const modeId = modelData ? modelData.model.getLanguageIdentifier().language : ''; - glyphMarginHoverMessage = new MarkdownString().appendCodeblock(modeId, message); - } else { - glyphMarginHoverMessage = new MarkdownString().appendText(message); - } - } - - return { - glyphMarginClassName: className, - glyphMarginHoverMessage, - stickiness: DebugEditorModelManager.STICKINESS, - beforeContentClassName: breakpoint.column ? `debug-breakpoint-column ${className}-column` : undefined - }; - } - - // editor decorations - - // we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement. - private static TOP_STACK_FRAME_MARGIN: IModelDecorationOptions = { - glyphMarginClassName: 'debug-top-stack-frame', - stickiness: DebugEditorModelManager.STICKINESS - }; - - private static FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = { - glyphMarginClassName: 'debug-focused-stack-frame', - stickiness: DebugEditorModelManager.STICKINESS - }; - - private static TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = { - isWholeLine: true, - inlineClassName: 'debug-remove-token-colors', - className: 'debug-top-stack-frame-line', - stickiness: DebugEditorModelManager.STICKINESS - }; - - private static TOP_STACK_FRAME_INLINE_DECORATION: IModelDecorationOptions = { - beforeContentClassName: 'debug-top-stack-frame-column' - }; - - private static FOCUSED_STACK_FRAME_DECORATION: IModelDecorationOptions = { - isWholeLine: true, - inlineClassName: 'debug-remove-token-colors', - className: 'debug-focused-stack-frame-line', - stickiness: DebugEditorModelManager.STICKINESS - }; -} - -registerThemingParticipant((theme, collector) => { - const topStackFrame = theme.getColor(topStackFrameColor); - if (topStackFrame) { - collector.addRule(`.monaco-editor .view-overlays .debug-top-stack-frame-line { background: ${topStackFrame}; }`); - collector.addRule(`.monaco-editor .view-overlays .debug-top-stack-frame-line { background: ${topStackFrame}; }`); - } - - const focusedStackFrame = theme.getColor(focusedStackFrameColor); - if (focusedStackFrame) { - collector.addRule(`.monaco-editor .view-overlays .debug-focused-stack-frame-line { background: ${focusedStackFrame}; }`); - } -}); - -const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#fff600' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.')); -const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#cee7ce' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.')); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 41bcd6b563..ebdac647cc 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -10,19 +10,20 @@ import * as platform from 'vs/base/common/platform'; import severity from 'vs/base/common/severity'; import { Event, Emitter } from 'vs/base/common/event'; import { CompletionItem, completionKindFromString } from 'vs/editor/common/modes'; -import { Position } from 'vs/editor/common/core/position'; +import { Position, IPosition } from 'vs/editor/common/core/position'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { mixin } from 'vs/base/common/objects'; import { Thread, ExpressionContainer, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { generateUuid } from 'vs/base/common/uuid'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { Range } from 'vs/editor/common/core/range'; @@ -34,6 +35,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IOpenerService } from 'vs/platform/opener/common/opener'; import { variableSetEmitter } from 'vs/workbench/contrib/debug/browser/variablesView'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; +import { distinct } from 'vs/base/common/arrays'; export class DebugSession implements IDebugSession { @@ -73,7 +75,7 @@ export class DebugSession implements IDebugSession { @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @INotificationService private readonly notificationService: INotificationService, @IProductService private readonly productService: IProductService, - @IWindowsService private readonly windowsService: IWindowsService, + @IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService, @IOpenerService private readonly openerService: IOpenerService ) { this.id = generateUuid(); @@ -185,7 +187,7 @@ export class DebugSession implements IDebugSession { return dbgr.createDebugAdapter(this).then(debugAdapter => { - this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.windowsService, this.openerService); + this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.extensionHostDebugService, this.openerService); return this.raw.start().then(() => { @@ -284,15 +286,7 @@ export class DebugSession implements IDebugSession { return Promise.resolve(undefined); } - const source = this.getSourceForUri(modelUri); - let rawSource: DebugProtocol.Source; - if (source) { - rawSource = source.raw; - } else { - const data = Source.getEncodedDebugData(modelUri); - rawSource = { name: data.name, path: data.path, sourceReference: data.sourceReference }; - } - + const rawSource = this.getRawSource(modelUri); if (breakpointsToSend.length && !rawSource.adapterData) { rawSource.adapterData = breakpointsToSend[0].adapterData; } @@ -376,6 +370,17 @@ export class DebugSession implements IDebugSession { return Promise.reject(new Error('no debug adapter')); } + async breakpointsLocations(uri: URI, lineNumber: number): Promise { + if (this.raw) { + const source = this.getRawSource(uri); + const response = await this.raw.breakpointLocations({ source, line: lineNumber }); + const positions = response.body.breakpoints.map(bp => ({ lineNumber: bp.line, column: bp.column || 1 })); + + return distinct(positions, p => `${p.lineNumber}:${p.column}`); + } + return Promise.reject(new Error('no debug adapter')); + } + customRequest(request: string, args: any): Promise { if (this.raw) { return this.raw.custom(request, args); @@ -914,6 +919,16 @@ export class DebugSession implements IDebugSession { return source; } + private getRawSource(uri: URI): DebugProtocol.Source { + const source = this.getSourceForUri(uri); + if (source) { + return source.raw; + } else { + const data = Source.getEncodedDebugData(uri); + return { name: data.name, path: data.path, sourceReference: data.sourceReference }; + } + } + private getNewCancellationToken(threadId: number): CancellationToken { const tokenSource = new CancellationTokenSource(); const tokens = this.cancellationMap.get(threadId) || []; diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index 4f604bdfe4..e24d0ff573 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -5,7 +5,7 @@ import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; @@ -13,8 +13,10 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; -class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient { +class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient implements IExtensionHostDebugService { constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, @@ -45,6 +47,52 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient { } })); } + + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + // we pass the "ParsedArgs" as query parameters of the URL + + let newAddress = `${document.location.origin}${document.location.pathname}?`; + let gotFolder = false; + + const addQueryParameter = (key: string, value: string) => { + const lastChar = newAddress.charAt(newAddress.length - 1); + if (lastChar !== '?' && lastChar !== '&') { + newAddress += '&'; + } + newAddress += `${key}=${encodeURIComponent(value)}`; + }; + + const f = args['folder-uri']; + if (f) { + const u = URI.parse(f[0]); + gotFolder = true; + addQueryParameter('folder', u.path); + } + if (!gotFolder) { + // request empty window + addQueryParameter('ew', 'true'); + } + + const ep = args['extensionDevelopmentPath']; + if (ep) { + let u = ep[0]; + addQueryParameter('edp', u); + } + + const di = args['debugId']; + if (di) { + addQueryParameter('di', di); + } + + const ibe = args['inspect-brk-extensions']; + if (ibe) { + addQueryParameter('ibe', ibe); + } + + window.open(newAddress); + + return Promise.resolve(); + } } registerSingleton(IExtensionHostDebugService, BrowserExtensionHostDebugService); diff --git a/src/vs/workbench/contrib/debug/browser/media/clear-dark.svg b/src/vs/workbench/contrib/debug/browser/media/clear-dark.svg deleted file mode 100644 index 04d64ab41c..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/clear-dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/clear-hc.svg b/src/vs/workbench/contrib/debug/browser/media/clear-hc.svg deleted file mode 100644 index 44a41edd3b..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/clear-hc.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/clear-light.svg b/src/vs/workbench/contrib/debug/browser/media/clear-light.svg deleted file mode 100644 index f6a51c856f..0000000000 --- a/src/vs/workbench/contrib/debug/browser/media/clear-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index c4c5072a29..1364e6a4c7 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -17,12 +17,20 @@ } .debug-breakpoint-disabled, -.monaco-editor .debug-breakpoint-column.debug-breakpoint-disabled-column::before { +.monaco-editor .inline-breakpoint-widget.debug-breakpoint-disabled { background: url('breakpoint-disabled.svg') center center no-repeat; } +.monaco-editor .inline-breakpoint-widget.debug-breakpoint-disabled:hover { + background: url('breakpoint-hint.svg') center center no-repeat; +} + +.monaco-editor .inline-breakpoint-widget.line-start { + left: -0.45em !important; +} + .debug-breakpoint-unverified, -.monaco-editor .debug-breakpoint-column.debug-breakpoint-unverified-column::before { +.monaco-editor .inline-breakpoint-widget.debug-breakpoint-unverified { background: url('breakpoint-unverified.svg') center center no-repeat; } @@ -35,21 +43,31 @@ } .debug-breakpoint, -.monaco-editor .debug-breakpoint-column::before { +.monaco-editor .inline-breakpoint-widget { background: url('breakpoint.svg') center center no-repeat; } -.monaco-editor .debug-breakpoint-column::before, +.monaco-editor .debug-breakpoint-placeholder::before, .monaco-editor .debug-top-stack-frame-column::before { content: " "; width: 1.3em; - height: 1.3em; display: inline-block; vertical-align: text-bottom; margin-right: 2px; margin-left: 2px; } +.monaco-editor .debug-top-stack-frame-column::before { + height: 1.3em; +} + +.monaco-editor .inline-breakpoint-widget { + width: 1.3em; + height: 1.3em; + margin-left: 0.61em; + cursor: pointer; +} + .debug-function-breakpoint { background: url('breakpoint-function.svg') center center no-repeat; } @@ -75,34 +93,34 @@ } .debug-breakpoint-conditional, -.monaco-editor .debug-breakpoint-column.debug-breakpoint-conditional-column::before { +.monaco-editor .inline-breakpoint-widget.debug-breakpoint-conditional { background: url('breakpoint-conditional.svg') center center no-repeat; } .debug-breakpoint-log, -.monaco-editor .debug-breakpoint-column.debug-breakpoint-log-column::before { +.monaco-editor .inline-breakpoint-widget.debug-breakpoint-log { background: url('breakpoint-log.svg') center center no-repeat; } .debug-breakpoint-log-disabled, -.monaco-editor .debug-breakpoint-log-disabled-column::before { +.monaco-editor .inline-breakpoint-widget.debug-breakpoint-log-disabled { background: url('breakpoint-log-disabled.svg') center center no-repeat; } .debug-breakpoint-log-unverified, -.monaco-editor .debug-breakpoint-log-unverified-column::before { +.monaco-editor .inline-breakpoint-widget.debug-breakpoint-log-unverified { background: url('breakpoint-log-unverified.svg') center center no-repeat; } .debug-breakpoint-unsupported, -.monaco-editor .debug-breakpoint-column.debug-breakpoint-unsupported-column::before { +.monaco-editor .inline-breakpoint-widget.debug-breakpoint-unsupported { background: url('breakpoint-unsupported.svg') center center no-repeat; } .monaco-editor .debug-top-stack-frame.debug-breakpoint, .monaco-editor .debug-top-stack-frame.debug-breakpoint-conditional, .monaco-editor .debug-top-stack-frame.debug-breakpoint-log, -.monaco-editor .debug-breakpoint-column.debug-breakpoint-column.debug-top-stack-frame-column::before { +.monaco-editor .inline-breakpoint-widget.debug-top-stack-frame-column { background: url('current-and-breakpoint.svg') center center no-repeat; } diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index fae75d5dcc..cdb38f82ba 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -88,19 +88,6 @@ font-size: 9px; } -/* Actions */ -.debug-action.clear-repl { - background: url('clear-light.svg') center center no-repeat; -} - -.vs-dark .debug-action.clear-repl { - background: url('clear-dark.svg') center center no-repeat; -} - -.hc-black .debug-action.clear-repl { - background: url('clear-hc.svg') center center no-repeat; -} - /* Output coloring and styling */ .repl .repl-tree .output.expression > .ignore { font-style: italic; diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 1dcb9efe4f..7af7df6c67 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -13,7 +13,7 @@ import { formatPII, isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; import { IDebugAdapter, IConfig, AdapterEndEvent, IDebugger } from 'vs/workbench/contrib/debug/common/debug'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { URI } from 'vs/base/common/uri'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { env as processEnv } from 'vs/base/common/process'; @@ -79,7 +79,7 @@ export class RawDebugSession implements IDisposable { dbgr: IDebugger, private readonly telemetryService: ITelemetryService, public readonly customTelemetryService: ITelemetryService | undefined, - private readonly windowsService: IWindowsService, + private readonly extensionHostDebugService: IExtensionHostDebugService, private readonly openerService: IOpenerService ) { @@ -381,6 +381,13 @@ export class RawDebugSession implements IDisposable { return this.send('setExceptionBreakpoints', args); } + breakpointLocations(args: DebugProtocol.BreakpointLocationsArguments): Promise { + if (this.capabilities.supportsBreakpointLocationsRequest) { + return this.send('breakpointLocations', args); + } + return Promise.reject(new Error('breakpointLocations is not supported')); + } + configurationDone(): Promise { if (this.capabilities.supportsConfigurationDoneRequest) { return this.send('configurationDone', null); @@ -594,7 +601,7 @@ export class RawDebugSession implements IDisposable { } else { args[key] = [value]; } - } else if (key === 'extensionDevelopmentPath') { + } else if (key === 'extensionDevelopmentPath' || key === 'enable-proposed-api') { const v = args[key]; if (v) { v.push(value); @@ -625,7 +632,7 @@ export class RawDebugSession implements IDisposable { Object.keys(env).filter(k => env[k] === null).forEach(key => delete env[key]); } - return this.windowsService.openExtensionDevelopmentHostWindow(args, env); + return this.extensionHostDebugService.openExtensionDevelopmentHostWindow(args, env); } private send(command: string, args: any, token?: CancellationToken, timeout?: number): Promise { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 88dd5edeb7..3d5dc43499 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -1008,7 +1008,7 @@ export class ClearReplAction extends Action { constructor(id: string, label: string, @IPanelService private readonly panelService: IPanelService ) { - super(id, label, 'debug-action clear-repl'); + super(id, label, 'debug-action codicon-clear-all'); } run(): Promise { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 76c476e101..cfdd333b43 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -12,7 +12,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ITextModel as EditorIModel } from 'vs/editor/common/model'; import { IEditor, ITextEditor } from 'vs/workbench/common/editor'; -import { Position } from 'vs/editor/common/core/position'; +import { Position, IPosition } from 'vs/editor/common/core/position'; import { CompletionItem } from 'vs/editor/common/modes'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { Range, IRange } from 'vs/editor/common/core/range'; @@ -58,6 +58,7 @@ export const CONTEXT_RESTART_FRAME_SUPPORTED = new RawContextKey('resta export const CONTEXT_JUMP_TO_CURSOR_SUPPORTED = new RawContextKey('jumpToCursorSupported', false); export const EDITOR_CONTRIBUTION_ID = 'editor.contrib.debug'; +export const BREAKPOINT_EDITOR_CONTRIBUTION_ID = 'editor.contrib.breakpoint'; export const DEBUG_SCHEME = 'debug'; export const INTERNAL_CONSOLE_OPTIONS_SCHEMA = { enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart'], @@ -207,6 +208,7 @@ export interface IDebugSession extends ITreeElement { dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }>; sendDataBreakpoints(dbps: IDataBreakpoint[]): Promise; sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise; + breakpointsLocations(uri: uri, lineNumber: number): Promise; stackTrace(threadId: number, startFrame: number, levels: number): Promise; exceptionInfo(threadId: number): Promise; @@ -850,9 +852,12 @@ export const enum BreakpointWidgetContext { export interface IDebugEditorContribution extends IEditorContribution { showHover(range: Range, focus: boolean): Promise; + addLaunchConfiguration(): Promise; +} + +export interface IBreakpointEditorContribution extends IEditorContribution { showBreakpointWidget(lineNumber: number, column: number | undefined, context?: BreakpointWidgetContext): void; closeBreakpointWidget(): void; - addLaunchConfiguration(): Promise; } // temporary debug helper service diff --git a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts index 4d93e4cae2..a357078862 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts @@ -7,13 +7,22 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient { constructor( - @IMainProcessService readonly windowService: IMainProcessService, + @IMainProcessService readonly mainProcessService: IMainProcessService, + @IWindowsService private readonly windowsService: IWindowsService ) { - super(windowService.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); + super(mainProcessService.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); + } + + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + // TODO@Isidor use debug IPC channel + return this.windowsService.openExtensionDevelopmentHostWindow(args, env); } } diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index d01a67855b..5f0633812a 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -6,7 +6,7 @@ import { URI as uri } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { Position } from 'vs/editor/common/core/position'; +import { Position, IPosition } from 'vs/editor/common/core/position'; import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { CompletionItem } from 'vs/editor/common/modes'; @@ -132,6 +132,11 @@ export class MockDebugService implements IDebugService { } export class MockSession implements IDebugSession { + + breakpointsLocations(uri: uri, lineNumber: number): Promise { + throw new Error('Method not implemented.'); + } + dataBreakpointInfo(name: string, variablesReference?: number | undefined): Promise<{ dataId: string | null; description: string; canPersist?: boolean | undefined; }> { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index 4ef7728f75..04c6afaac8 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -19,7 +19,7 @@ import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/co import { CancellationToken } from 'vs/base/common/cancellation'; import { distinct } from 'vs/base/common/arrays'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IWorkspaceStatsService } from 'vs/workbench/contrib/stats/common/workspaceStats'; export const enum ExperimentState { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts index be089c2858..92cccf6e09 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionTipsService.ts @@ -42,7 +42,7 @@ import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/wo import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { extname } from 'vs/base/common/resources'; -import { IExeBasedExtensionTip, IProductService } from 'vs/platform/product/common/product'; +import { IExeBasedExtensionTip, IProductService } from 'vs/platform/product/common/productService'; import { timeout } from 'vs/base/common/async'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; // {{SQL CARBON EDIT}} import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; // {{SQL CARBON EDIT}} diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index acf883c6df..08d8ee22de 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -20,7 +20,7 @@ import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/brow import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, ShowBuiltInExtensionsAction, UpdateAllAction, - EnableAllAction, EnableAllWorkpsaceAction, DisableAllAction, DisableAllWorkpsaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, ShowAzureExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, OpenExtensionsFolderAction, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction + EnableAllAction, EnableAllWorkpsaceAction, DisableAllAction, DisableAllWorkpsaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, ShowAzureExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; @@ -43,7 +43,6 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionDependencyChecker } from 'vs/workbench/contrib/extensions/browser/extensionsDependencyChecker'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { RemoteExtensionsInstaller } from 'vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller'; import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; @@ -351,7 +350,6 @@ const workbenchRegistry = Registry.as(Workbench class ExtensionsContributions implements IWorkbenchContribution { constructor( - @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService ) { @@ -369,14 +367,7 @@ class ExtensionsContributions implements IWorkbenchContribution { ) ); } - - if (workbenchEnvironmentService.extensionsPath) { - const openExtensionsFolderActionDescriptor = new SyncActionDescriptor(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL); - actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); - } - } - } workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 3f50ed7d1c..4d02373245 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -26,7 +26,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { IFileService, IFileContent } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; @@ -45,7 +45,6 @@ import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/w import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; @@ -59,7 +58,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -2073,13 +2072,13 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio removeFrom = removeFrom.filter(x => x.toLowerCase() !== extensionIdLowerCase); return this.jsonEditingService.write(workspaceConfigurationFile, - { + [{ key: 'extensions', value: { recommendations: shouldRecommend ? insertInto : removeFrom, unwantedRecommendations: shouldRecommend ? removeFrom : insertInto } - }, + }], true); }); } @@ -2102,19 +2101,19 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio if (removeFrom.some(e => e.toLowerCase() === extensionIdLowerCase)) { removeFrom = removeFrom.filter(x => x.toLowerCase() !== extensionIdLowerCase); removeFromPromise = this.jsonEditingService.write(extensionsFileResource, - { + [{ key: shouldRecommend ? 'unwantedRecommendations' : 'recommendations', value: removeFrom - }, + }], true); } return removeFromPromise.then(() => this.jsonEditingService.write(extensionsFileResource, - { + [{ key: shouldRecommend ? 'recommendations' : 'unwantedRecommendations', value: insertInto - }, + }], true) ); }); @@ -2139,7 +2138,7 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio .then(content => { const workspaceRecommendations = json.parse(content.value.toString())['extensions']; if (!workspaceRecommendations || !workspaceRecommendations.recommendations) { - return this.jsonEditingService.write(workspaceConfigurationFile, { key: 'extensions', value: { recommendations: [] } }, true) + return this.jsonEditingService.write(workspaceConfigurationFile, [{ key: 'extensions', value: { recommendations: [] } }], true) .then(() => this.fileService.readFile(workspaceConfigurationFile)); } return content; @@ -2801,41 +2800,6 @@ export class EnableAllWorkpsaceAction extends Action { } } -export class OpenExtensionsFolderAction extends Action { - - static readonly ID = 'workbench.extensions.action.openExtensionsFolder'; - static LABEL = localize('openExtensionsFolder', "Open Extensions Folder"); - - constructor( - id: string, - label: string, - @IWindowsService private readonly windowsService: IWindowsService, - @IFileService private readonly fileService: IFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - if (this.environmentService.extensionsPath) { - - const extensionsHome = URI.file(this.environmentService.extensionsPath); - - return Promise.resolve(this.fileService.resolve(extensionsHome)).then(file => { - let itemToShow: URI; - if (file.children && file.children.length > 0) { - itemToShow = file.children[0].resource; - } else { - itemToShow = extensionsHome; - } - - return this.windowsService.showItemInFolder(itemToShow); - }); - } - return Promise.resolve(); - } -} - export class InstallVSIXAction extends Action { static readonly ID = 'workbench.extensions.action.installVSIX'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index a649f6b095..3538d6c0ec 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -44,7 +44,7 @@ import { IAction } from 'vs/base/common/actions'; import { ExtensionType, ExtensionIdentifier, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 20b4cb3fd7..b09958f639 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -36,7 +36,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionManifest, ExtensionType, IExtension as IPlatformExtension, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { asDomUri } from 'vs/base/browser/dom'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; // {{SQL CARBON EDIT}} diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css index 22c7193cf7..585422ab33 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css @@ -78,7 +78,7 @@ margin: 0.15em; } -.monaco-action-bar .action-item .action-label.system-disable.icon { +.monaco-action-bar .action-item .action-label.system-disable.codicon { opacity: 1; height: 18px; width: 10px; diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index 83206a788e..6ebd114194 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -197,7 +197,7 @@ } .extensions-viewlet > .extensions .extension > .details > .header-container > .header > .install-count > .octicon { - font-size: 100%; + font-size: 120%; margin-right: 2px; } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index fb8f7304a4..df80b52e6b 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -15,7 +15,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { randomPort } from 'vs/base/node/ports'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler'; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index 49ce8cc194..88e8721741 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -8,7 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -22,6 +22,9 @@ import { URI } from 'vs/base/common/uri'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler'; import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { OpenExtensionsFolderAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; +import { ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement'; // Singletons registerSingleton(IExtensionHostProfileService, ExtensionHostProfileService, true); @@ -57,6 +60,20 @@ const actionRegistry = Registry.as(WorkbenchActionExte actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowRuntimeExtensionsAction, ShowRuntimeExtensionsAction.ID, ShowRuntimeExtensionsAction.LABEL), 'Show Running Extensions', localize('developer', "Developer")); +class ExtensionsContributions implements IWorkbenchContribution { + + constructor( + @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService + ) { + if (workbenchEnvironmentService.extensionsPath) { + const openExtensionsFolderActionDescriptor = new SyncActionDescriptor(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL); + actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); + } + } +} + +workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting); + // Register Commands CommandsRegistry.registerCommand(DebugExtensionHostAction.ID, (accessor: ServicesAccessor) => { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts new file mode 100644 index 0000000000..74092653ca --- /dev/null +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { Schemas } from 'vs/base/common/network'; + +export class OpenExtensionsFolderAction extends Action { + + static readonly ID = 'workbench.extensions.action.openExtensionsFolder'; + static LABEL = localize('openExtensionsFolder', "Open Extensions Folder"); + + constructor( + id: string, + label: string, + @IElectronService private readonly electronService: IElectronService, + @IFileService private readonly fileService: IFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService + ) { + super(id, label, undefined, true); + } + + async run(): Promise { + if (this.environmentService.extensionsPath) { + const extensionsHome = URI.file(this.environmentService.extensionsPath); + const file = await this.fileService.resolve(extensionsHome); + + let itemToShow: URI; + if (file.children && file.children.length > 0) { + itemToShow = file.children[0].resource; + } else { + itemToShow = extensionsHome; + } + + if (itemToShow.scheme === Schemas.file) { + return this.electronService.showItemInFolder(itemToShow.fsPath); + } + } + } +} + diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts index a6cf97a013..375b1a10fe 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsSlowActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as os from 'os'; -import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/common/product'; import { Action } from 'vs/base/common/actions'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; @@ -139,7 +139,7 @@ class ReportExtensionSlowAction extends Action { - Extension Name: \`${this.extension.name}\` - Extension Version: \`${this.extension.version}\` - OS Version: \`${osVersion}\` -- VSCode version: \`${pkg.version}\`\n\n${message}`); +- VSCode version: \`${product.version}\`\n\n${message}`); const url = `${this.repoInfo.base}/${this.repoInfo.owner}/${this.repoInfo.repo}/issues/new/?body=${body}&title=${title}`; this._openerService.open(URI.parse(url)); diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 2f7a265293..449fee92d9 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -6,8 +6,7 @@ import 'vs/css!./media/runtimeExtensionsEditor'; import * as nls from 'vs/nls'; import * as os from 'os'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/common/product'; import { Action, IAction } from 'vs/base/common/actions'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -523,7 +522,7 @@ export class ReportExtensionIssueAction extends Action { - Extension Name: \`${extension.description.name}\` - Extension Version: \`${extension.description.version}\` - OS Version: \`${osVersion}\` -- VSCode version: \`${pkg.version}\`\n\n${message}` +- VSCode version: \`${product.version}\`\n\n${message}` ); return `${baseUrl}${queryStringPrefix}body=${body}&title=${encodeURIComponent(title)}`; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index b002e5c3f5..84716874c6 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -41,7 +41,7 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedPr import { CancellationToken } from 'vs/base/common/cancellation'; import { ILabelService } from 'vs/platform/label/common/label'; import { ExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 36bd1e0ad1..a9a9cba802 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -51,7 +51,7 @@ import { NullLogService } from 'vs/platform/log/common/log'; import { Schemas } from 'vs/base/common/network'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { IFileService } from 'vs/platform/files/common/files'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; const mockExtensionGallery: IGalleryExtension[] = [ aGalleryExtension('MockExtension1', { diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 5a38560825..180a54d040 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -41,7 +41,7 @@ import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browse import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { ExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index cf570b3efd..58342a72f2 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -19,7 +19,7 @@ import { Button } from 'vs/base/browser/ui/button/button'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; export interface IFeedback { diff --git a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts index 9896eeccc5..7a43e34344 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedbackStatusbarItem.ts @@ -7,7 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { FeedbackDropdown, IFeedback, IFeedbackDelegate } from 'vs/workbench/contrib/feedback/browser/feedback'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStatusbarService, StatusbarAlignment, IStatusbarEntry, IStatusbarEntryAccessor } from 'vs/platform/statusbar/common/statusbar'; import { localize } from 'vs/nls'; diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 2a038c53ac..096f0031d7 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -10,11 +10,11 @@ import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTI import { SyncActionDescriptor, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { openWindowCommand, REVEAL_IN_OS_COMMAND_ID, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, REVEAL_IN_OS_LABEL, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand } from 'vs/workbench/contrib/files/browser/fileCommands'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { isWindows, isMacintosh, isWeb } from 'vs/base/common/platform'; +import { isMacintosh } from 'vs/base/common/platform'; import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext } from 'vs/workbench/contrib/files/common/files'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; @@ -23,9 +23,9 @@ 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, IsWebContext, RemoteFileDialogContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; +import { SupportsWorkspacesContext, IsWebContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { OpenFileFolderAction, OpenLocalFileFolderCommand, OpenFileAction, OpenFolderAction, OpenLocalFileCommand, OpenLocalFolderCommand, OpenWorkspaceAction, SaveLocalFileCommand } from 'vs/workbench/browser/actions/workspaceActions'; +import { OpenFileFolderAction, OpenFileAction, OpenFolderAction, OpenWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ActiveEditorIsSaveableContext } from 'vs/workbench/common/editor'; import { SidebarFocusContext } from 'vs/workbench/common/viewlet'; import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; @@ -48,57 +48,17 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(CompareWithClipboardAc registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleAutoSaveAction, ToggleAutoSaveAction.ID, ToggleAutoSaveAction.LABEL), 'File: Toggle Auto Save', category.value); -const fileCategory = nls.localize('file', "File"); +const workspacesCategory = nls.localize('workspaces', "Workspaces"); +registry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory); +const fileCategory = nls.localize('file', "File"); if (isMacintosh) { registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); - if (!isWeb) { - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: OpenLocalFileFolderCommand.ID, - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KEY_O, - when: RemoteFileDialogContext, - description: { description: OpenLocalFileFolderCommand.LABEL, args: [] }, - handler: OpenLocalFileFolderCommand.handler() - }); - } } 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); - if (!isWeb) { - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: OpenLocalFileCommand.ID, - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyCode.KEY_O, - when: RemoteFileDialogContext, - description: { description: OpenLocalFileCommand.LABEL, args: [] }, - handler: OpenLocalFileCommand.handler() - }); - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: OpenLocalFolderCommand.ID, - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O), - when: RemoteFileDialogContext, - description: { description: OpenLocalFolderCommand.LABEL, args: [] }, - handler: OpenLocalFolderCommand.handler() - }); - } } -if (!isWeb) { - KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: SaveLocalFileCommand.ID, - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S, - when: RemoteFileDialogContext, - description: { description: SaveLocalFileCommand.LABEL, args: [] }, - handler: SaveLocalFileCommand.handler() - }); -} - -const workspacesCategory = nls.localize('workspaces', "Workspaces"); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory, SupportsWorkspacesContext); - // Commands CommandsRegistry.registerCommand('_files.windowOpen', openWindowCommand); CommandsRegistry.registerCommand('_files.newWindow', newWindowCommand); @@ -212,11 +172,9 @@ const copyRelativePathCommand = { // Editor Title Context Menu appendEditorTitleContextMenuItem(COPY_PATH_COMMAND_ID, copyPathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste'); appendEditorTitleContextMenuItem(COPY_RELATIVE_PATH_COMMAND_ID, copyRelativePathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste'); -appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ResourceContextKey.Scheme.isEqualTo(Schemas.file)); -appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.isEqualTo(Schemas.userData))); appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Side Bar"), ResourceContextKey.IsFileSystemResource); -function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpr | undefined, group?: string): void { +export function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpr | undefined, group?: string): void { // Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { @@ -252,7 +210,7 @@ function appendSaveConflictEditorTitleAction(id: string, title: string, iconLoca // Menu registration - command palette -function appendToCommandPalette(id: string, title: ILocalizedString, category: ILocalizedString, when?: ContextKeyExpr): void { +export function appendToCommandPalette(id: string, title: ILocalizedString, category: ILocalizedString, when?: ContextKeyExpr): void { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id, @@ -272,7 +230,6 @@ appendToCommandPalette(SAVE_ALL_IN_GROUP_COMMAND_ID, { value: nls.localize('save appendToCommandPalette(SAVE_FILES_COMMAND_ID, { value: nls.localize('saveFiles', "Save All Files"), original: 'Save All Files' }, category); appendToCommandPalette(REVERT_FILE_COMMAND_ID, { value: nls.localize('revert', "Revert File"), original: 'Revert File' }, category); appendToCommandPalette(COMPARE_WITH_SAVED_COMMAND_ID, { value: nls.localize('compareActiveWithSaved', "Compare Active File with Saved"), original: 'Compare Active File with Saved' }, category); -appendToCommandPalette(REVEAL_IN_OS_COMMAND_ID, { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, category); appendToCommandPalette(SAVE_FILE_AS_COMMAND_ID, { value: SAVE_FILE_AS_LABEL, original: 'Save As...' }, category); appendToCommandPalette(CLOSE_EDITOR_COMMAND_ID, { value: nls.localize('closeEditor', "Close Editor"), original: 'Close Editor' }, { value: nls.localize('view', "View"), original: 'View' }); appendToCommandPalette(NEW_FILE_COMMAND_ID, { value: NEW_FILE_LABEL, original: 'New File' }, category, WorkspaceFolderCountContext.notEqualsTo('0')); @@ -292,17 +249,6 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { when: ResourceContextKey.IsFileSystemResource }); -const revealInOsCommand = { - id: REVEAL_IN_OS_COMMAND_ID, - title: isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder") -}; -MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { - group: 'navigation', - order: 20, - command: revealInOsCommand, - when: ResourceContextKey.IsFileSystemResource -}); - MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: '1_cutcopypaste', order: 10, @@ -470,13 +416,6 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource) }); -MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { - group: 'navigation', - order: 20, - command: revealInOsCommand, - when: ResourceContextKey.Scheme.isEqualTo(Schemas.file) -}); - MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { group: '3_compare', order: 20, @@ -570,7 +509,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { id: REMOVE_ROOT_FOLDER_COMMAND_ID, title: REMOVE_ROOT_FOLDER_LABEL }, - when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext) + when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext, SupportsWorkspacesContext) }); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { @@ -714,8 +653,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { id: OpenWorkspaceAction.ID, title: nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...") }, - order: 3, - when: SupportsWorkspacesContext + order: 3 }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index d02903686e..9ae1d100c1 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -26,9 +26,8 @@ import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; -import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { isWindows } from 'vs/base/common/platform'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { sequence } from 'vs/base/common/async'; import { getResourceForCommand, getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { getMultiSelectedEditorContexts } from 'vs/workbench/browser/parts/editor/editorCommands'; @@ -50,8 +49,6 @@ import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/q // Commands -export const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; -export const REVEAL_IN_OS_LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); export const REVEAL_IN_EXPLORER_COMMAND_ID = 'revealInExplorer'; export const REVERT_FILE_COMMAND_ID = 'workbench.action.files.revert'; export const OPEN_TO_SIDE_COMMAND_ID = 'explorer.openToSide'; @@ -437,44 +434,6 @@ CommandsRegistry.registerCommand({ } }); -function revealResourcesInOS(resources: URI[], windowsService: IWindowsService, notificationService: INotificationService, workspaceContextService: IWorkspaceContextService): void { - if (resources.length) { - sequence(resources.map(r => () => windowsService.showItemInFolder(r.scheme === Schemas.userData ? r.with({ scheme: Schemas.file }) : r))); - } else if (workspaceContextService.getWorkspace().folders.length) { - windowsService.showItemInFolder(workspaceContextService.getWorkspace().folders[0].uri); - } else { - notificationService.info(nls.localize('openFileToReveal', "Open a file first to reveal")); - } -} - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: REVEAL_IN_OS_COMMAND_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: EditorContextKeys.focus.toNegated(), - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R, - win: { - primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_R - }, - handler: (accessor: ServicesAccessor, resource: URI | object) => { - const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); - revealResourcesInOS(resources, accessor.get(IWindowsService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_R), - id: 'workbench.action.files.revealActiveFileInWindows', - handler: (accessor: ServicesAccessor) => { - const editorService = accessor.get(IEditorService); - const activeInput = editorService.activeEditor; - const resource = activeInput ? activeInput.getResource() : null; - const resources = resource ? [resource] : []; - revealResourcesInOS(resources, accessor.get(IWindowsService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); - } -}); - async function resourcesToClipboard(resources: URI[], relative: boolean, clipboardService: IClipboardService, notificationService: INotificationService, labelService: ILabelService): Promise { if (resources.length) { const lineDelimiter = isWindows ? '\r\n' : '\n'; diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 4e8b9805f4..a1d3b67226 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -381,13 +381,16 @@ export class OpenEditorsView extends ViewletPanel { private focusActiveEditor(): void { if (this.list.length && this.editorGroupService.activeGroup) { const index = this.getIndex(this.editorGroupService.activeGroup, this.editorGroupService.activeGroup.activeEditor); - this.list.setFocus([index]); - this.list.setSelection([index]); - this.list.reveal(index); - } else { - this.list.setFocus([]); - this.list.setSelection([]); + if (index >= 0) { + this.list.setFocus([index]); + this.list.setSelection([index]); + this.list.reveal(index); + return; + } } + + this.list.setFocus([]); + this.list.setSelection([]); } private onConfigurationChange(event: IConfigurationChangeEvent): void { diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 23e31480b1..b68ff49bc4 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -111,7 +111,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { setPreferredEncoding(encoding: string): void { this.preferredEncoding = encoding; - this.forceOpenAs = ForceOpenAs.Text; // encoding is a good hint to open the file as text + this.setForceOpenAsText(); // encoding is a good hint to open the file as text } getPreferredMode(): string | undefined { @@ -129,7 +129,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { setPreferredMode(mode: string): void { this.preferredMode = mode; - this.forceOpenAs = ForceOpenAs.Text; // mode is a good hint to open the file as text + this.setForceOpenAsText(); // mode is a good hint to open the file as text } setForceOpenAsText(): void { @@ -165,21 +165,15 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { } getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string { - let description: string; switch (verbosity) { case Verbosity.SHORT: - description = this.shortDescription; - break; + return this.shortDescription; case Verbosity.LONG: - description = this.longDescription; - break; + return this.longDescription; case Verbosity.MEDIUM: default: - description = this.mediumDescription; - break; + return this.mediumDescription; } - - return description; } @memoize @@ -198,24 +192,16 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { } getTitle(verbosity: Verbosity): string { - let title: string; switch (verbosity) { case Verbosity.SHORT: - title = this.shortTitle; // already decorated by getName() - break; + return this.shortTitle; default: case Verbosity.MEDIUM: - title = this.mediumTitle; - title = this.decorateLabel(title); - break; + return this.decorateLabel(this.mediumTitle); case Verbosity.LONG: - title = this.longTitle; - title = this.decorateLabel(title); - break; + return this.decorateLabel(this.longTitle); } - - return title; } private decorateLabel(label: string): string { diff --git a/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts new file mode 100644 index 0000000000..b2a0cbfd4f --- /dev/null +++ b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { Schemas } from 'vs/base/common/network'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; +import { IListService } from 'vs/platform/list/browser/listService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { revealResourcesInOS } from 'vs/workbench/contrib/files/electron-browser/fileCommands'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { ResourceContextKey } from 'vs/workbench/common/resources'; +import { appendToCommandPalette, appendEditorTitleContextMenuItem } from 'vs/workbench/contrib/files/browser/fileActions.contribution'; + +const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; +const REVEAL_IN_OS_LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: REVEAL_IN_OS_COMMAND_ID, + weight: KeybindingWeight.WorkbenchContrib, + when: EditorContextKeys.focus.toNegated(), + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R, + win: { + primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_R + }, + handler: (accessor: ServicesAccessor, resource: URI | object) => { + const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); + revealResourcesInOS(resources, accessor.get(IElectronService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_R), + id: 'workbench.action.files.revealActiveFileInWindows', + handler: (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const activeInput = editorService.activeEditor; + const resource = activeInput ? activeInput.getResource() : null; + const resources = resource ? [resource] : []; + revealResourcesInOS(resources, accessor.get(IElectronService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); + } +}); + +appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ResourceContextKey.Scheme.isEqualTo(Schemas.file)); + +// Menu registration - open editors + +const revealInOsCommand = { + id: REVEAL_IN_OS_COMMAND_ID, + title: isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder") +}; +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: 'navigation', + order: 20, + command: revealInOsCommand, + when: ResourceContextKey.IsFileSystemResource +}); + +// Menu registration - explorer + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: 'navigation', + order: 20, + command: revealInOsCommand, + when: ResourceContextKey.Scheme.isEqualTo(Schemas.file) +}); + +// Command Palette + +const category = { value: nls.localize('filesCategory', "File"), original: 'File' }; +appendToCommandPalette(REVEAL_IN_OS_COMMAND_ID, { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, category); diff --git a/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts b/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts new file mode 100644 index 0000000000..461f83358f --- /dev/null +++ b/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { sequence } from 'vs/base/common/async'; +import { Schemas } from 'vs/base/common/network'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IElectronService } from 'vs/platform/electron/node/electron'; + +// Commands + +export function revealResourcesInOS(resources: URI[], electronService: IElectronService, notificationService: INotificationService, workspaceContextService: IWorkspaceContextService): void { + if (resources.length) { + sequence(resources.map(r => async () => { + if (r.scheme === Schemas.file) { + electronService.showItemInFolder(r.fsPath); + } + })); + } else if (workspaceContextService.getWorkspace().folders.length) { + const uri = workspaceContextService.getWorkspace().folders[0].uri; + if (uri.scheme === Schemas.file) { + electronService.showItemInFolder(uri.fsPath); + } + } else { + notificationService.info(nls.localize('openFileToReveal', "Open a file first to reveal")); + } +} diff --git a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts index acfebcc568..0dd6b7434b 100644 --- a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts @@ -5,7 +5,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as nls from 'vs/nls'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { SyncActionDescriptor, ICommandAction, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { ReportPerformanceIssueUsingReporterAction, OpenProcessExplorer } from 'vs/workbench/contrib/issue/electron-browser/issueActions'; diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index 36b15836b9..cc515c8994 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -80,7 +80,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo [{ label: updateAndRestart ? localize('yes', "Yes") : localize('restart now', "Restart Now"), run: () => { - const updatePromise = updateAndRestart ? this.jsonEditingService.write(this.environmentService.localeResource, { key: 'locale', value: locale }, true) : Promise.resolve(undefined); + const updatePromise = updateAndRestart ? this.jsonEditingService.write(this.environmentService.localeResource, [{ key: 'locale', value: locale }], true) : Promise.resolve(undefined); updatePromise.then(() => this.windowsService.relaunch({}), e => this.notificationService.error(e)); } }], diff --git a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts index 7192674a43..80052645f8 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts @@ -65,7 +65,7 @@ export class ConfigureLocaleAction extends Action { } if (selectedLanguage) { - await this.jsonEditingService.write(this.environmentService.localeResource, { key: 'locale', value: selectedLanguage.label }, true); + await this.jsonEditingService.write(this.environmentService.localeResource, [{ key: 'locale', value: selectedLanguage.label }], true); const restart = await this.dialogService.confirm({ type: 'info', message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."), diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 56c6677e9d..cb372f113d 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -8,7 +8,7 @@ import { join } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { SetLogLevelAction, OpenLogsFolderAction, OpenWindowSessionLogFileAction } from 'vs/workbench/contrib/logs/common/logsActions'; +import { SetLogLevelAction, OpenWindowSessionLogFileAction } from 'vs/workbench/contrib/logs/common/logsActions'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -64,10 +64,6 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { }; registerTelemetryChannel(this.logService.getLevel()); this.logService.onDidChangeLogLevel(registerTelemetryChannel); - - const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); - const devCategory = nls.localize('developer', "Developer"); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); } private async registerLogChannel(id: string, label: string, file: URI): Promise { diff --git a/src/vs/workbench/contrib/logs/common/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts index f9e5a6b122..7403a4c861 100644 --- a/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -5,9 +5,6 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { join } from 'vs/base/common/path'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; import { ILogService, LogLevel, DEFAULT_LOG_LEVEL } from 'vs/platform/log/common/log'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { URI } from 'vs/base/common/uri'; @@ -16,23 +13,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { dirname, basename, isEqual } from 'vs/base/common/resources'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export class OpenLogsFolderAction extends Action { - - static ID = 'workbench.action.openLogsFolder'; - static LABEL = nls.localize('openLogsFolder', "Open Logs Folder"); - - constructor(id: string, label: string, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IWindowsService private readonly windowsService: IWindowsService, - ) { - super(id, label); - } - - run(): Promise { - return this.windowsService.showItemInFolder(URI.file(join(this.environmentService.logsPath, 'main.log'))); - } -} - export class SetLogLevelAction extends Action { static ID = 'workbench.action.setLogLevel'; diff --git a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts new file mode 100644 index 0000000000..ccd3051cd3 --- /dev/null +++ b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts @@ -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. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { OpenLogsFolderAction } from 'vs/workbench/contrib/logs/electron-browser/logsActions'; + +const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); +const devCategory = nls.localize('developer', "Developer"); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); diff --git a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts new file mode 100644 index 0000000000..0f16a556c8 --- /dev/null +++ b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { join } from 'vs/base/common/path'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { URI } from 'vs/base/common/uri'; +import { IElectronService } from 'vs/platform/electron/node/electron'; + +export class OpenLogsFolderAction extends Action { + + static ID = 'workbench.action.openLogsFolder'; + static LABEL = nls.localize('openLogsFolder', "Open Logs Folder"); + + constructor(id: string, label: string, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IElectronService private readonly electronService: IElectronService, + ) { + super(id, label); + } + + run(): Promise { + return this.electronService.showItemInFolder(URI.file(join(this.environmentService.logsPath, 'main.log')).fsPath); + } +} diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index 9f99d73b74..1a1ccf254c 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -117,7 +117,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.rangeHighlightDecorations = this._register(this.instantiationService.createInstance(RangeHighlightDecorations)); // actions - this.collapseAllAction = this._register(new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, async () => this.collapseAll())); + this.collapseAllAction = this._register(new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action codicon-collapse-all', true, async () => this.collapseAll())); this.filterAction = this._register(this.instantiationService.createInstance(MarkersFilterAction, { filterText: this.panelState['filter'] || '', filterHistory: this.panelState['filterHistory'] || [], useFilesExclude: !!this.panelState['useFilesExclude'] })); } diff --git a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts index 13b2fdbbb7..9e58ba1ca7 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts @@ -213,7 +213,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private createFilesExcludeCheckbox(container: HTMLElement): void { const filesExcludeFilter = this._register(new Checkbox({ - actionClassName: 'markers-panel-filter-filesExclude', + actionClassName: 'codicon codicon-exclude', title: this.action.useFilesExclude ? Messages.MARKERS_PANEL_ACTION_TOOLTIP_DO_NOT_USE_FILES_EXCLUDE : Messages.MARKERS_PANEL_ACTION_TOOLTIP_USE_FILES_EXCLUDE, isChecked: this.action.useFilesExclude })); diff --git a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-dark.svg b/src/vs/workbench/contrib/markers/browser/media/exclude-settings-dark.svg deleted file mode 100644 index 0b1694dc2f..0000000000 --- a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-hc.svg b/src/vs/workbench/contrib/markers/browser/media/exclude-settings-hc.svg deleted file mode 100644 index ba88235419..0000000000 --- a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-light.svg b/src/vs/workbench/contrib/markers/browser/media/exclude-settings-light.svg deleted file mode 100644 index 114ec3f0fe..0000000000 --- a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css index 286d893381..b7c60be8d8 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -56,19 +56,6 @@ display: none; } -.vs .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude { - background: url('exclude-settings-light.svg') center center no-repeat; -} - -.vs-dark .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude, -.hc-black .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude { - background: url('exclude-settings-dark.svg') center center no-repeat; -} - -.hc-black .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude { - background: url('exclude-settings-hc.svg') center center no-repeat; -} - .markers-panel .markers-panel-container { height: 100%; } diff --git a/src/vs/workbench/contrib/output/browser/media/clear-dark.svg b/src/vs/workbench/contrib/output/browser/media/clear-dark.svg deleted file mode 100644 index 04d64ab41c..0000000000 --- a/src/vs/workbench/contrib/output/browser/media/clear-dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/output/browser/media/clear-hc.svg b/src/vs/workbench/contrib/output/browser/media/clear-hc.svg deleted file mode 100644 index 44a41edd3b..0000000000 --- a/src/vs/workbench/contrib/output/browser/media/clear-hc.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/output/browser/media/clear-light.svg b/src/vs/workbench/contrib/output/browser/media/clear-light.svg deleted file mode 100644 index f6a51c856f..0000000000 --- a/src/vs/workbench/contrib/output/browser/media/clear-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/output/browser/media/locked-dark.svg b/src/vs/workbench/contrib/output/browser/media/locked-dark.svg deleted file mode 100644 index ebdc1c0078..0000000000 --- a/src/vs/workbench/contrib/output/browser/media/locked-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/locked-hc.svg b/src/vs/workbench/contrib/output/browser/media/locked-hc.svg deleted file mode 100644 index 350dc41771..0000000000 --- a/src/vs/workbench/contrib/output/browser/media/locked-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/locked-light.svg b/src/vs/workbench/contrib/output/browser/media/locked-light.svg deleted file mode 100644 index 03b0351368..0000000000 --- a/src/vs/workbench/contrib/output/browser/media/locked-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/open-file-dark.svg b/src/vs/workbench/contrib/output/browser/media/open-file-dark.svg deleted file mode 100644 index ed302ae139..0000000000 --- a/src/vs/workbench/contrib/output/browser/media/open-file-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/open-file-hc.svg b/src/vs/workbench/contrib/output/browser/media/open-file-hc.svg deleted file mode 100644 index bba63494e6..0000000000 --- a/src/vs/workbench/contrib/output/browser/media/open-file-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/open-file-light.svg b/src/vs/workbench/contrib/output/browser/media/open-file-light.svg deleted file mode 100644 index 392a840c5e..0000000000 --- a/src/vs/workbench/contrib/output/browser/media/open-file-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/output.css b/src/vs/workbench/contrib/output/browser/media/output.css deleted file mode 100644 index f61c5a1541..0000000000 --- a/src/vs/workbench/contrib/output/browser/media/output.css +++ /dev/null @@ -1,52 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench .output-action.clear-output { - background: url('clear-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .output-action.clear-output { - background: url('clear-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .output-action.clear-output { - background: url('clear-hc.svg') center center no-repeat; -} - -.monaco-workbench .output-action.output-scroll-lock { - background: url('locked-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .output-action.output-scroll-lock { - background: url('locked-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .output-action.output-scroll-lock { - background: url('locked-hc.svg') center center no-repeat; -} - -.monaco-workbench .output-action.output-scroll-unlock { - background: url('unlocked-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .output-action.output-scroll-unlock { - background: url('unlocked-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .output-action.output-scroll-unlock { - background: url('unlocked-hc.svg') center center no-repeat; -} - -.monaco-workbench .output-action.open-log-file { - background: url('open-file-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .output-action.open-log-file { - background: url('open-file-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .output-action.open-log-file { - background: url('open-file-hc.svg') center center no-repeat; -} diff --git a/src/vs/workbench/contrib/output/browser/media/unlocked-dark.svg b/src/vs/workbench/contrib/output/browser/media/unlocked-dark.svg deleted file mode 100644 index 3b7947f975..0000000000 --- a/src/vs/workbench/contrib/output/browser/media/unlocked-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/unlocked-hc.svg b/src/vs/workbench/contrib/output/browser/media/unlocked-hc.svg deleted file mode 100644 index 100cfc1385..0000000000 --- a/src/vs/workbench/contrib/output/browser/media/unlocked-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/unlocked-light.svg b/src/vs/workbench/contrib/output/browser/media/unlocked-light.svg deleted file mode 100644 index 0c3cb4b93b..0000000000 --- a/src/vs/workbench/contrib/output/browser/media/unlocked-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index b63fe7262a..2099edf33b 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -45,7 +45,7 @@ export class ClearOutputAction extends Action { id: string, label: string, @IOutputService private readonly outputService: IOutputService ) { - super(id, label, 'output-action clear-output'); + super(id, label, 'output-action codicon-clear-all'); } public run(): Promise { @@ -67,7 +67,7 @@ export class ToggleOrSetOutputScrollLockAction extends Action { public static readonly LABEL = nls.localize({ key: 'toggleOutputScrollLock', comment: ['Turn on / off automatic output scrolling'] }, "Toggle Output Scroll Lock"); constructor(id: string, label: string, @IOutputService private readonly outputService: IOutputService) { - super(id, label, 'output-action output-scroll-unlock'); + super(id, label, 'output-action codicon-unlock'); this._register(this.outputService.onActiveOutputChannel(channel => { const activeChannel = this.outputService.getActiveChannel(); if (activeChannel) { @@ -94,10 +94,10 @@ export class ToggleOrSetOutputScrollLockAction extends Action { private setClassAndLabel(locked: boolean) { if (locked) { - this.class = 'output-action output-scroll-lock'; + this.class = 'output-action codicon-lock'; this.label = nls.localize('outputScrollOn', "Turn Auto Scrolling On"); } else { - this.class = 'output-action output-scroll-unlock'; + this.class = 'output-action codicon-unlock'; this.label = nls.localize('outputScrollOff', "Turn Auto Scrolling Off"); } } @@ -185,7 +185,7 @@ export class OpenLogOutputFile extends Action { @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { - super(OpenLogOutputFile.ID, OpenLogOutputFile.LABEL, 'output-action open-log-file'); + super(OpenLogOutputFile.ID, OpenLogOutputFile.LABEL, 'output-action codicon-go-to-file'); this._register(this.outputService.onActiveOutputChannel(this.update, this)); this.update(); } diff --git a/src/vs/workbench/contrib/output/browser/outputPanel.ts b/src/vs/workbench/contrib/output/browser/outputPanel.ts index ab5723a513..92907b8e68 100644 --- a/src/vs/workbench/contrib/output/browser/outputPanel.ts +++ b/src/vs/workbench/contrib/output/browser/outputPanel.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/output'; import * as nls from 'vs/nls'; import { Action, IAction } from 'vs/base/common/actions'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; diff --git a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index f1c18dab7a..8039bd2521 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -20,8 +20,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { writeTransientState } from 'vs/workbench/contrib/codeEditor/browser/toggleWordWrap'; import { mergeSort } from 'vs/base/common/arrays'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/common/product'; export class PerfviewContrib { @@ -127,7 +126,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { private _addSummary(md: MarkdownBuilder, metrics: IStartupMetrics): void { md.heading(2, 'System Info'); - md.li(`${product.nameShort}: ${pkg.version} (${product.commit || '0000000'})`); + md.li(`${product.nameShort}: ${product.version} (${product.commit || '0000000'})`); md.li(`OS: ${metrics.platform}(${metrics.release})`); if (metrics.cpus) { md.li(`CPUs: ${metrics.cpus.model}(${metrics.cpus.count} x ${metrics.cpus.speed})`); diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts index 83155827dc..94e2657659 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts @@ -10,7 +10,7 @@ import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { PerfviewInput } from 'vs/workbench/contrib/performance/electron-browser/perfviewEditor'; @@ -18,6 +18,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { URI } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IElectronService } from 'vs/platform/electron/node/electron'; export class StartupProfiler implements IWorkbenchContribution { @@ -29,7 +30,8 @@ export class StartupProfiler implements IWorkbenchContribution { @IClipboardService private readonly _clipboardService: IClipboardService, @ILifecycleService lifecycleService: ILifecycleService, @IExtensionService extensionService: IExtensionService, - @IOpenerService private readonly _openerService: IOpenerService + @IOpenerService private readonly _openerService: IOpenerService, + @IElectronService private readonly _electronService: IElectronService ) { // wait for everything to be ready Promise.all([ @@ -81,7 +83,7 @@ export class StartupProfiler implements IWorkbenchContribution { }).then(res => { if (res.confirmed) { Promise.all([ - this._windowsService.showItemInFolder(URI.file(join(dir, files[0]))), + this._electronService.showItemInFolder(URI.file(join(dir, files[0])).fsPath), this._createPerfIssue(files) ]).then(() => { // keep window stable until restart is selected diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts index 2a595ecc95..d8f53e1242 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts @@ -10,7 +10,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILifecycleService, StartupKind } from 'vs/platform/lifecycle/common/lifecycle'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService } from 'vs/platform/update/common/update'; import { IWindowsService } from 'vs/platform/windows/common/windows'; @@ -21,6 +21,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { didUseCachedData, ITimerService } from 'vs/workbench/services/timer/electron-browser/timerService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { getEntries } from 'vs/base/common/performance'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class StartupTimings implements IWorkbenchContribution { @@ -34,6 +35,7 @@ export class StartupTimings implements IWorkbenchContribution { @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IUpdateService private readonly _updateService: IUpdateService, @IEnvironmentService private readonly _envService: IEnvironmentService, + @IHostService private readonly _hostService: IHostService ) { // this._report().catch(onUnexpectedError); @@ -91,7 +93,7 @@ export class StartupTimings implements IWorkbenchContribution { if (this._lifecycleService.startupKind !== StartupKind.NewWindow) { return false; } - if (await this._windowsService.getWindowCount() !== 1) { + if (await this._hostService.windowCount !== 1) { return false; } const activeViewlet = this._viewletService.getActiveViewlet(); diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts index 26d4db2fe4..115dbf5a6e 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts @@ -24,7 +24,7 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { nullRange } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStringDictionary } from 'vs/base/common/collections'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export interface IEndpointDetails { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts index fa1afa47b1..833b9a17e1 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts @@ -195,6 +195,11 @@ export const tocData: ITOCEntry = { id: 'application/telemetry', label: localize('telemetry', "Telemetry"), settings: ['telemetry.*'] + }, + { + id: 'application/sync', + label: localize('sync', "Sync"), + settings: ['userConfiguration.*'] } ] } @@ -218,3 +223,5 @@ export const knownAcronyms = new Set(); export const knownTermMappings = new Map(); knownTermMappings.set('power shell', 'PowerShell'); knownTermMappings.set('powershell', 'PowerShell'); +knownTermMappings.set('javascript', 'JavaScript'); +knownTermMappings.set('typescript', 'TypeScript'); diff --git a/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts index 019ecca7bb..c86df42212 100644 --- a/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts @@ -171,7 +171,7 @@ export class WorkspaceChangeExtHostRelauncher extends Disposable implements IWor if (environmentService.configuration.remoteAuthority) { windowService.reloadWindow(); // TODO@aeschli, workaround - } else { + } else if (isNative) { extensionService.restartExtensionHost(); } }, 10)); diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 8da04bcf76..a6ca519382 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -38,6 +38,15 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { IProgress, IProgressStep, IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { ReconnectionWaitEvent, PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; +import Severity from 'vs/base/common/severity'; +import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; interface HelpInformation { extensionDescription: IExtensionDescription; @@ -126,7 +135,7 @@ class HelpDataSource implements IAsyncDataSource { } getChildren(element: any) { - if (element instanceof HelpModel) { + if (element instanceof HelpModel && element.items) { return element.items; } @@ -201,7 +210,7 @@ class IssueReporterItem implements IHelpItem { } class HelpModel { - items: IHelpItem[]; + items: IHelpItem[] | undefined; constructor( viewModel: IViewModel, @@ -445,3 +454,189 @@ Registry.as(WorkbenchActionExtensions.WorkbenchActions 'View: Show Remote Explorer', nls.localize('view', "View") ); + + +class ProgressReporter { + private _currentProgress: IProgress | null = null; + private lastReport: string | null = null; + + constructor(currentProgress: IProgress | null) { + this._currentProgress = currentProgress; + } + + set currentProgress(progress: IProgress) { + this._currentProgress = progress; + } + + report(message?: string) { + if (message) { + this.lastReport = message; + } + + if (this.lastReport && this._currentProgress) { + this._currentProgress.report({ message: this.lastReport }); + } + } +} + +class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { + constructor( + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IProgressService progressService: IProgressService, + @IDialogService dialogService: IDialogService, + @ICommandService commandService: ICommandService, + @IContextKeyService contextKeyService: IContextKeyService + ) { + const connection = remoteAgentService.getConnection(); + if (connection) { + let currentProgressPromiseResolve: (() => void) | null = null; + let progressReporter: ProgressReporter | null = null; + let lastLocation: ProgressLocation | null = null; + let currentTimer: ReconnectionTimer | null = null; + let reconnectWaitEvent: ReconnectionWaitEvent | null = null; + let disposableListener: IDisposable | null = null; + + function showProgress(location: ProgressLocation, buttons?: string[]) { + if (currentProgressPromiseResolve) { + currentProgressPromiseResolve(); + } + + const promise = new Promise((resolve) => currentProgressPromiseResolve = resolve); + lastLocation = location; + + if (location === ProgressLocation.Dialog) { + // Show dialog + progressService!.withProgress( + { location: ProgressLocation.Dialog, buttons }, + (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, + (choice?) => { + // Handle choice from dialog + if (choice === 0 && buttons && reconnectWaitEvent) { + reconnectWaitEvent.skipWait(); + } else { + showProgress(ProgressLocation.Notification, buttons); + } + + progressReporter!.report(); + }); + } else { + // Show notification + progressService!.withProgress( + { location: ProgressLocation.Notification, buttons }, + (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, + (choice?) => { + // Handle choice from notification + if (choice === 0 && buttons && reconnectWaitEvent) { + reconnectWaitEvent.skipWait(); + } else { + hideProgress(); + } + }); + } + } + + function hideProgress() { + if (currentProgressPromiseResolve) { + currentProgressPromiseResolve(); + } + + currentProgressPromiseResolve = null; + } + + connection.onDidStateChange((e) => { + if (currentTimer) { + currentTimer.dispose(); + currentTimer = null; + } + + if (disposableListener) { + disposableListener.dispose(); + disposableListener = null; + } + switch (e.type) { + case PersistentConnectionEventType.ConnectionLost: + if (!currentProgressPromiseResolve) { + progressReporter = new ProgressReporter(null); + showProgress(ProgressLocation.Dialog, [nls.localize('reconnectNow', "Reconnect Now")]); + } + + progressReporter!.report(nls.localize('connectionLost', "Connection Lost")); + break; + case PersistentConnectionEventType.ReconnectionWait: + hideProgress(); + reconnectWaitEvent = e; + showProgress(lastLocation || ProgressLocation.Notification, [nls.localize('reconnectNow', "Reconnect Now")]); + currentTimer = new ReconnectionTimer(progressReporter!, Date.now() + 1000 * e.durationSeconds); + break; + case PersistentConnectionEventType.ReconnectionRunning: + hideProgress(); + showProgress(lastLocation || ProgressLocation.Notification); + progressReporter!.report(nls.localize('reconnectionRunning', "Attempting to reconnect...")); + + // Register to listen for quick input is opened + disposableListener = contextKeyService.onDidChangeContext((contextKeyChangeEvent) => { + const reconnectInteraction = new Set(['inQuickOpen']); + if (contextKeyChangeEvent.affectsSome(reconnectInteraction)) { + // Need to move from dialog if being shown and user needs to type in a prompt + if (lastLocation === ProgressLocation.Dialog && progressReporter !== null) { + hideProgress(); + showProgress(ProgressLocation.Notification); + progressReporter.report(); + } + } + }); + + break; + case PersistentConnectionEventType.ReconnectionPermanentFailure: + hideProgress(); + progressReporter = null; + + dialogService.show(Severity.Error, nls.localize('reconnectionPermanentFailure', "Cannot reconnect. Please reload the window."), [nls.localize('reloadWindow', "Reload Window"), nls.localize('cancel', "Cancel")], { cancelId: 1 }).then(result => { + // Reload the window + if (result.choice === 0) { + commandService.executeCommand(ReloadWindowAction.ID); + } + }); + break; + case PersistentConnectionEventType.ConnectionGain: + hideProgress(); + progressReporter = null; + break; + } + }); + } + } +} + +class ReconnectionTimer implements IDisposable { + private readonly _progressReporter: ProgressReporter; + private readonly _completionTime: number; + private readonly _token: any; + + constructor(progressReporter: ProgressReporter, completionTime: number) { + this._progressReporter = progressReporter; + this._completionTime = completionTime; + this._token = setInterval(() => this._render(), 1000); + this._render(); + } + + public dispose(): void { + clearInterval(this._token); + } + + private _render() { + const remainingTimeMs = this._completionTime - Date.now(); + if (remainingTimeMs < 0) { + return; + } + const remainingTime = Math.ceil(remainingTimeMs / 1000); + if (remainingTime === 1) { + this._progressReporter.report(nls.localize('reconnectionWaitOne', "Attempting to reconnect in {0} second...", remainingTime)); + } else { + this._progressReporter.report(nls.localize('reconnectionWaitMany', "Attempting to reconnect in {0} seconds...", remainingTime)); + } + } +} + +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 5258b62995..15873391af 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -11,7 +11,7 @@ import { OperatingSystem, isWeb } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IRemoteAgentService, RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ILogService } from 'vs/platform/log/common/log'; -import { LogLevelSetterChannelClient } from 'vs/platform/log/common/logIpc'; +import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/contrib/output/common/output'; import { localize } from 'vs/nls'; import { joinPath } from 'vs/base/common/resources'; @@ -79,9 +79,9 @@ class RemoteChannelsContribution extends Disposable implements IWorkbenchContrib super(); const connection = remoteAgentService.getConnection(); if (connection) { - const logLevelClient = new LogLevelSetterChannelClient(connection.getChannel('loglevel')); - logLevelClient.setLevel(logService.getLevel()); - this._register(logService.onDidChangeLogLevel(level => logLevelClient.setLevel(level))); + const loggerClient = new LoggerChannelClient(connection.getChannel('logger')); + loggerClient.setLevel(logService.getLevel()); + this._register(logService.onDidChangeLogLevel(level => loggerClient.setLevel(level))); } } } diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 8838e40772..62bfc2f8bd 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -6,11 +6,12 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme'; - import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; - +import { Disposable } from 'vs/base/common/lifecycle'; +import { isMacintosh } from 'vs/base/common/platform'; +import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -23,22 +24,20 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { ILogService } from 'vs/platform/log/common/log'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc'; +import { DialogChannel } from 'vs/platform/dialogs/electron-browser/dialogIpc'; import { DownloadServiceChannel } from 'vs/platform/download/common/downloadIpc'; -import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; +import { LoggerChannel } from 'vs/platform/log/common/logIpc'; import { ipcRenderer as ipc } from 'electron'; import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IProgressService, IProgress, IProgressStep, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { PersistentConnectionEventType, ReconnectionWaitEvent } from 'vs/platform/remote/common/remoteAgentConnection'; +import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import Severity from 'vs/base/common/severity'; -import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { RemoteConnectionState, Deprecated_RemoteAuthorityContext } from 'vs/workbench/browser/contextkeys'; +import { RemoteConnectionState, Deprecated_RemoteAuthorityContext, RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; import { IDownloadService } from 'vs/platform/download/common/download'; +import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand } from 'vs/workbench/services/dialogs/browser/remoteFileDialog'; const WINDOW_ACTIONS_COMMAND_ID = 'remote.showActions'; const CLOSE_REMOTE_COMMAND_ID = 'remote.closeRemote'; @@ -228,7 +227,7 @@ class RemoteChannelsContribution implements IWorkbenchContribution { if (connection) { connection.registerChannel('dialog', new DialogChannel(dialogService)); connection.registerChannel('download', new DownloadServiceChannel(downloadService)); - connection.registerChannel('loglevel', new LogLevelSetterChannel(logService)); + connection.registerChannel('logger', new LoggerChannel(logService)); } } } @@ -261,29 +260,6 @@ class RemoteAgentDiagnosticListener implements IWorkbenchContribution { } } -class ProgressReporter { - private _currentProgress: IProgress | null = null; - private lastReport: string | null = null; - - constructor(currentProgress: IProgress | null) { - this._currentProgress = currentProgress; - } - - set currentProgress(progress: IProgress) { - this._currentProgress = progress; - } - - report(message?: string) { - if (message) { - this.lastReport = message; - } - - if (this.lastReport && this._currentProgress) { - this._currentProgress.report({ message: this.lastReport }); - } - } -} - class RemoteExtensionHostEnvironmentUpdater implements IWorkbenchContribution { constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, @@ -304,165 +280,6 @@ class RemoteExtensionHostEnvironmentUpdater implements IWorkbenchContribution { } } -class RemoteAgentConnectionStatusListener implements IWorkbenchContribution { - constructor( - @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IProgressService progressService: IProgressService, - @IDialogService dialogService: IDialogService, - @ICommandService commandService: ICommandService, - @IContextKeyService contextKeyService: IContextKeyService - ) { - const connection = remoteAgentService.getConnection(); - if (connection) { - let currentProgressPromiseResolve: (() => void) | null = null; - let progressReporter: ProgressReporter | null = null; - let lastLocation: ProgressLocation | null = null; - let currentTimer: ReconnectionTimer | null = null; - let reconnectWaitEvent: ReconnectionWaitEvent | null = null; - let disposableListener: IDisposable | null = null; - - function showProgress(location: ProgressLocation, buttons?: string[]) { - if (currentProgressPromiseResolve) { - currentProgressPromiseResolve(); - } - - const promise = new Promise((resolve) => currentProgressPromiseResolve = resolve); - lastLocation = location; - - if (location === ProgressLocation.Dialog) { - // Show dialog - progressService!.withProgress( - { location: ProgressLocation.Dialog, buttons }, - (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, - (choice?) => { - // Handle choice from dialog - if (choice === 0 && buttons && reconnectWaitEvent) { - reconnectWaitEvent.skipWait(); - } else { - showProgress(ProgressLocation.Notification, buttons); - } - - progressReporter!.report(); - }); - } else { - // Show notification - progressService!.withProgress( - { location: ProgressLocation.Notification, buttons }, - (progress) => { if (progressReporter) { progressReporter.currentProgress = progress; } return promise; }, - (choice?) => { - // Handle choice from notification - if (choice === 0 && buttons && reconnectWaitEvent) { - reconnectWaitEvent.skipWait(); - } else { - hideProgress(); - } - }); - } - } - - function hideProgress() { - if (currentProgressPromiseResolve) { - currentProgressPromiseResolve(); - } - - currentProgressPromiseResolve = null; - } - - connection.onDidStateChange((e) => { - if (currentTimer) { - currentTimer.dispose(); - currentTimer = null; - } - - if (disposableListener) { - disposableListener.dispose(); - disposableListener = null; - } - switch (e.type) { - case PersistentConnectionEventType.ConnectionLost: - if (!currentProgressPromiseResolve) { - progressReporter = new ProgressReporter(null); - showProgress(ProgressLocation.Dialog, [nls.localize('reconnectNow', "Reconnect Now")]); - } - - progressReporter!.report(nls.localize('connectionLost', "Connection Lost")); - break; - case PersistentConnectionEventType.ReconnectionWait: - hideProgress(); - reconnectWaitEvent = e; - showProgress(lastLocation || ProgressLocation.Notification, [nls.localize('reconnectNow', "Reconnect Now")]); - currentTimer = new ReconnectionTimer(progressReporter!, Date.now() + 1000 * e.durationSeconds); - break; - case PersistentConnectionEventType.ReconnectionRunning: - hideProgress(); - showProgress(lastLocation || ProgressLocation.Notification); - progressReporter!.report(nls.localize('reconnectionRunning', "Attempting to reconnect...")); - - // Register to listen for quick input is opened - disposableListener = contextKeyService.onDidChangeContext((contextKeyChangeEvent) => { - const reconnectInteraction = new Set(['inQuickOpen']); - if (contextKeyChangeEvent.affectsSome(reconnectInteraction)) { - // Need to move from dialog if being shown and user needs to type in a prompt - if (lastLocation === ProgressLocation.Dialog && progressReporter !== null) { - hideProgress(); - showProgress(ProgressLocation.Notification); - progressReporter.report(); - } - } - }); - - break; - case PersistentConnectionEventType.ReconnectionPermanentFailure: - hideProgress(); - progressReporter = null; - - dialogService.show(Severity.Error, nls.localize('reconnectionPermanentFailure', "Cannot reconnect. Please reload the window."), [nls.localize('reloadWindow', "Reload Window"), nls.localize('cancel', "Cancel")], { cancelId: 1 }).then(result => { - // Reload the window - if (result.choice === 0) { - commandService.executeCommand(ReloadWindowAction.ID); - } - }); - break; - case PersistentConnectionEventType.ConnectionGain: - hideProgress(); - progressReporter = null; - break; - } - }); - } - } -} - -class ReconnectionTimer implements IDisposable { - private readonly _progressReporter: ProgressReporter; - private readonly _completionTime: number; - private readonly _token: NodeJS.Timeout; - - constructor(progressReporter: ProgressReporter, completionTime: number) { - this._progressReporter = progressReporter; - this._completionTime = completionTime; - this._token = setInterval(() => this._render(), 1000); - this._render(); - } - - public dispose(): void { - clearInterval(this._token); - } - - private _render() { - const remainingTimeMs = this._completionTime - Date.now(); - if (remainingTimeMs < 0) { - return; - } - const remainingTime = Math.ceil(remainingTimeMs / 1000); - if (remainingTime === 1) { - this._progressReporter.report(nls.localize('reconnectionWaitOne', "Attempting to reconnect in {0} second...", remainingTime)); - } else { - this._progressReporter.report(nls.localize('reconnectionWaitMany', "Attempting to reconnect in {0} seconds...", remainingTime)); - } - } -} - class RemoteTelemetryEnablementUpdater extends Disposable implements IWorkbenchContribution { constructor( @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, @@ -523,7 +340,6 @@ class RemoteEmptyWorkbenchPresentation extends Disposable implements IWorkbenchC const workbenchContributionsRegistry = Registry.as(WorkbenchContributionsExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentDiagnosticListener, LifecyclePhase.Eventually); -workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteExtensionHostEnvironmentUpdater, LifecyclePhase.Eventually); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteWindowActiveIndicator, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteTelemetryEnablementUpdater, LifecyclePhase.Ready); @@ -558,3 +374,40 @@ Registry.as(ConfigurationExtensions.Configuration) } } }); + +if (isMacintosh) { + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: OpenLocalFileFolderCommand.ID, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KEY_O, + when: RemoteFileDialogContext, + description: { description: OpenLocalFileFolderCommand.LABEL, args: [] }, + handler: OpenLocalFileFolderCommand.handler() + }); +} else { + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: OpenLocalFileCommand.ID, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyCode.KEY_O, + when: RemoteFileDialogContext, + description: { description: OpenLocalFileCommand.LABEL, args: [] }, + handler: OpenLocalFileCommand.handler() + }); + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: OpenLocalFolderCommand.ID, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O), + when: RemoteFileDialogContext, + description: { description: OpenLocalFolderCommand.LABEL, args: [] }, + handler: OpenLocalFolderCommand.handler() + }); +} + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: SaveLocalFileCommand.ID, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S, + when: RemoteFileDialogContext, + description: { description: SaveLocalFileCommand.LABEL, args: [] }, + handler: SaveLocalFileCommand.handler() +}); diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index a7e2f0d36c..b125d79b91 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -86,7 +86,7 @@ height: 25px; } -.search-view .search-widget .replace-container .monaco-action-bar .action-item .icon { +.search-view .search-widget .replace-container .monaco-action-bar .action-item .codicon { background-repeat: no-repeat; width: 20px; height: 25px; diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 5bef9b769f..a98e4f2d9c 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -364,11 +364,12 @@ export class SearchWidget extends Widget { } })); - let controls = document.createElement('div'); + const controls = document.createElement('div'); controls.className = 'controls'; controls.style.display = 'block'; controls.appendChild(this._preserveCase.domNode); replaceBox.appendChild(controls); + this.replaceInput.paddingRight = this._preserveCase.width(); this._register(attachInputBoxStyler(this.replaceInput, this.themeService)); this.onkeydown(this.replaceInput.inputElement, (keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent)); diff --git a/src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts similarity index 87% rename from src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts rename to src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts index 832bef10b5..191e0e2777 100644 --- a/src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/contrib/surveys/browser/languageSurveys.contribution.ts @@ -10,14 +10,13 @@ import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions as import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import pkg from 'vs/platform/product/node/package'; -import product from 'vs/platform/product/node/product'; -import { ISurveyData } from 'vs/platform/product/common/product'; +import { ISurveyData, IProductService } from 'vs/platform/product/common/productService'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; +import { platform } from 'vs/base/common/process'; class LanguageSurvey { @@ -28,7 +27,8 @@ class LanguageSurvey { telemetryService: ITelemetryService, modelService: IModelService, textFileService: ITextFileService, - openerService: IOpenerService + openerService: IOpenerService, + productService: IProductService ) { const SESSION_COUNT_KEY = `${data.surveyId}.sessionCount`; const LAST_SESSION_DATE_KEY = `${data.surveyId}.lastSessionDate`; @@ -82,7 +82,7 @@ class LanguageSurvey { storageService.store(IS_CANDIDATE_KEY, isCandidate, StorageScope.GLOBAL); if (!isCandidate) { - storageService.store(SKIP_VERSION_KEY, pkg.version, StorageScope.GLOBAL); + storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL); return; } @@ -97,9 +97,9 @@ class LanguageSurvey { run: () => { telemetryService.publicLog(`${data.surveyId}.survey/takeShortSurvey`); telemetryService.getTelemetryInfo().then(info => { - openerService.open(URI.parse(`${data.surveyUrl}?o=${encodeURIComponent(process.platform)}&v=${encodeURIComponent(pkg.version)}&m=${encodeURIComponent(info.machineId)}`)); + openerService.open(URI.parse(`${data.surveyUrl}?o=${encodeURIComponent(platform)}&v=${encodeURIComponent(productService.version)}&m=${encodeURIComponent(info.machineId)}`)); storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); - storageService.store(SKIP_VERSION_KEY, pkg.version, StorageScope.GLOBAL); + storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL); }); } }, { @@ -114,7 +114,7 @@ class LanguageSurvey { run: () => { telemetryService.publicLog(`${data.surveyId}.survey/dontShowAgain`); storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); - storageService.store(SKIP_VERSION_KEY, pkg.version, StorageScope.GLOBAL); + storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL); } }], { sticky: true } @@ -130,15 +130,20 @@ class LanguageSurveysContribution implements IWorkbenchContribution { @ITelemetryService telemetryService: ITelemetryService, @IModelService modelService: IModelService, @ITextFileService textFileService: ITextFileService, - @IOpenerService openerService: IOpenerService + @IOpenerService openerService: IOpenerService, + @IProductService productService: IProductService ) { - product.surveys! + if (!productService.surveys) { + return; + } + + productService.surveys .filter(surveyData => surveyData.surveyId && surveyData.editCount && surveyData.languageId && surveyData.surveyUrl && surveyData.userProbability) - .map(surveyData => new LanguageSurvey(surveyData, storageService, notificationService, telemetryService, modelService, textFileService, openerService)); + .map(surveyData => new LanguageSurvey(surveyData, storageService, notificationService, telemetryService, modelService, textFileService, openerService, productService)); } } -if (language === 'en' && product.surveys && product.surveys.length) { +if (language === 'en') { const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(LanguageSurveysContribution, LifecyclePhase.Restored); } diff --git a/src/vs/workbench/contrib/surveys/electron-browser/nps.contribution.ts b/src/vs/workbench/contrib/surveys/browser/nps.contribution.ts similarity index 81% rename from src/vs/workbench/contrib/surveys/electron-browser/nps.contribution.ts rename to src/vs/workbench/contrib/surveys/browser/nps.contribution.ts index da0aeb9732..8aaa798801 100644 --- a/src/vs/workbench/contrib/surveys/electron-browser/nps.contribution.ts +++ b/src/vs/workbench/contrib/surveys/browser/nps.contribution.ts @@ -9,12 +9,12 @@ import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions as import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import pkg from 'vs/platform/product/node/package'; -import product from 'vs/platform/product/node/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; +import { platform } from 'vs/base/common/process'; const PROBABILITY = 0.15; const SESSION_COUNT_KEY = 'nps/sessionCount'; @@ -28,8 +28,13 @@ class NPSContribution implements IWorkbenchContribution { @IStorageService storageService: IStorageService, @INotificationService notificationService: INotificationService, @ITelemetryService telemetryService: ITelemetryService, - @IOpenerService openerService: IOpenerService + @IOpenerService openerService: IOpenerService, + @IProductService productService: IProductService ) { + if (!productService.npsSurveyUrl) { + return; + } + const skipVersion = storageService.get(SKIP_VERSION_KEY, StorageScope.GLOBAL, ''); if (skipVersion) { return; @@ -56,7 +61,7 @@ class NPSContribution implements IWorkbenchContribution { storageService.store(IS_CANDIDATE_KEY, isCandidate, StorageScope.GLOBAL); if (!isCandidate) { - storageService.store(SKIP_VERSION_KEY, pkg.version, StorageScope.GLOBAL); + storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL); return; } @@ -67,9 +72,9 @@ class NPSContribution implements IWorkbenchContribution { label: nls.localize('takeSurvey', "Take Survey"), run: () => { telemetryService.getTelemetryInfo().then(info => { - openerService.open(URI.parse(`${product.npsSurveyUrl}?o=${encodeURIComponent(process.platform)}&v=${encodeURIComponent(pkg.version)}&m=${encodeURIComponent(info.machineId)}`)); + openerService.open(URI.parse(`${productService.npsSurveyUrl}?o=${encodeURIComponent(platform)}&v=${encodeURIComponent(productService.version)}&m=${encodeURIComponent(info.machineId)}`)); storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); - storageService.store(SKIP_VERSION_KEY, pkg.version, StorageScope.GLOBAL); + storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL); }); } }, { @@ -79,7 +84,7 @@ class NPSContribution implements IWorkbenchContribution { label: nls.localize('neverAgain', "Don't Show Again"), run: () => { storageService.store(IS_CANDIDATE_KEY, false, StorageScope.GLOBAL); - storageService.store(SKIP_VERSION_KEY, pkg.version, StorageScope.GLOBAL); + storageService.store(SKIP_VERSION_KEY, productService.version, StorageScope.GLOBAL); } }], { sticky: true } @@ -87,7 +92,7 @@ class NPSContribution implements IWorkbenchContribution { } } -if (language === 'en' && product.npsSurveyUrl) { +if (language === 'en') { const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(NPSContribution, LifecyclePhase.Restored); } diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 251e891414..aba32f8947 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -188,6 +188,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer protected _taskSystemInfos: Map; protected _workspaceTasksPromise?: Promise>; + protected _areJsonTasksSupportedPromise: Promise = Promise.resolve(false); protected _taskSystem?: ITaskSystem; protected _taskSystemListener?: IDisposable; @@ -1416,6 +1417,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } + public setJsonTasksSupported(areSupported: Promise) { + this._areJsonTasksSupportedPromise = areSupported; + } + private computeWorkspaceFolderTasks(workspaceFolder: IWorkspaceFolder, runSource: TaskRunSource = TaskRunSource.User): Promise { return (this.executionEngine === ExecutionEngine.Process ? this.computeLegacyConfiguration(workspaceFolder) @@ -1424,7 +1429,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!workspaceFolderConfiguration || !workspaceFolderConfiguration.config || workspaceFolderConfiguration.hasErrors) { return Promise.resolve({ workspaceFolder, set: undefined, configurations: undefined, hasErrors: workspaceFolderConfiguration ? workspaceFolderConfiguration.hasErrors : false }); } - return ProblemMatcherRegistry.onReady().then((): WorkspaceFolderTaskResult => { + return ProblemMatcherRegistry.onReady().then(async (): Promise => { let taskSystemInfo: TaskSystemInfo | undefined = this._taskSystemInfos.get(workspaceFolder.uri.scheme); let problemReporter = new ProblemReporter(this._outputChannel); let parseResult = TaskConfig.parse(workspaceFolder, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter); @@ -1446,7 +1451,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer customizedTasks.byIdentifier[task.configures._key] = task; } } - return { workspaceFolder, set: { tasks: parseResult.custom }, configurations: customizedTasks, hasErrors }; + if (!(await this._areJsonTasksSupportedPromise) && (parseResult.custom.length > 0)) { + console.warn('Custom workspace tasks are not supported.'); + } + return { workspaceFolder, set: { tasks: await this._areJsonTasksSupportedPromise ? parseResult.custom : [] }, configurations: customizedTasks, hasErrors }; }); }); } diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index e0caef8d16..ec76f2afb2 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -83,6 +83,7 @@ export interface ITaskService { registerTaskProvider(taskProvider: ITaskProvider, type: string): IDisposable; registerTaskSystem(scheme: string, taskSystemInfo: TaskSystemInfo): void; + setJsonTasksSupported(areSuppored: Promise): void; extensionCallbackTaskComplete(task: Task, result: number | undefined): Promise; } diff --git a/src/vs/workbench/contrib/terminal/browser/media/kill-dark.svg b/src/vs/workbench/contrib/terminal/browser/media/kill-dark.svg deleted file mode 100644 index 9831c96a16..0000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/kill-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/kill-hc.svg b/src/vs/workbench/contrib/terminal/browser/media/kill-hc.svg deleted file mode 100644 index 656f3bd7a4..0000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/kill-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/kill-light.svg b/src/vs/workbench/contrib/terminal/browser/media/kill-light.svg deleted file mode 100644 index d5ac851860..0000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/kill-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/new-dark.svg b/src/vs/workbench/contrib/terminal/browser/media/new-dark.svg deleted file mode 100644 index 4d9389336b..0000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/new-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/new-hc.svg b/src/vs/workbench/contrib/terminal/browser/media/new-hc.svg deleted file mode 100644 index fb50c6c284..0000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/new-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/new-light.svg b/src/vs/workbench/contrib/terminal/browser/media/new-light.svg deleted file mode 100644 index 01a9de7d5a..0000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/new-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-dark.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-dark.svg deleted file mode 100644 index 8c22a7c5bf..0000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-hc.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-hc.svg deleted file mode 100644 index 82c19d0c8f..0000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-light.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-light.svg deleted file mode 100644 index 2d53ab6d3c..0000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-dark.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-dark.svg deleted file mode 100644 index 419c21be4f..0000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-hc.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-hc.svg deleted file mode 100644 index 7565fd3c16..0000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-light.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-light.svg deleted file mode 100644 index 7e95763b46..0000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index e84bdf0854..5ea5fc9e91 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -144,24 +144,6 @@ opacity: 0 !important; } -/* Light theme */ -.monaco-workbench .terminal-action.kill { background: url('kill-light.svg') center center no-repeat; } -.monaco-workbench .terminal-action.new { background: url('new-light.svg') center center no-repeat; } -.monaco-workbench .terminal-action.split { background: url('split-editor-horizontal-light.svg') center center no-repeat; } -.monaco-workbench .panel.right .terminal-action.split { background: url('split-editor-vertical-light.svg') center center no-repeat; } - -/* Dark theme */ -.vs-dark .monaco-workbench .terminal-action.kill, .hc-black .monaco-workbench .terminal-action.kill { background: url('kill-dark.svg') center center no-repeat; } -.vs-dark .monaco-workbench .terminal-action.new, .hc-black .monaco-workbench .terminal-action.new { background: url('new-dark.svg') center center no-repeat; } -.vs-dark .monaco-workbench .terminal-action.split, .hc-black .monaco-workbench .terminal-action.split { background: url('split-editor-horizontal-dark.svg') center center no-repeat; } -.vs-dark .monaco-workbench .panel.right .terminal-action.split, .hc-black .monaco-workbench .panel.right .terminal-action.split { background: url('split-editor-vertical-dark.svg') center center no-repeat; } - -/* HC theme */ -.hc-black .monaco-workbench .terminal-action.kill, .hc-black .monaco-workbench .terminal-action.kill { background: url('kill-hc.svg') center center no-repeat; } -.hc-black .monaco-workbench .terminal-action.new, .hc-black .monaco-workbench .terminal-action.new { background: url('new-hc.svg') center center no-repeat; } -.hc-black .monaco-workbench .terminal-action.split, .hc-black .monaco-workbench .terminal-action.split { background: url('split-editor-horizontal-hc.svg') center center no-repeat; } -.hc-black .monaco-workbench .panel.right .terminal-action.split, .hc-black .monaco-workbench .panel.right .terminal-action.split { background: url('split-editor-vertical-hc.svg') center center no-repeat; } - .vs-dark .monaco-workbench.mac .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events), .hc-black .monaco-workbench.mac .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events) { cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text; diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 9aecc9a026..bb719e4893 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -20,7 +20,7 @@ import * as panel from 'vs/workbench/browser/panel'; import { getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; -import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand, CreateNewWithCwdTerminalCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel'; import { TerminalPickerHandler } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -550,27 +550,43 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, Fi mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_G, secondary: [KeyMod.Shift | KeyCode.F3, KeyCode.Enter] }, }, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED), 'Terminal: Find previous'); - -const sendSequenceTerminalCommand = new SendSequenceTerminalCommand({ +(new SendSequenceTerminalCommand({ id: SendSequenceTerminalCommand.ID, precondition: undefined, description: { - description: `Send Custom Sequence To Terminal`, + description: SendSequenceTerminalCommand.LABEL, args: [{ name: 'args', schema: { - 'type': 'object', - 'required': ['text'], - 'properties': { - 'text': { - 'type': 'string' + type: 'object', + required: ['text'], + properties: { + text: { type: 'string' } + }, + } + }] + } +})).register(); +(new CreateNewWithCwdTerminalCommand({ + id: CreateNewWithCwdTerminalCommand.ID, + precondition: undefined, + description: { + description: CreateNewWithCwdTerminalCommand.LABEL, + args: [{ + name: 'args', + schema: { + type: 'object', + required: ['cwd'], + properties: { + cwd: { + description: CreateNewWithCwdTerminalCommand.CWD_ARG_LABEL, + type: 'string' } }, } }] } -}); -sendSequenceTerminalCommand.register(); +})).register(); setupTerminalCommands(); setupTerminalMenu(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index af21f393f3..7a71e2c2c5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -105,7 +105,7 @@ export class KillTerminalAction extends Action { id: string, label: string, @ITerminalService private readonly terminalService: ITerminalService ) { - super(id, label, 'terminal-action kill'); + super(id, label, 'terminal-action codicon-trash'); } public run(event?: any): Promise { @@ -298,6 +298,32 @@ export class SendSequenceTerminalCommand extends Command { } } +export class CreateNewWithCwdTerminalCommand extends Command { + public static readonly ID = TERMINAL_COMMAND_ID.NEW_WITH_CWD; + public static readonly LABEL = nls.localize('workbench.action.terminal.newWithCwd', "Create New Integrated Terminal Starting in a Custom Working Directory"); + public static readonly CWD_ARG_LABEL = nls.localize('workbench.action.terminal.newWithCwd.cwd', "The directory to start the terminal at"); + + public runCommand(accessor: ServicesAccessor, args: { cwd: string } | undefined): Promise { + const terminalService = accessor.get(ITerminalService); + const configurationResolverService = accessor.get(IConfigurationResolverService); + const workspaceContextService = accessor.get(IWorkspaceContextService); + const historyService = accessor.get(IHistoryService); + const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file); + const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? withNullAsUndefined(workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; + + let cwd: string | undefined; + if (args && args.cwd) { + cwd = configurationResolverService.resolve(lastActiveWorkspaceRoot, args.cwd); + } + const instance = terminalService.createTerminal({ cwd }); + if (!instance) { + return Promise.resolve(undefined); + } + terminalService.setActiveInstance(instance); + return terminalService.showPanel(true); + } +} + export class CreateNewTerminalAction extends Action { public static readonly ID = TERMINAL_COMMAND_ID.NEW; @@ -310,7 +336,7 @@ export class CreateNewTerminalAction extends Action { @ICommandService private readonly commandService: ICommandService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService ) { - super(id, label, 'terminal-action new'); + super(id, label, 'terminal-action codicon-add'); } public run(event?: any): Promise { @@ -386,7 +412,7 @@ export class SplitTerminalAction extends Action { @ICommandService private readonly commandService: ICommandService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService ) { - super(id, label, 'terminal-action split'); + super(id, label, 'terminal-action codicon-split-horizontal'); } public run(event?: any): Promise { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 970a4e4499..c5544ead33 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -19,7 +19,7 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private'; const MINIMUM_FONT_SIZE = 6; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index fd767e7f93..8660142f70 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -17,7 +17,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati import { Schemas } from 'vs/base/common/network'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 1447f3474b..e423624da3 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -423,6 +423,7 @@ export const enum TERMINAL_COMMAND_ID { MOVE_TO_LINE_START = 'workbench.action.terminal.moveToLineStart', MOVE_TO_LINE_END = 'workbench.action.terminal.moveToLineEnd', NEW = 'workbench.action.terminal.new', + NEW_WITH_CWD = 'workbench.action.terminal.newWithCwd', NEW_LOCAL = 'workbench.action.terminal.newLocal', NEW_IN_ACTIVE_WORKSPACE = 'workbench.action.terminal.newInActiveWorkspace', SPLIT = 'workbench.action.terminal.split', diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 417d158934..6b419e2537 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -18,10 +18,10 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IRequestService, asText } from 'vs/platform/request/common/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IWebviewEditorService } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { WebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; +import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -32,7 +32,7 @@ export class ReleaseNotesManager { private readonly _releaseNotesCache = new Map>(); - private _currentReleaseNotes: WebviewEditorInput | undefined = undefined; + private _currentReleaseNotes: WebviewInput | undefined = undefined; private _lastText: string | undefined; public constructor( diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index ab5222038a..bdcddbf319 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -28,7 +28,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { FalseContext } from 'vs/platform/contextkey/common/contextkeys'; import { ShowCurrentReleaseNotesActionId } from 'vs/workbench/contrib/update/common/update'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; const CONTEXT_UPDATE_STATE = new RawContextKey('updateState', StateType.Uninitialized); @@ -400,7 +400,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu private registerGlobalActivityActions(): void { CommandsRegistry.registerCommand('update.check', () => this.updateService.checkForUpdates({ windowId: this.environmentService.configuration.windowId })); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '5_update', + group: '6_update', command: { id: 'update.check', title: nls.localize('checkForUpdates', "Check for Updates...") @@ -410,7 +410,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu CommandsRegistry.registerCommand('update.checking', () => { }); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '5_update', + group: '6_update', command: { id: 'update.checking', title: nls.localize('checkingForUpdates', "Checking for Updates..."), @@ -421,7 +421,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu CommandsRegistry.registerCommand('update.downloadNow', () => this.updateService.downloadUpdate()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '5_update', + group: '6_update', command: { id: 'update.downloadNow', title: nls.localize('download update', "Download Update") @@ -431,7 +431,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu CommandsRegistry.registerCommand('update.downloading', () => { }); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '5_update', + group: '6_update', command: { id: 'update.downloading', title: nls.localize('DownloadingUpdate', "Downloading Update..."), @@ -442,7 +442,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu CommandsRegistry.registerCommand('update.install', () => this.updateService.applyUpdate()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '5_update', + group: '6_update', command: { id: 'update.install', title: nls.localize('installUpdate...', "Install Update...") @@ -452,7 +452,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu CommandsRegistry.registerCommand('update.updating', () => { }); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '5_update', + group: '6_update', command: { id: 'update.updating', title: nls.localize('installingUpdate', "Installing Update..."), @@ -463,7 +463,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu CommandsRegistry.registerCommand('update.restart', () => this.updateService.quitAndInstall()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - group: '5_update', + group: '6_update', command: { id: 'update.restart', title: nls.localize('restartToUpdate', "Restart to Update") diff --git a/src/vs/workbench/contrib/update/electron-browser/update.ts b/src/vs/workbench/contrib/update/electron-browser/update.ts index 9a54595b4a..4b461e2fd6 100644 --- a/src/vs/workbench/contrib/update/electron-browser/update.ts +++ b/src/vs/workbench/contrib/update/electron-browser/update.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import severity from 'vs/base/common/severity'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { INotificationService } from 'vs/platform/notification/common/notification'; diff --git a/src/vs/workbench/contrib/url/common/externalUriResolver.ts b/src/vs/workbench/contrib/url/common/externalUriResolver.ts new file mode 100644 index 0000000000..1a86f56aa2 --- /dev/null +++ b/src/vs/workbench/contrib/url/common/externalUriResolver.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; + +export class ExternalUriResolverContribution extends Disposable implements IWorkbenchContribution { + constructor( + @IOpenerService _openerService: IOpenerService, + @IWorkbenchEnvironmentService _workbenchEnvironmentService: IWorkbenchEnvironmentService, + ) { + super(); + + if (_workbenchEnvironmentService.options && _workbenchEnvironmentService.options.resolveExternalUri) { + this._register(_openerService.registerExternalUriResolver({ + resolveExternalUri: async (resource) => { + return _workbenchEnvironmentService.options!.resolveExternalUri!(resource); + } + })); + } + } +} diff --git a/src/vs/workbench/contrib/url/common/trustedDomains.ts b/src/vs/workbench/contrib/url/common/trustedDomains.ts new file mode 100644 index 0000000000..c66f207570 --- /dev/null +++ b/src/vs/workbench/contrib/url/common/trustedDomains.ts @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +const TRUSTED_DOMAINS_URI = URI.parse('trustedDomains:/Trusted Domains'); + +export const configureTrustedDomainSettingsCommand = { + id: 'workbench.action.configureTrustedDomain', + description: { + description: localize('trustedDomain.configureTrustedDomain', 'Configure Trusted Domains'), + args: [] + }, + handler: async (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + editorService.openEditor({ resource: TRUSTED_DOMAINS_URI, mode: 'jsonc' }); + return; + } +}; + +export async function configureOpenerTrustedDomainsHandler( + trustedDomains: string[], + domainToConfigure: string, + quickInputService: IQuickInputService, + storageService: IStorageService, + editorService: IEditorService +) { + const parsedDomainToConfigure = URI.parse(domainToConfigure); + const toplevelDomainSegements = domainToConfigure.split('.'); + const domainEnd = toplevelDomainSegements.slice(toplevelDomainSegements.length - 2).join('.'); + const topLevelDomain = parsedDomainToConfigure.scheme + '://' + '*.' + domainEnd; + + const trustDomainAndOpenLinkItem: IQuickPickItem = { + type: 'item', + label: localize('trustedDomain.trustDomainAndOpenLink', 'Trust {0} and open link', domainToConfigure), + id: domainToConfigure, + picked: true + }; + const trustSubDomainAndOpenLinkItem: IQuickPickItem = { + type: 'item', + label: localize('trustedDomain.trustSubDomainAndOpenLink', 'Trust all domains ending in {0} and open link', domainEnd), + id: topLevelDomain + }; + const openAllLinksItem: IQuickPickItem = { + type: 'item', + label: localize('trustedDomain.trustAllAndOpenLink', 'Disable Link Protection and open link'), + id: '*', + picked: trustedDomains.indexOf('*') !== -1 + }; + const configureTrustedDomainItem: IQuickPickItem = { + type: 'item', + label: localize('trustedDomain.configureTrustedDomains', 'Configure Trusted Domains'), + id: 'configure' + }; + + const pickedResult = await quickInputService.pick( + [trustDomainAndOpenLinkItem, trustSubDomainAndOpenLinkItem, openAllLinksItem, configureTrustedDomainItem], + { + activeItem: trustDomainAndOpenLinkItem + } + ); + + if (pickedResult) { + if (pickedResult.id === 'configure') { + editorService.openEditor({ + resource: TRUSTED_DOMAINS_URI, + mode: 'jsonc' + }); + return trustedDomains; + } + if (pickedResult.id && trustedDomains.indexOf(pickedResult.id) === -1) { + storageService.store( + 'http.linkProtectionTrustedDomains', + JSON.stringify([...trustedDomains, pickedResult.id]), + StorageScope.GLOBAL + ); + + return [...trustedDomains, pickedResult.id]; + } + } + + return []; +} + +export function readTrustedDomains(storageService: IStorageService, productService: IProductService) { + let trustedDomains: string[] = productService.linkProtectionTrustedDomains + ? [...productService.linkProtectionTrustedDomains] + : []; + + try { + const trustedDomainsSrc = storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); + if (trustedDomainsSrc) { + trustedDomains = JSON.parse(trustedDomainsSrc); + } + } catch (err) { } + + return trustedDomains; +} diff --git a/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts b/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts new file mode 100644 index 0000000000..0be5c4830e --- /dev/null +++ b/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts @@ -0,0 +1,126 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { parse } from 'vs/base/common/json'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { + FileDeleteOptions, + FileOverwriteOptions, + FileSystemProviderCapabilities, + FileType, + FileWriteOptions, + IFileService, + IFileSystemProvider, + IStat, + IWatchOptions +} from 'vs/platform/files/common/files'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { VSBuffer } from 'vs/base/common/buffer'; + +const TRUSTED_DOMAINS_SCHEMA = 'trustedDomains'; + +const TRUSTED_DOMAINS_STAT: IStat = { + type: FileType.File, + ctime: Date.now(), + mtime: Date.now(), + size: 0 +}; + +const CONFIG_HELP_TEXT = `// You can run "Configure Trusted Domains" command to edit trusted domains settings in this JSON file. +// The setting is updated upon saving this file. +// Links that match one of the entries can be opened without link protection. +// +// Example entries include: +// - "microsoft.com" +// - "*.microsoft.com": Match all domains ending in "microsoft.com" +// - "*": Match all domains +// +// By default, VS Code whitelists certain localhost and domains such as "code.visualstudio.com" +`; +const CONFIG_PLACEHOLDER_TEXT = `[ + // "microsoft.com" +] +`; + +function computeTrustedDomainContent(trustedDomains: string[]) { + if (trustedDomains.length === 0) { + return CONFIG_HELP_TEXT + CONFIG_PLACEHOLDER_TEXT; + } + + return CONFIG_HELP_TEXT + JSON.stringify(trustedDomains, null, 2); +} + +export class TrustedDomainsFileSystemProvider implements IFileSystemProvider, IWorkbenchContribution { + readonly capabilities = FileSystemProviderCapabilities.FileReadWrite; + + readonly onDidChangeCapabilities = Event.None; + readonly onDidChangeFile = Event.None; + + constructor( + @IFileService private readonly fileService: IFileService, + @IStorageService private readonly storageService: IStorageService + ) { + this.fileService.registerProvider(TRUSTED_DOMAINS_SCHEMA, this); + } + + stat(resource: URI): Promise { + return Promise.resolve(TRUSTED_DOMAINS_STAT); + } + + readFile(resource: URI): Promise { + let trustedDomains: string[] = []; + + try { + const trustedDomainsSrc = this.storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); + if (trustedDomainsSrc) { + trustedDomains = JSON.parse(trustedDomainsSrc); + } + } catch (err) { } + + + const trustedDomainsContent = computeTrustedDomainContent(trustedDomains); + const buffer = VSBuffer.fromString(trustedDomainsContent).buffer; + return Promise.resolve(buffer); + } + + writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise { + let trustedDomainsd = []; + + try { + trustedDomainsd = parse(content.toString()); + } catch (err) { } + + this.storageService.store( + 'http.linkProtectionTrustedDomains', + JSON.stringify(trustedDomainsd), + StorageScope.GLOBAL + ); + + return Promise.resolve(); + } + + watch(resource: URI, opts: IWatchOptions): IDisposable { + return { + dispose() { + return; + } + }; + } + mkdir(resource: URI): Promise { + return Promise.resolve(undefined!); + } + readdir(resource: URI): Promise<[string, FileType][]> { + return Promise.resolve(undefined!); + } + delete(resource: URI, opts: FileDeleteOptions): Promise { + return Promise.resolve(undefined!); + } + rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { + return Promise.resolve(undefined!); + } +} diff --git a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts new file mode 100644 index 0000000000..28901eb85c --- /dev/null +++ b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts @@ -0,0 +1,147 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Schemas } from 'vs/base/common/network'; +import Severity from 'vs/base/common/severity'; +import { equalsIgnoreCase } from 'vs/base/common/strings'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { configureOpenerTrustedDomainsHandler, readTrustedDomains } from 'vs/workbench/contrib/url/common/trustedDomains'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +export class OpenerValidatorContributions implements IWorkbenchContribution { + constructor( + @IOpenerService private readonly _openerService: IOpenerService, + @IStorageService private readonly _storageService: IStorageService, + @IDialogService private readonly _dialogService: IDialogService, + @IProductService private readonly _productService: IProductService, + @IQuickInputService private readonly _quickInputService: IQuickInputService, + @IEditorService private readonly _editorService: IEditorService + ) { + this._openerService.registerValidator({ shouldOpen: r => this.validateLink(r) }); + } + + async validateLink(resource: URI): Promise { + const { scheme, authority } = resource; + + if (!equalsIgnoreCase(scheme, Schemas.http) && !equalsIgnoreCase(scheme, Schemas.https)) { + return true; + } + + const domainToOpen = `${scheme}://${authority}`; + const trustedDomains = readTrustedDomains(this._storageService, this._productService); + + if (isURLDomainTrusted(resource, trustedDomains)) { + return true; + } else { + const { choice } = await this._dialogService.show( + Severity.Info, + localize( + 'openExternalLinkAt', + 'Do you want {0} to open the external website?\n{1}', + this._productService.nameShort, + resource.toString(true) + ), + [ + localize('openLink', 'Open Link'), + localize('cancel', 'Cancel'), + localize('configureTrustedDomains', 'Configure Trusted Domains') + ], + { + cancelId: 1 + } + ); + + // Open Link + if (choice === 0) { + return true; + } + // Configure Trusted Domains + else if (choice === 2) { + const pickedDomains = await configureOpenerTrustedDomainsHandler( + trustedDomains, + domainToOpen, + this._quickInputService, + this._storageService, + this._editorService + ); + // Trust all domains + if (pickedDomains.indexOf('*') !== -1) { + return true; + } + // Trust current domain + if (pickedDomains.indexOf(domainToOpen) !== -1) { + return true; + } + return false; + } + + return false; + } + } +} + +const rLocalhost = /^localhost(:\d+)?$/i; +const r127 = /^127.0.0.1(:\d+)?$/; + +function isLocalhostAuthority(authority: string) { + return rLocalhost.test(authority) || r127.test(authority); +} + +/** + * Check whether a domain like https://www.microsoft.com matches + * the list of trusted domains. + * + * - Schemes must match + * - There's no subdomain matching. For example https://microsoft.com doesn't match https://www.microsoft.com + * - Star matches all subdomains. For example https://*.microsoft.com matches https://www.microsoft.com and https://foo.bar.microsoft.com + */ +export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { + if (isLocalhostAuthority(url.authority)) { + return true; + } + + const domain = `${url.scheme}://${url.authority}`; + + for (let i = 0; i < trustedDomains.length; i++) { + if (trustedDomains[i] === '*') { + return true; + } + + if (trustedDomains[i] === domain) { + return true; + } + + if (trustedDomains[i].indexOf('*') !== -1) { + const parsedTrustedDomain = URI.parse(trustedDomains[i]); + if (url.scheme === parsedTrustedDomain.scheme) { + let reversedAuthoritySegments = url.authority.split('.').reverse(); + const reversedTrustedDomainAuthoritySegments = parsedTrustedDomain.authority.split('.').reverse(); + if ( + reversedTrustedDomainAuthoritySegments.length < reversedAuthoritySegments.length && + reversedTrustedDomainAuthoritySegments[reversedTrustedDomainAuthoritySegments.length - 1] === '*' + ) { + reversedAuthoritySegments = reversedAuthoritySegments.slice(0, reversedTrustedDomainAuthoritySegments.length); + } + + if ( + reversedAuthoritySegments.every((val, i) => { + return reversedTrustedDomainAuthoritySegments[i] === '*' || val === reversedTrustedDomainAuthoritySegments[i]; + }) + ) { + return true; + } + } + } + } + + return false; +} diff --git a/src/vs/workbench/contrib/url/common/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts index 3e0466dba1..1e65e36da2 100644 --- a/src/vs/workbench/contrib/url/common/url.contribution.ts +++ b/src/vs/workbench/contrib/url/common/url.contribution.ts @@ -3,29 +3,21 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; -import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; -import { IURLService } from 'vs/platform/url/common/url'; -import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { URI } from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { - IWorkbenchContribution, - IWorkbenchContributionsRegistry, - Extensions as WorkbenchExtensions -} from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IProductService } from 'vs/platform/product/common/product'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { equalsIgnoreCase } from 'vs/base/common/strings'; -import { Schemas } from 'vs/base/common/network'; -import Severity from 'vs/base/common/severity'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IURLService } from 'vs/platform/url/common/url'; +import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { ExternalUriResolverContribution } from 'vs/workbench/contrib/url/common/externalUriResolver'; +import { configureTrustedDomainSettingsCommand } from 'vs/workbench/contrib/url/common/trustedDomains'; +import { TrustedDomainsFileSystemProvider } from 'vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider'; +import { OpenerValidatorContributions } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; export class OpenUrlAction extends Action { static readonly ID = 'workbench.action.url.openUrl'; @@ -54,227 +46,27 @@ Registry.as(ActionExtensions.WorkbenchActions).registe localize('developer', 'Developer') ); -const configureTrustedDomainsHandler = async ( - quickInputService: IQuickInputService, - storageService: IStorageService, - linkProtectionTrustedDomains: string[], - domainToConfigure?: string -) => { - try { - const trustedDomainsSrc = storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); - if (trustedDomainsSrc) { - linkProtectionTrustedDomains = JSON.parse(trustedDomainsSrc); - } - } catch (err) { } +/** + * Trusted Domains Contribution + */ - const domainQuickPickItems: IQuickPickItem[] = linkProtectionTrustedDomains - .filter(d => d !== '*') - .map(d => { - return { - type: 'item', - label: d, - id: d, - picked: true - }; - }); - - const specialQuickPickItems: IQuickPickItem[] = [ - { - type: 'item', - label: localize('openAllLinksWithoutPrompt', 'Open all links without prompt'), - id: '*', - picked: linkProtectionTrustedDomains.indexOf('*') !== -1 - } - ]; - - let domainToConfigureItem: IQuickPickItem | undefined = undefined; - if (domainToConfigure && linkProtectionTrustedDomains.indexOf(domainToConfigure) === -1) { - domainToConfigureItem = { - type: 'item', - label: domainToConfigure, - id: domainToConfigure, - picked: true, - description: localize('trustDomainAndOpenLink', 'Trust domain and open link') - }; - specialQuickPickItems.push(domainToConfigureItem); - } - - const quickPickItems: (IQuickPickItem | IQuickPickSeparator)[] = - domainQuickPickItems.length === 0 - ? specialQuickPickItems - : [...specialQuickPickItems, { type: 'separator' }, ...domainQuickPickItems]; - - const pickedResult = await quickInputService.pick(quickPickItems, { - canPickMany: true, - activeItem: domainToConfigureItem - }); - - if (pickedResult) { - const pickedDomains: string[] = pickedResult.map(r => r.id!); - storageService.store('http.linkProtectionTrustedDomains', JSON.stringify(pickedDomains), StorageScope.GLOBAL); - - return pickedDomains; - } - - return []; -}; - -const configureTrustedDomainCommand = { - id: 'workbench.action.configureLinkProtectionTrustedDomains', - description: { - description: localize('configureLinkProtectionTrustedDomains', 'Configure Trusted Domains for Link Protection'), - args: [{ name: 'domainToConfigure', schema: { type: 'string' } }] - }, - handler: (accessor: ServicesAccessor, domainToConfigure?: string) => { - const quickInputService = accessor.get(IQuickInputService); - const storageService = accessor.get(IStorageService); - const productService = accessor.get(IProductService); - - const trustedDomains = productService.linkProtectionTrustedDomains - ? [...productService.linkProtectionTrustedDomains] - : []; - - return configureTrustedDomainsHandler(quickInputService, storageService, trustedDomains, domainToConfigure); - } -}; - -CommandsRegistry.registerCommand(configureTrustedDomainCommand); +CommandsRegistry.registerCommand(configureTrustedDomainSettingsCommand); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { - id: configureTrustedDomainCommand.id, - title: configureTrustedDomainCommand.description.description + id: configureTrustedDomainSettingsCommand.id, + title: configureTrustedDomainSettingsCommand.description.description } }); -class OpenerValidatorContributions implements IWorkbenchContribution { - constructor( - @IOpenerService private readonly _openerService: IOpenerService, - @IStorageService private readonly _storageService: IStorageService, - @IDialogService private readonly _dialogService: IDialogService, - @IProductService private readonly _productService: IProductService, - @IQuickInputService private readonly _quickInputService: IQuickInputService - ) { - this._openerService.registerValidator({ shouldOpen: r => this.validateLink(r) }); - } - - async validateLink(resource: URI): Promise { - const { scheme, authority } = resource; - - if (!equalsIgnoreCase(scheme, Schemas.http) && !equalsIgnoreCase(scheme, Schemas.https)) { - return true; - } - - let trustedDomains: string[] = this._productService.linkProtectionTrustedDomains - ? [...this._productService.linkProtectionTrustedDomains] - : []; - - try { - const trustedDomainsSrc = this._storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); - if (trustedDomainsSrc) { - trustedDomains = JSON.parse(trustedDomainsSrc); - } - } catch (err) { } - - const domainToOpen = `${scheme}://${authority}`; - - if (isURLDomainTrusted(resource, trustedDomains)) { - return true; - } else { - const { choice } = await this._dialogService.show( - Severity.Info, - localize( - 'openExternalLinkAt', - 'Do you want {0} to open the external website?\n{1}', - this._productService.nameShort, - resource.toString(true) - ), - [ - localize('openLink', 'Open Link'), - localize('cancel', 'Cancel'), - localize('configureTrustedDomains', 'Configure Trusted Domains') - ], - { - cancelId: 1 - } - ); - - // Open Link - if (choice === 0) { - return true; - } - // Configure Trusted Domains - else if (choice === 2) { - const pickedDomains = await configureTrustedDomainsHandler( - this._quickInputService, - this._storageService, - trustedDomains, - domainToOpen - ); - if (pickedDomains.indexOf(domainToOpen) !== -1) { - return true; - } - return false; - } - - return false; - } - } -} - Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( OpenerValidatorContributions, LifecyclePhase.Restored ); - -const rLocalhost = /^localhost(:\d+)?$/i; -const r127 = /^127.0.0.1(:\d+)?$/; - -function isLocalhostAuthority(authority: string) { - return rLocalhost.test(authority) || r127.test(authority); -} - -/** - * Check whether a domain like https://www.microsoft.com matches - * the list of trusted domains. - * - * - Schemes must match - * - There's no subdomain matching. For example https://microsoft.com doesn't match https://www.microsoft.com - * - Star matches all. For example https://*.microsoft.com matches https://www.microsoft.com - */ -export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { - if (isLocalhostAuthority(url.authority)) { - return true; - } - - const domain = `${url.scheme}://${url.authority}`; - - for (let i = 0; i < trustedDomains.length; i++) { - if (trustedDomains[i] === '*') { - return true; - } - - if (trustedDomains[i] === domain) { - return true; - } - - if (trustedDomains[i].indexOf('*') !== -1) { - const parsedTrustedDomain = URI.parse(trustedDomains[i]); - if (url.scheme === parsedTrustedDomain.scheme) { - const authoritySegments = url.authority.split('.'); - const trustedDomainAuthoritySegments = parsedTrustedDomain.authority.split('.'); - - if (authoritySegments.length === trustedDomainAuthoritySegments.length) { - if ( - authoritySegments.every( - (val, i) => trustedDomainAuthoritySegments[i] === '*' || val === trustedDomainAuthoritySegments[i] - ) - ) { - return true; - } - } - } - } - } - - return false; -} +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( + TrustedDomainsFileSystemProvider, + LifecyclePhase.Ready +); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( + ExternalUriResolverContribution, + LifecyclePhase.Ready +); diff --git a/src/vs/workbench/contrib/userDataSync/browser/media/sync-push-dark.svg b/src/vs/workbench/contrib/userDataSync/browser/media/sync-push-dark.svg new file mode 100644 index 0000000000..72695bb2e5 --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/media/sync-push-dark.svg @@ -0,0 +1,3 @@ + diff --git a/src/vs/workbench/contrib/userDataSync/browser/media/sync-push-light.svg b/src/vs/workbench/contrib/userDataSync/browser/media/sync-push-light.svg new file mode 100644 index 0000000000..82cbddecbc --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/media/sync-push-light.svg @@ -0,0 +1,3 @@ + diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts new file mode 100644 index 0000000000..5cc05ba395 --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts @@ -0,0 +1,235 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IUserDataSyncService, SyncStatus, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { localize } from 'vs/nls'; +import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions'; +import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IActivityService, IBadge, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; +import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { URI } from 'vs/base/common/uri'; +import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; +import { ResourceContextKey } from 'vs/workbench/common/resources'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { Event } from 'vs/base/common/event'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { isEqual } from 'vs/base/common/resources'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { isWeb } from 'vs/base/common/platform'; +import { UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataSyncService'; + +const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized); + +Registry.as(ConfigurationExtensions.Configuration) + .registerConfiguration({ + id: 'userConfiguration', + order: 30, + title: localize('userConfiguration', "User Configuration"), + type: 'object', + properties: { + 'userConfiguration.enableSync': { + type: 'boolean', + description: localize('userConfiguration.enableSync', "When enabled, synchronises User Configuration: Settings, Keybindings, Extensions & Snippets."), + default: true, + scope: ConfigurationScope.APPLICATION + } + } + }); + +class UserDataAutoSyncContribution extends Disposable implements IWorkbenchContribution { + + constructor( + @IInstantiationService instantiationService: IInstantiationService + ) { + super(); + if (isWeb) { + instantiationService.createInstance(UserDataAutoSync); + } + } +} + +const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/sync-push-light.svg`)); +const SYNC_PUSH_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/sync-push-dark.svg`)); +class SyncActionsContribution extends Disposable implements IWorkbenchContribution { + + private readonly syncEnablementContext: IContextKey; + private readonly badgeDisposable = this._register(new MutableDisposable()); + private readonly conflictsWarningDisposable = this._register(new MutableDisposable()); + + constructor( + @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, + @IContextKeyService contextKeyService: IContextKeyService, + @IActivityService private readonly activityService: IActivityService, + @INotificationService private readonly notificationService: INotificationService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IEditorService private readonly editorService: IEditorService, + @ITextFileService private readonly textFileService: ITextFileService, + @IHistoryService private readonly historyService: IHistoryService, + @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, + ) { + super(); + this.syncEnablementContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); + this.onDidChangeStatus(userDataSyncService.status); + this._register(Event.debounce(userDataSyncService.onDidChangeStatus, () => undefined, 500)(status => this.onDidChangeStatus(userDataSyncService.status))); + this.registerActions(); + } + + private onDidChangeStatus(status: SyncStatus) { + this.syncEnablementContext.set(status); + + let badge: IBadge | undefined = undefined; + let clazz: string | undefined; + + if (status === SyncStatus.HasConflicts) { + badge = new NumberBadge(1, () => localize('resolve conflicts', "Resolve Conflicts")); + } else if (status === SyncStatus.Syncing) { + badge = new ProgressBadge(() => localize('syncing', "Synchronising User Configuration...")); + clazz = 'progress-badge'; + } + + this.badgeDisposable.clear(); + + if (badge) { + this.badgeDisposable.value = this.activityService.showActivity(GLOBAL_ACTIVITY_ID, badge, clazz); + } + + if (status === SyncStatus.HasConflicts) { + if (!this.conflictsWarningDisposable.value) { + const handle = this.notificationService.prompt(Severity.Warning, localize('conflicts detected', "Unable to sync due to conflicts. Please resolve them to continue."), + [ + { + label: localize('resolve', "Resolve Conflicts"), + run: () => this.handleConflicts() + } + ]); + this.conflictsWarningDisposable.value = toDisposable(() => handle.close()); + handle.onDidClose(() => this.conflictsWarningDisposable.clear()); + } + } else { + this.conflictsWarningDisposable.clear(); + } + } + + private async continueSync(): Promise { + // Get the preview editor + const editorInput = this.editorService.editors.filter(input => isEqual(input.getResource(), this.workbenchEnvironmentService.settingsSyncPreviewResource))[0]; + // Save the preview + if (editorInput && editorInput.isDirty()) { + await this.textFileService.save(editorInput.getResource()!); + } + try { + // Continue Sync + await this.userDataSyncService.sync(true); + } catch (error) { + this.notificationService.error(error); + return; + } + // Close the preview editor + if (editorInput) { + editorInput.dispose(); + } + } + + private async handleConflicts(): Promise { + if (this.userDataSyncService.conflictsSource === SyncSource.Settings) { + const resourceInput = { + resource: this.workbenchEnvironmentService.settingsSyncPreviewResource, + options: { + preserveFocus: false, + pinned: false, + revealIfVisible: true, + }, + mode: 'jsonc' + }; + this.editorService.openEditor(resourceInput) + .then(editor => { + this.historyService.remove(resourceInput); + if (editor && editor.input) { + // Trigger sync after closing the conflicts editor. + const disposable = editor.input.onDispose(() => { + disposable.dispose(); + this.userDataSyncService.sync(true); + }); + } + }); + } + } + + private registerActions(): void { + + const startSyncMenuItem: IMenuItem = { + group: '5_sync', + command: { + id: 'workbench.userData.actions.syncStart', + title: localize('start sync', "Sync: Start") + }, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not('config.userConfiguration.enableSync')), + }; + CommandsRegistry.registerCommand(startSyncMenuItem.command.id, () => this.configurationService.updateValue('userConfiguration.enableSync', true)); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, startSyncMenuItem); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, startSyncMenuItem); + + const stopSyncMenuItem: IMenuItem = { + group: '5_sync', + command: { + id: 'workbench.userData.actions.stopSync', + title: localize('stop sync', "Sync: Stop") + }, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has('config.userConfiguration.enableSync')), + }; + CommandsRegistry.registerCommand(stopSyncMenuItem.command.id, () => this.configurationService.updateValue('userConfiguration.enableSync', false)); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, stopSyncMenuItem); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, stopSyncMenuItem); + + const resolveConflictsMenuItem: IMenuItem = { + group: '5_sync', + command: { + id: 'sync.resolveConflicts', + title: localize('resolveConflicts', "Sync: Resolve Conflicts"), + }, + when: CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), + }; + CommandsRegistry.registerCommand(resolveConflictsMenuItem.command.id, () => this.handleConflicts()); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, resolveConflictsMenuItem); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, resolveConflictsMenuItem); + + const continueSyncCommandId = 'workbench.userData.actions.continueSync'; + CommandsRegistry.registerCommand(continueSyncCommandId, () => this.continueSync()); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: continueSyncCommandId, + title: localize('continue sync', "Sync: Continue") + }, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts)), + }); + MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { + id: continueSyncCommandId, + title: localize('continue sync', "Sync: Continue"), + iconLocation: { + light: SYNC_PUSH_LIGHT_ICON_URI, + dark: SYNC_PUSH_DARK_ICON_URI + } + }, + group: 'navigation', + order: 1, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts), ResourceContextKey.Resource.isEqualTo(this.workbenchEnvironmentService.settingsSyncPreviewResource.toString())), + }); + } +} + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(SyncActionsContribution, LifecyclePhase.Starting); +workbenchRegistry.registerWorkbenchContribution(UserDataAutoSyncContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts new file mode 100644 index 0000000000..6ffa857b30 --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { SettingsMergeChannel } from 'vs/platform/userDataSync/common/settingsSyncIpc'; + +class UserDataSyncServicesContribution implements IWorkbenchContribution { + + constructor( + @ISettingsMergeService settingsMergeService: ISettingsMergeService, + @ISharedProcessService sharedProcessService: ISharedProcessService, + ) { + sharedProcessService.registerChannel('settingsMerge', new SettingsMergeChannel(settingsMergeService)); + } +} + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(UserDataSyncServicesContribution, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/watermark/browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts index 60f19e5bf9..b283c86d16 100644 --- a/src/vs/workbench/contrib/watermark/browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -77,7 +77,7 @@ const folderEntries = [ const WORKBENCH_TIPS_ENABLED_KEY = 'workbench.tips.enabled'; export class WatermarkContribution extends Disposable implements IWorkbenchContribution { - private watermark: HTMLElement; + private watermark: HTMLElement | undefined; private watermarkDisposable = this._register(new DisposableStore()); private enabled: boolean; private workbenchState: WorkbenchState; diff --git a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts index ce06cf2b31..8640bd7e24 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts @@ -18,14 +18,14 @@ import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/browser/ import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, webviewDeveloperCategory, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/webview/browser/webview'; import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetCommand, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from '../browser/webviewCommands'; import { WebviewEditor } from '../browser/webviewEditor'; -import { WebviewEditorInput } from '../browser/webviewEditorInput'; +import { WebviewInput } from '../browser/webviewEditorInput'; import { IWebviewEditorService, WebviewEditorService } from '../browser/webviewEditorService'; (Registry.as(EditorExtensions.Editors)).registerEditor(new EditorDescriptor( WebviewEditor, WebviewEditor.ID, localize('webview.editor.label', "webview editor")), - [new SyncDescriptor(WebviewEditorInput)]); + [new SyncDescriptor(WebviewInput)]); Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( WebviewEditorInputFactory.ID, diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index 576dc0dc4e..fb779c4e98 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -14,7 +14,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorOptions, EditorInput } from 'vs/workbench/common/editor'; -import { WebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; +import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE, Webview, WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -27,6 +27,7 @@ export class WebviewEditor extends BaseEditor { private _findWidgetVisible: IContextKey; private _editorFrame?: HTMLElement; private _content?: HTMLElement; + private _dimension?: DOM.Dimension; private readonly _webviewFocusTrackerDisposables = this._register(new DisposableStore()); private readonly _onFocusWindowHandler = this._register(new MutableDisposable()); @@ -89,7 +90,8 @@ export class WebviewEditor extends BaseEditor { } public layout(dimension: DOM.Dimension): void { - if (this.input && this.input instanceof WebviewEditorInput) { + this._dimension = dimension; + if (this.input && this.input instanceof WebviewInput) { this.synchronizeWebviewContainerDimensions(this.input.webview, dimension); this.input.webview.layout(); } @@ -109,27 +111,27 @@ export class WebviewEditor extends BaseEditor { } public withWebview(f: (element: Webview) => void): void { - if (this.input && this.input instanceof WebviewEditorInput) { + if (this.input && this.input instanceof WebviewInput) { f(this.input.webview); } } protected setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { - const webview = this.input && (this.input as WebviewEditorInput).webview; + const webview = this.input && (this.input as WebviewInput).webview; if (webview) { if (visible) { webview.claim(this); } else { webview.release(this); } - this.claimWebview(this.input as WebviewEditorInput); + this.claimWebview(this.input as WebviewInput); } super.setEditorVisible(visible, group); } public clearInput() { - if (this.input && this.input instanceof WebviewEditorInput) { + if (this.input && this.input instanceof WebviewInput) { this.input.webview.release(this); } @@ -141,7 +143,7 @@ export class WebviewEditor extends BaseEditor { return; } - if (this.input && this.input instanceof WebviewEditorInput) { + if (this.input && this.input instanceof WebviewInput) { this.input.webview.release(this); } @@ -151,16 +153,19 @@ export class WebviewEditor extends BaseEditor { return; } - if (input instanceof WebviewEditorInput) { + if (input instanceof WebviewInput) { if (this.group) { input.updateGroup(this.group.id); } this.claimWebview(input); + if (this._dimension) { + this.layout(this._dimension); + } } } - private claimWebview(input: WebviewEditorInput): void { + private claimWebview(input: WebviewInput): void { input.webview.claim(this); if (input.webview.options.enableFindWidget) { diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 973de6d983..879bdae79c 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -52,7 +52,7 @@ class WebviewIconsManager { } } -export class WebviewEditorInput extends EditorInput { +export class WebviewInput extends EditorInput { public static typeId = 'workbench.editors.webviewInput'; @@ -82,7 +82,7 @@ export class WebviewEditorInput extends EditorInput { } public getTypeId(): string { - return WebviewEditorInput.typeId; + return WebviewInput.typeId; } public getResource(): URI { @@ -119,7 +119,7 @@ export class WebviewEditorInput extends EditorInput { public set iconPath(value: { light: URI, dark: URI } | undefined) { this._iconPath = value; - WebviewEditorInput.iconsManager.setIcons(this.id, value); + WebviewInput.iconsManager.setIcons(this.id, value); } public matches(other: IEditorInput): boolean { @@ -143,7 +143,7 @@ export class WebviewEditorInput extends EditorInput { } } -export class RevivedWebviewEditorInput extends WebviewEditorInput { +export class RevivedWebviewEditorInput extends WebviewInput { private _revived: boolean = false; constructor( @@ -154,7 +154,7 @@ export class RevivedWebviewEditorInput extends WebviewEditorInput { readonly location: URI; readonly id: ExtensionIdentifier }, - private readonly reviver: (input: WebviewEditorInput) => Promise, + private readonly reviver: (input: WebviewInput) => Promise, webview: Unowned ) { super(id, viewType, name, extension, webview); diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts index 903ea0589c..4f789df57d 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts @@ -8,7 +8,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorInputFactory } from 'vs/workbench/common/editor'; -import { WebviewEditorInput } from './webviewEditorInput'; +import { WebviewInput } from './webviewEditorInput'; import { IWebviewEditorService, WebviewInputOptions } from './webviewEditorService'; interface SerializedIconPath { @@ -29,13 +29,13 @@ interface SerializedWebview { export class WebviewEditorInputFactory implements IEditorInputFactory { - public static readonly ID = WebviewEditorInput.typeId; + public static readonly ID = WebviewInput.typeId; public constructor( @IWebviewEditorService private readonly _webviewService: IWebviewEditorService ) { } - public serialize(input: WebviewEditorInput): string | undefined { + public serialize(input: WebviewInput): string | undefined { if (!this._webviewService.shouldPersist(input)) { return undefined; } @@ -51,7 +51,7 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { public deserialize( _instantiationService: IInstantiationService, serializedEditorInput: string - ): WebviewEditorInput { + ): WebviewInput { const data = this.fromJson(serializedEditorInput); return this._webviewService.reviveWebview(generateUuid(), data.viewType, data.title, data.iconPath, data.state, data.options, data.extensionLocation ? { location: data.extensionLocation, @@ -70,7 +70,7 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { }; } - protected toJson(input: WebviewEditorInput): SerializedWebview { + protected toJson(input: WebviewInput): SerializedWebview { return { viewType: input.viewType, title: input.getName(), diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts index f973e6d867..d4c223849f 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts @@ -13,7 +13,7 @@ import { GroupIdentifier } from 'vs/workbench/common/editor'; import { IWebviewService, WebviewOptions, WebviewContentOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; -import { RevivedWebviewEditorInput, WebviewEditorInput } from './webviewEditorInput'; +import { RevivedWebviewEditorInput, WebviewInput } from './webviewEditorInput'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { EditorActivation } from 'vs/platform/editor/common/editor'; @@ -38,7 +38,7 @@ export interface IWebviewEditorService { location: URI, id: ExtensionIdentifier }, - ): WebviewEditorInput; + ): WebviewInput; reviveWebview( id: string, @@ -52,10 +52,10 @@ export interface IWebviewEditorService { readonly id?: ExtensionIdentifier }, group: number | undefined - ): WebviewEditorInput; + ): WebviewInput; revealWebview( - webview: WebviewEditorInput, + webview: WebviewInput, group: IEditorGroup, preserveFocus: boolean ): void; @@ -65,21 +65,21 @@ export interface IWebviewEditorService { ): IDisposable; shouldPersist( - input: WebviewEditorInput + input: WebviewInput ): boolean; resolveWebview( - webview: WebviewEditorInput, + webview: WebviewInput, ): Promise; } export interface WebviewResolve { canResolve( - webview: WebviewEditorInput, + webview: WebviewInput, ): boolean; resolveWebview( - webview: WebviewEditorInput, + webview: WebviewInput, ): Promise; } @@ -99,7 +99,7 @@ export function areWebviewInputOptionsEqual(a: WebviewInputOptions, b: WebviewIn && (a.portMapping === b.portMapping || (Array.isArray(a.portMapping) && Array.isArray(b.portMapping) && equals(a.portMapping, b.portMapping, (a, b) => a.extensionHostPort === b.extensionHostPort && a.webviewPort === b.webviewPort))); } -function canRevive(reviver: WebviewResolve, webview: WebviewEditorInput): boolean { +function canRevive(reviver: WebviewResolve, webview: WebviewInput): boolean { if (webview.isDisposed()) { return false; } @@ -107,9 +107,9 @@ function canRevive(reviver: WebviewResolve, webview: WebviewEditorInput): boolea } class RevivalPool { - private _awaitingRevival: Array<{ input: WebviewEditorInput, resolve: () => void }> = []; + private _awaitingRevival: Array<{ input: WebviewInput, resolve: () => void }> = []; - public add(input: WebviewEditorInput, resolve: () => void) { + public add(input: WebviewInput, resolve: () => void) { this._awaitingRevival.push({ input, resolve }); } @@ -147,10 +147,10 @@ export class WebviewEditorService implements IWebviewEditorService { location: URI, id: ExtensionIdentifier }, - ): WebviewEditorInput { + ): WebviewInput { const webview = this.createWebiew(id, extension, options); - const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, id, viewType, title, extension, new UnownedDisposable(webview), undefined); + const webviewInput = this._instantiationService.createInstance(WebviewInput, id, viewType, title, extension, new UnownedDisposable(webview), undefined); this._editorService.openEditor(webviewInput, { pinned: true, preserveFocus: showOptions.preserveFocus, @@ -162,7 +162,7 @@ export class WebviewEditorService implements IWebviewEditorService { } public revealWebview( - webview: WebviewEditorInput, + webview: WebviewInput, group: IEditorGroup, preserveFocus: boolean ): void { @@ -193,11 +193,11 @@ export class WebviewEditorService implements IWebviewEditorService { readonly id: ExtensionIdentifier }, group: number | undefined, - ): WebviewEditorInput { + ): WebviewInput { const webview = this.createWebiew(id, extension, options); webview.state = state; - const webviewInput = new RevivedWebviewEditorInput(id, viewType, title, extension, async (webview: WebviewEditorInput): Promise => { + const webviewInput = new RevivedWebviewEditorInput(id, viewType, title, extension, async (webview: WebviewInput): Promise => { const didRevive = await this.tryRevive(webview); if (didRevive) { return Promise.resolve(undefined); @@ -230,7 +230,7 @@ export class WebviewEditorService implements IWebviewEditorService { } public shouldPersist( - webview: WebviewEditorInput + webview: WebviewInput ): boolean { // Has no state, don't persist if (!webview.webview.state) { @@ -247,7 +247,7 @@ export class WebviewEditorService implements IWebviewEditorService { } private async tryRevive( - webview: WebviewEditorInput + webview: WebviewInput ): Promise { for (const reviver of values(this._revivers)) { if (canRevive(reviver, webview)) { @@ -259,7 +259,7 @@ export class WebviewEditorService implements IWebviewEditorService { } public async resolveWebview( - webview: WebviewEditorInput, + webview: WebviewInput, ): Promise { const didRevive = await this.tryRevive(webview); if (!didRevive) { diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index ac92123a9a..5b5ccd2719 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -6,11 +6,11 @@ import { addClass, addDisposableListener } from 'vs/base/browser/dom'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; -import product from 'vs/platform/product/browser/product'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { Webview, WebviewContentOptions, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; @@ -19,7 +19,6 @@ import { WebviewPortMappingManager } from 'vs/workbench/contrib/webview/common/p import { loadLocalResource } from 'vs/workbench/contrib/webview/common/resourceLoader'; import { getWebviewThemeData } from 'vs/workbench/contrib/webview/common/themeing'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { isWeb } from 'vs/base/common/platform'; interface WebviewContent { readonly html: string; @@ -48,8 +47,8 @@ export class IFrameWebview extends Disposable implements Webview { @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); - if (!this.useExternalEndpoint && (!environmentService.options || typeof environmentService.options.webviewEndpoint !== 'string')) { - throw new Error('To use iframe based webviews, you must configure `environmentService.webviewEndpoint`'); + if (!this.useExternalEndpoint && (!environmentService.options || typeof environmentService.webviewExternalEndpoint !== 'string')) { + throw new Error('To use iframe based webviews, you must configure `environmentService.webviewExternalEndpoint`'); } this._portMappingManager = this._register(new WebviewPortMappingManager( @@ -66,7 +65,7 @@ export class IFrameWebview extends Disposable implements Webview { this.element = document.createElement('iframe'); this.element.sandbox.add('allow-scripts', 'allow-same-origin'); - this.element.setAttribute('src', `${this.endpoint}/index.html?id=${this.id}`); + this.element.setAttribute('src', `${this.externalEndpoint}/index.html?id=${this.id}`); this.element.style.border = 'none'; this.element.style.width = '100%'; this.element.style.height = '100%'; @@ -144,24 +143,14 @@ export class IFrameWebview extends Disposable implements Webview { this._register(themeService.onThemeChange(this.style, this)); } - private get endpoint(): string { - const baseEndpoint = this.externalEndpoint || this.environmentService.options!.webviewEndpoint!; - const endpoint = baseEndpoint.replace('{{uuid}}', this.id); + private get externalEndpoint(): string { + const endpoint = this.environmentService.webviewExternalEndpoint!.replace('{{uuid}}', this.id); if (endpoint[endpoint.length - 1] === '/') { return endpoint.slice(0, endpoint.length - 1); } return endpoint; } - private get externalEndpoint(): string | undefined { - const useExternalEndpoint = this.useExternalEndpoint; - if (!useExternalEndpoint) { - return undefined; - } - const commit = product.quality && product.commit ? product.commit : '211fa02efe8c041fd7baa8ec3dce199d5185aa44'; - return `https://{{uuid}}.vscode-webview-test.com/${commit}`; - } - private get useExternalEndpoint(): boolean { return isWeb || this._configurationService.getValue('webview.experimental.useExternalEndpoint'); } @@ -196,7 +185,7 @@ export class IFrameWebview extends Disposable implements Webview { private preprocessHtml(value: string): string { return value.replace(/(["'])vscode-resource:([^\s'"]+?)(["'])/gi, (_, startQuote, path, endQuote) => - `${startQuote}${this.endpoint}/vscode-resource${path}${endQuote}`); + `${startQuote}${this.externalEndpoint}/vscode-resource${path}${endQuote}`); } public update(html: string, options: WebviewContentOptions, retainContextWhenHidden: boolean) { @@ -216,7 +205,7 @@ export class IFrameWebview extends Disposable implements Webview { contents: this.content.html, options: this.content.options, state: this.content.state, - endpoint: this.endpoint, + endpoint: this.externalEndpoint, }); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js b/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js index 07f7d132d5..59edaba961 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js +++ b/src/vs/workbench/contrib/webview/electron-browser/pre/electron-index.js @@ -13,15 +13,6 @@ return; } hasRegistered = true; - - // @ts-ignore - require('electron').webFrame.registerURLSchemeAsPrivileged('vscode-resource', { - secure: true, - bypassCSP: false, - allowServiceWorkers: false, - supportFetchAPI: true, - corsEnabled: true - }); }; }()); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts similarity index 100% rename from src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution.ts rename to src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts similarity index 87% rename from src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.ts rename to src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts index bae4f70075..2e09732554 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts @@ -8,9 +8,9 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import * as platform from 'vs/base/common/platform'; -import product from 'vs/platform/product/node/product'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; +import { IProductService } from 'vs/platform/product/common/productService'; export class GettingStarted implements IWorkbenchContribution { @@ -23,23 +23,20 @@ export class GettingStarted implements IWorkbenchContribution { @IStorageService private readonly storageService: IStorageService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IOpenerService private readonly openerService: IOpenerService + @IOpenerService private readonly openerService: IOpenerService, + @IProductService productService: IProductService ) { - this.appName = product.nameLong; + this.appName = productService.nameLong; + this.welcomePageURL = productService.welcomePage; - if (!product.welcomePage) { + if ( + !productService.welcomePage || + environmentService.skipGettingStarted || + environmentService.isExtensionDevelopment + ) { return; } - if (environmentService.skipGettingStarted) { - return; - } - - if (environmentService.isExtensionDevelopment) { - return; - } - - this.welcomePageURL = product.welcomePage; this.handleWelcome(); } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts similarity index 89% rename from src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts rename to src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts index 5ffcaf7a6c..43a4709709 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts @@ -6,7 +6,6 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import product from 'vs/platform/product/node/product'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { URI } from 'vs/base/common/uri'; @@ -18,6 +17,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { language, locale } from 'vs/base/common/platform'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class TelemetryOptOut implements IWorkbenchContribution { @@ -30,18 +31,20 @@ export class TelemetryOptOut implements IWorkbenchContribution { @INotificationService private readonly notificationService: INotificationService, @IWindowService windowService: IWindowService, @IWindowsService windowsService: IWindowsService, + @IHostService hostService: IHostService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IExperimentService private readonly experimentService: IExperimentService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IProductService productService: IProductService ) { - if (!product.telemetryOptOutUrl || storageService.get(TelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, StorageScope.GLOBAL)) { + if (!productService.telemetryOptOutUrl || storageService.get(TelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, StorageScope.GLOBAL)) { return; } const experimentId = 'telemetryOptOut'; Promise.all([ windowService.isFocused(), - windowsService.getWindowCount(), + hostService.windowCount, experimentService.getExperimentById(experimentId) ]).then(([focused, count, experimentState]) => { if (!focused && count > 1) { @@ -49,17 +52,17 @@ export class TelemetryOptOut implements IWorkbenchContribution { } storageService.store(TelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, true, StorageScope.GLOBAL); - this.privacyUrl = product.privacyStatementUrl || product.telemetryOptOutUrl; + this.privacyUrl = productService.privacyStatementUrl || productService.telemetryOptOutUrl; if (experimentState && experimentState.state === ExperimentState.Run && telemetryService.isOptedIn) { this.runExperiment(experimentId); return; } - const telemetryOptOutUrl = product.telemetryOptOutUrl; + const telemetryOptOutUrl = productService.telemetryOptOutUrl; if (telemetryOptOutUrl) { - const optOutNotice = localize('telemetryOptOut.optOutNotice', "Help improve Azure Data Studio by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt out]({1}).", this.privacyUrl, product.telemetryOptOutUrl); // {{SQL CARBON EDIT}} VScode to ads - const optInNotice = localize('telemetryOptOut.optInNotice', "Help improve Azure Data Studio by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt in]({1}).", this.privacyUrl, product.telemetryOptOutUrl); // {{SQL CARBON EDIT}} VScode to ads + const optOutNotice = localize('telemetryOptOut.optOutNotice', "Help improve Azure Data Studio by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt out]({1}).", this.privacyUrl, productService.telemetryOptOutUrl); // {{SQL CARBON EDIT}} VScode to ads + const optInNotice = localize('telemetryOptOut.optInNotice', "Help improve Azure Data Studio by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt in]({1}).", this.privacyUrl, productService.telemetryOptOutUrl); // {{SQL CARBON EDIT}} VScode to ads notificationService.prompt( Severity.Info, diff --git a/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts b/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts index 0d2074a624..4e70f372bc 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts @@ -6,9 +6,6 @@ import { escape } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; -export function used() { -} - export default () => `
diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index f10967ff25..14df4ed1cc 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./welcomePage'; +import 'vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page'; import { URI } from 'vs/base/common/uri'; import * as strings from 'vs/base/common/strings'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -24,7 +25,6 @@ import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { getInstalledExtensions, IExtensionStatus, onExtensionChanged, isKeymapExtension } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, EnablementState, IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { used, setProductQuality } from 'sql/workbench/contrib/welcome/page/browser/az_data_welcome_page'; // {{SQL CARBON EDIT}} - Redirect to ADS welcome page import { ILifecycleService, StartupKind } from 'vs/platform/lifecycle/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle'; import { splitName } from 'vs/base/common/labels'; @@ -43,8 +43,7 @@ import { joinPath } from 'vs/base/common/resources'; import { IRecentlyOpened, isRecentWorkspace, IRecentWorkspace, IRecentFolder, isRecentFolder } from 'vs/platform/history/common/history'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; // {{SQL CARBON EDIT}} - -used(); +import { setProductQuality } from 'sql/workbench/contrib/welcome/page/browser/az_data_welcome_page'; // {{SQL CARBON EDIT}} const configurationKey = 'workbench.startupEditor'; const oldConfigurationKey = 'workbench.welcome.enabled'; diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts index 6aaaf9dfe9..191176302e 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough'; import { localize } from 'vs/nls'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Action } from 'vs/base/common/actions'; @@ -17,7 +18,10 @@ const inputOptions: WalkThroughInputOptions = { typeId, name: localize('editorWalkThrough.title', "Interactive Playground"), resource: URI.parse(require.toUrl('./vs_code_editor_walkthrough.md')) - .with({ scheme: Schemas.walkThrough }), + .with({ + scheme: Schemas.walkThrough, + query: JSON.stringify({ moduleId: 'vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough' }) + }), telemetryFrom: 'walkThrough' }; diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.md b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.ts similarity index 83% rename from src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.md rename to src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.ts index 1f306f79ef..b791a8bf95 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.md +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.ts @@ -1,3 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export default () => ` ## Interactive Editor Playground The core editor in VS Code is packed with features. This page highlights a number of them and lets you interactively try them out through the use of a number of embedded editors. For full details on the editor features for VS Code and more head over to our [documentation](command:workbench.action.openDocumentationUrl). @@ -16,34 +22,34 @@ The core editor in VS Code is packed with features. This page highlights a numb ### Multi-Cursor Editing Using multiple cursors allows you to edit multiple parts of the document at once, greatly improving your productivity. Try the following actions in the code block below: -1. Box Selection - press any combination of kb(cursorColumnSelectDown), kb(cursorColumnSelectRight), kb(cursorColumnSelectUp), kb(cursorColumnSelectLeft) to select a block of text. You can also press `⇧⌥``Shift+Alt` while selecting text with the mouse or drag-select using the middle mouse button. +1. Box Selection - press any combination of kb(cursorColumnSelectDown), kb(cursorColumnSelectRight), kb(cursorColumnSelectUp), kb(cursorColumnSelectLeft) to select a block of text. You can also press |⇧⌥||Shift+Alt| while selecting text with the mouse or drag-select using the middle mouse button. 2. Add a cursor - press kb(editor.action.insertCursorAbove) to add a new cursor above, or kb(editor.action.insertCursorBelow) to add a new cursor below. You can also use your mouse with +Click to add a cursor anywhere. -3. Create cursors on all occurrences of a string - select one instance of a string e.g. `background-color` and press kb(editor.action.selectHighlights). Now you can replace all instances by simply typing. +3. Create cursors on all occurrences of a string - select one instance of a string e.g. |background-color| and press kb(editor.action.selectHighlights). Now you can replace all instances by simply typing. That is the tip of the iceberg for multi-cursor editing. Have a look at the selection menu and our handy [keyboard reference guide](command:workbench.action.keybindingsReference) for additional actions. -```css +|||css #p1 {background-color: #ff0000;} /* red in HEX format */ #p2 {background-color: hsl(120, 100%, 50%);} /* green in HSL format */ #p3 {background-color: rgba(0, 4, 255, 0.733);} /* blue with alpha channel in RGBA format */ -``` +||| -> **CSS Tip:** you may have noticed in the example above we also provide color swatches inline for CSS, additionally if you hover over an element such as `#p1` we will show how this is represented in HTML. These swatches also act as color pickers that allow you to easily change a color value. A simple example of some language-specific editor features. +> **CSS Tip:** you may have noticed in the example above we also provide color swatches inline for CSS, additionally if you hover over an element such as |#p1| we will show how this is represented in HTML. These swatches also act as color pickers that allow you to easily change a color value. A simple example of some language-specific editor features. ### IntelliSense Visual Studio Code comes with the powerful IntelliSense for JavaScript and TypeScript pre-installed. In the below example, position the text cursor in front of the error underline, right after the dot and press kb(editor.action.triggerSuggest) to invoke IntelliSense. Notice how the suggestion comes from the Request API. -```js +|||js const express = require('express'); const app = express(); app.get('/', (req, res) => { - res.send(`Hello ${req.}`); + res.send(|Hello \${req.}|); }); app.listen(3000); -``` +||| >**Tip:** while we ship JavaScript and TypeScript support out of the box other languages can be upgraded with better IntelliSense through one of the many [extensions](command:workbench.extensions.action.showPopularExtensions). @@ -54,22 +60,22 @@ Since it's very common to work with the entire text in a line we provide a set o 2. Move an entire line or selection of lines up or down with kb(editor.action.moveLinesUpAction) and kb(editor.action.moveLinesDownAction) respectively. 3. Delete the entire line with kb(editor.action.deleteLines). -```json +|||json { - "name": "John", - "age": 31, - "city": "New York" + "name": "John", + "age": 31, + "city": "New York" } -``` +||| >**Tip:** Another very common task is to comment out a block of code - you can toggle commenting by pressing kb(editor.action.commentLine). ### Rename Refactoring -It's easy to rename a symbol such as a function name or variable name. Hit kb(editor.action.rename) while in the symbol `Book` to rename all instances - this will occur across all files in a project. You can also see refactoring in the right-click context menu. +It's easy to rename a symbol such as a function name or variable name. Hit kb(editor.action.rename) while in the symbol |Book| to rename all instances - this will occur across all files in a project. You can also see refactoring in the right-click context menu. -```js +|||js // Reference the function new Book("War of the Worlds", "H G Wells"); new Book("The Martian", "Andy Weir"); @@ -84,91 +90,91 @@ function Book(title, author) { this.title = title; this.author = author; } -``` +||| -> **JSDoc Tip:** VS Code's IntelliSense uses JSDoc comments to provide richer suggestions. The types and documentation from JSDoc comments show up when you hover over a reference to `Book` or in IntelliSense when you create a new instance of `Book`. +> **JSDoc Tip:** VS Code's IntelliSense uses JSDoc comments to provide richer suggestions. The types and documentation from JSDoc comments show up when you hover over a reference to |Book| or in IntelliSense when you create a new instance of |Book|. ### Formatting Keeping your code looking great is hard without a good formatter. Luckily it's easy to format content, either for the entire document with kb(editor.action.formatDocument) or for the current selection with kb(editor.action.formatSelection). Both of these options are also available through the right-click context menu. -```js +|||js const cars = ["🚗", "🚙", "🚕"]; for (const car of cars){ - // Drive the car - console.log(`This is the car ${car}`); + // Drive the car + console.log(|This is the car \${car}|); } -``` +||| ->**Tip:** Additional formatters are available in the [extension gallery](command:workbench.extensions.action.showPopularExtensions). Formatting support can also be configured via [settings](command:workbench.action.openGlobalSettings) e.g. enabling `editor.formatOnSave`. +>**Tip:** Additional formatters are available in the [extension gallery](command:workbench.extensions.action.showPopularExtensions). Formatting support can also be configured via [settings](command:workbench.action.openGlobalSettings) e.g. enabling |editor.formatOnSave|. ### Code Folding In a large file it can often be useful to collapse sections of code to increase readability. To do this, you can simply press kb(editor.fold) to fold or press kb(editor.unfold) to unfold the ranges at the current cursor position. Folding can also be done with the +/- icons in the left gutter. To fold all sections use kb(editor.foldAll) or to unfold all use kb(editor.unfoldAll). -```html +|||html
-
-
    -
  • -
  • -
-
-
-

-
+
+
    +
  • +
  • +
+
+
+

+
-``` +||| >**Tip:** Folding is based on indentation and as a result can apply to all languages. Simply indent your code to create a foldable section you can fold a certain number of levels with shortcuts like kb(editor.foldLevel1) through to kb(editor.foldLevel5). ### Errors and Warnings Errors and warnings are highlighted as you edit your code with squiggles. In the sample below you can see a number of syntax errors. By pressing kb(editor.action.marker.nextInFiles) you can navigate across them in sequence and see the detailed error message. As you correct them the squiggles and scrollbar indicators will update. -```js +|||js // This code has a few syntax errors Console.log(add(1, 1.5)); function Add(a : Number, b : Number) : Int { - return a + b; + return a + b; } -``` +||| ### Snippets -You can greatly accelerate your editing through the use of snippets. Simply start typing `try` and select `trycatch` from the suggestion list and press kb(insertSnippet) to create a `try`->`catch` block. Your cursor will be placed on the text `error` for easy editing. If more than one parameter exists then press kb(jumpToNextSnippetPlaceholder) to jump to it. +You can greatly accelerate your editing through the use of snippets. Simply start typing |try| and select |trycatch| from the suggestion list and press kb(insertSnippet) to create a |try|->|catch| block. Your cursor will be placed on the text |error| for easy editing. If more than one parameter exists then press kb(jumpToNextSnippetPlaceholder) to jump to it. -```js +|||js -``` +||| >**Tip:** the [extension gallery](command:workbench.extensions.action.showPopularExtensions) includes snippets for almost every framework and language imaginable. You can also create your own [user-defined snippets](command:workbench.action.openSnippets). ### Emmet -Emmet takes the snippets idea to a whole new level: you can type CSS-like expressions that can be dynamically parsed, and produce output depending on what you type in the abbreviation. Try it by selecting `Emmet: Expand Abbreviation` from the `Edit` menu with the cursor at the end of a valid Emmet abbreviation or snippet and the expansion will occur. +Emmet takes the snippets idea to a whole new level: you can type CSS-like expressions that can be dynamically parsed, and produce output depending on what you type in the abbreviation. Try it by selecting |Emmet: Expand Abbreviation| from the |Edit| menu with the cursor at the end of a valid Emmet abbreviation or snippet and the expansion will occur. -```html +|||html ul>li.item$*5 -``` +||| ->**Tip:** The [Emmet cheat sheet](http://docs.emmet.io/cheat-sheet/) is a great source of Emmet syntax suggestions. To expand Emmet abbreviations and snippets using the `tab` key use the `emmet.triggerExpansionOnTab` [setting](command:workbench.action.openGlobalSettings). Check out the docs on [Emmet in VS Code](https://code.visualstudio.com/docs/editor/emmet) to learn more. +>**Tip:** The [Emmet cheat sheet](http://docs.emmet.io/cheat-sheet/) is a great source of Emmet syntax suggestions. To expand Emmet abbreviations and snippets using the |tab| key use the |emmet.triggerExpansionOnTab| [setting](command:workbench.action.openGlobalSettings). Check out the docs on [Emmet in VS Code](https://code.visualstudio.com/docs/editor/emmet) to learn more. ### JavaScript Type Checking -Sometimes type checking your JavaScript code can help you spot mistakes you might have not caught otherwise. You can run the TypeScript type checker against your existing JavaScript code by simply adding a `// @ts-check` comment to the top of your file. +Sometimes type checking your JavaScript code can help you spot mistakes you might have not caught otherwise. You can run the TypeScript type checker against your existing JavaScript code by simply adding a |// @ts-check| comment to the top of your file. -```js +|||js // @ts-nocheck let easy = true; easy = 42; -``` +||| ->**Tip:** You can also enable the checks workspace or application wide by adding `"javascript.implicitProjectConfig.checkJs": true` to your workspace or user settings and explicitly ignoring files or lines using `// @ts-nocheck` and `// @ts-ignore`. Check out the docs on [JavaScript in VS Code](https://code.visualstudio.com/docs/languages/javascript) to learn more. +>**Tip:** You can also enable the checks workspace or application wide by adding |"javascript.implicitProjectConfig.checkJs": true| to your workspace or user settings and explicitly ignoring files or lines using |// @ts-nocheck| and |// @ts-ignore|. Check out the docs on [JavaScript in VS Code](https://code.visualstudio.com/docs/languages/javascript) to learn more. ## Thanks! @@ -180,3 +186,5 @@ Well if you have got this far then you will have touched on some of the editing That's all for now, Happy Coding! 🎉 + +`.replace(/\|/g, '`'); diff --git a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts index e86280e45e..ee761b57bc 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts @@ -6,46 +6,58 @@ import { URI } from 'vs/base/common/uri'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { ITextModel, DefaultEndOfLine, EndOfLinePreference, ITextBufferFactory } from 'vs/editor/common/model'; +import { ITextModel, DefaultEndOfLine, EndOfLinePreference } from 'vs/editor/common/model'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import * as marked from 'vs/base/common/marked/marked'; import { Schemas } from 'vs/base/common/network'; import { Range } from 'vs/editor/common/core/range'; +import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; + +function requireToContent(resource: URI): Promise { + if (!resource.query) { + throw new Error('Welcome: invalid resource'); + } + + const query = JSON.parse(resource.query); + if (!query.moduleId) { + throw new Error('Welcome: invalid resource'); + } + + const content: Promise = new Promise((resolve, reject) => { + require([query.moduleId], content => { + try { + resolve(content.default()); + } catch (err) { + reject(err); + } + }); + }); + + return content; +} export class WalkThroughContentProvider implements ITextModelContentProvider, IWorkbenchContribution { constructor( @ITextModelService private readonly textModelResolverService: ITextModelService, - @ITextFileService private readonly textFileService: ITextFileService, @IModeService private readonly modeService: IModeService, @IModelService private readonly modelService: IModelService, ) { this.textModelResolverService.registerTextModelContentProvider(Schemas.walkThrough, this); } - public provideTextContent(resource: URI): Promise { - const query = resource.query ? JSON.parse(resource.query) : {}; - const content: Promise = (query.moduleId ? new Promise((resolve, reject) => { - require([query.moduleId], content => { - try { - resolve(content.default()); - } catch (err) { - reject(err); - } - }); - }) : this.textFileService.readStream(URI.file(resource.fsPath)).then(content => content.value)); - return content.then(content => { - let codeEditorModel = this.modelService.getModel(resource); - if (!codeEditorModel) { - codeEditorModel = this.modelService.createModel(content, this.modeService.createByFilepathOrFirstLine(resource), resource); - } else { - this.modelService.updateModel(codeEditorModel, content); - } + public async provideTextContent(resource: URI): Promise { + const content = await requireToContent(resource); - return codeEditorModel; - }); + let codeEditorModel = this.modelService.getModel(resource); + if (!codeEditorModel) { + codeEditorModel = this.modelService.createModel(content, this.modeService.createByFilepathOrFirstLine(resource), resource); + } else { + this.modelService.updateModel(codeEditorModel, content); + } + + return codeEditorModel; } } @@ -53,45 +65,44 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi constructor( @ITextModelService private readonly textModelResolverService: ITextModelService, - @ITextFileService private readonly textFileService: ITextFileService, @IModeService private readonly modeService: IModeService, @IModelService private readonly modelService: IModelService, ) { this.textModelResolverService.registerTextModelContentProvider(Schemas.walkThroughSnippet, this); } - public provideTextContent(resource: URI): Promise { - return this.textFileService.readStream(URI.file(resource.fsPath)).then(content => { - let codeEditorModel = this.modelService.getModel(resource); - if (!codeEditorModel) { - const j = parseInt(resource.fragment); + public async provideTextContent(resource: URI): Promise { + const factory = createTextBufferFactory(await requireToContent(resource)); - let codeSnippet = ''; - let languageName = ''; - let i = 0; - const renderer = new marked.Renderer(); - renderer.code = (code, lang) => { - if (i++ === j) { - codeSnippet = code; - languageName = lang; - } - return ''; - }; + let codeEditorModel = this.modelService.getModel(resource); + if (!codeEditorModel) { + const j = parseInt(resource.fragment); - const textBuffer = content.value.create(DefaultEndOfLine.LF); - const lineCount = textBuffer.getLineCount(); - const range = new Range(1, 1, lineCount, textBuffer.getLineLength(lineCount) + 1); - const markdown = textBuffer.getValueInRange(range, EndOfLinePreference.TextDefined); - marked(markdown, { renderer }); + let codeSnippet = ''; + let languageName = ''; + let i = 0; + const renderer = new marked.Renderer(); + renderer.code = (code, lang) => { + if (i++ === j) { + codeSnippet = code; + languageName = lang; + } + return ''; + }; - const languageId = this.modeService.getModeIdForLanguageName(languageName) || ''; - const languageSelection = this.modeService.create(languageId); - codeEditorModel = this.modelService.createModel(codeSnippet, languageSelection, resource); - } else { - this.modelService.updateModel(codeEditorModel, content.value); - } + const textBuffer = factory.create(DefaultEndOfLine.LF); + const lineCount = textBuffer.getLineCount(); + const range = new Range(1, 1, lineCount, textBuffer.getLineLength(lineCount) + 1); + const markdown = textBuffer.getValueInRange(range, EndOfLinePreference.TextDefined); + marked(markdown, { renderer }); - return codeEditorModel; - }); + const languageId = this.modeService.getModeIdForLanguageName(languageName) || ''; + const languageSelection = this.modeService.create(languageId); + codeEditorModel = this.modelService.createModel(codeSnippet, languageSelection, resource); + } else { + this.modelService.updateModel(codeEditorModel, factory); + } + + return codeEditorModel; } } diff --git a/src/vs/workbench/electron-browser/actions/helpActions.ts b/src/vs/workbench/electron-browser/actions/helpActions.ts deleted file mode 100644 index b3d74d1d04..0000000000 --- a/src/vs/workbench/electron-browser/actions/helpActions.ts +++ /dev/null @@ -1,236 +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 { Action } from 'vs/base/common/actions'; -import * as nls from 'vs/nls'; -import product from 'vs/platform/product/node/product'; -import { isMacintosh, isLinux, language } from 'vs/base/common/platform'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { URI } from 'vs/base/common/uri'; - -export class KeybindingsReferenceAction extends Action { - - static readonly ID = 'workbench.action.keybindingsReference'; - static readonly LABEL = nls.localize('keybindingsReference', "Keyboard Shortcuts Reference"); - - private static readonly URL = isLinux ? product.keyboardShortcutsUrlLinux : isMacintosh ? product.keyboardShortcutsUrlMac : product.keyboardShortcutsUrlWin; - static readonly AVAILABLE = !!KeybindingsReferenceAction.URL; - - constructor( - id: string, - label: string, - @IOpenerService private readonly openerService: IOpenerService - ) { - super(id, label); - } - - run(): Promise { - if (KeybindingsReferenceAction.URL) { - this.openerService.open(URI.parse(KeybindingsReferenceAction.URL)); - } - - return Promise.resolve(); - } -} - -export class OpenDocumentationUrlAction extends Action { - - static readonly ID = 'workbench.action.openDocumentationUrl'; - static readonly LABEL = nls.localize('openDocumentationUrl', "Documentation"); - - private static readonly URL = product.documentationUrl; - static readonly AVAILABLE = !!OpenDocumentationUrlAction.URL; - - constructor( - id: string, - label: string, - @IOpenerService private readonly openerService: IOpenerService - ) { - super(id, label); - } - - run(): Promise { - if (OpenDocumentationUrlAction.URL) { - this.openerService.open(URI.parse(OpenDocumentationUrlAction.URL)); - } - - return Promise.resolve(); - } -} - -export class OpenIntroductoryVideosUrlAction extends Action { - - static readonly ID = 'workbench.action.openIntroductoryVideosUrl'; - static readonly LABEL = nls.localize('openIntroductoryVideosUrl', "Introductory Videos"); - - private static readonly URL = product.introductoryVideosUrl; - static readonly AVAILABLE = !!OpenIntroductoryVideosUrlAction.URL; - - constructor( - id: string, - label: string, - @IOpenerService private readonly openerService: IOpenerService - ) { - super(id, label); - } - - run(): Promise { - if (OpenIntroductoryVideosUrlAction.URL) { - this.openerService.open(URI.parse(OpenIntroductoryVideosUrlAction.URL)); - } - - return Promise.resolve(); - } -} - -export class OpenTipsAndTricksUrlAction extends Action { - - static readonly ID = 'workbench.action.openTipsAndTricksUrl'; - static readonly LABEL = nls.localize('openTipsAndTricksUrl', "Tips and Tricks"); - - private static readonly URL = product.tipsAndTricksUrl; - static readonly AVAILABLE = !!OpenTipsAndTricksUrlAction.URL; - - constructor( - id: string, - label: string, - @IOpenerService private readonly openerService: IOpenerService - ) { - super(id, label); - } - - run(): Promise { - if (OpenTipsAndTricksUrlAction.URL) { - this.openerService.open(URI.parse(OpenTipsAndTricksUrlAction.URL)); - } - - return Promise.resolve(); - } -} - -export class OpenNewsletterSignupUrlAction extends Action { - - static readonly ID = 'workbench.action.openNewsletterSignupUrl'; - static readonly LABEL = nls.localize('newsletterSignup', "Signup for the VS Code Newsletter"); - static readonly AVAILABLE = !!product.newsletterSignupUrl; - - constructor( - id: string, - label: string, - @IOpenerService private readonly openerService: IOpenerService, - @ITelemetryService private readonly telemetryService: ITelemetryService - ) { - super(id, label); - } - - async run(): Promise { - const info = await this.telemetryService.getTelemetryInfo(); - - this.openerService.open(URI.parse(`${product.newsletterSignupUrl}?machineId=${encodeURIComponent(info.machineId)}`)); - } -} - -export class OpenTwitterUrlAction extends Action { - - static readonly ID = 'workbench.action.openTwitterUrl'; - static readonly LABEL = nls.localize('openTwitterUrl', "Join Us on Twitter", product.applicationName); - static readonly AVAILABLE = !!product.twitterUrl; - - constructor( - id: string, - label: string, - @IOpenerService private readonly openerService: IOpenerService - ) { - super(id, label); - } - - run(): Promise { - if (product.twitterUrl) { - this.openerService.open(URI.parse(product.twitterUrl)); - } - - return Promise.resolve(); - } -} - -export class OpenRequestFeatureUrlAction extends Action { - - static readonly ID = 'workbench.action.openRequestFeatureUrl'; - static readonly LABEL = nls.localize('openUserVoiceUrl', "Search Feature Requests"); - static readonly AVAILABLE = !!product.requestFeatureUrl; - - constructor( - id: string, - label: string, - @IOpenerService private readonly openerService: IOpenerService - ) { - super(id, label); - } - - run(): Promise { - if (product.requestFeatureUrl) { - this.openerService.open(URI.parse(product.requestFeatureUrl)); - } - - return Promise.resolve(); - } -} - -export class OpenLicenseUrlAction extends Action { - - static readonly ID = 'workbench.action.openLicenseUrl'; - static readonly LABEL = nls.localize('openLicenseUrl', "View License"); - static readonly AVAILABLE = !!product.licenseUrl; - - constructor( - id: string, - label: string, - @IOpenerService private readonly openerService: IOpenerService - ) { - super(id, label); - } - - run(): Promise { - if (product.licenseUrl) { - if (language) { - const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?'; - this.openerService.open(URI.parse(`${product.licenseUrl}${queryArgChar}lang=${language}`)); - } else { - this.openerService.open(URI.parse(product.licenseUrl)); - } - } - - return Promise.resolve(); - } -} - -export class OpenPrivacyStatementUrlAction extends Action { - - static readonly ID = 'workbench.action.openPrivacyStatementUrl'; - static readonly LABEL = nls.localize('openPrivacyStatement', "Privacy Statement"); - static readonly AVAILABE = !!product.privacyStatementUrl; - - constructor( - id: string, - label: string, - @IOpenerService private readonly openerService: IOpenerService - ) { - super(id, label); - } - - run(): Promise { - if (product.privacyStatementUrl) { - if (language) { - const queryArgChar = product.privacyStatementUrl.indexOf('?') > 0 ? '&' : '?'; - this.openerService.open(URI.parse(`${product.privacyStatementUrl}${queryArgChar}lang=${language}`)); - } else { - this.openerService.open(URI.parse(product.privacyStatementUrl)); - } - } - - return Promise.resolve(); - } -} diff --git a/src/vs/workbench/electron-browser/actions/workspaceActions.ts b/src/vs/workbench/electron-browser/actions/workspaceActions.ts new file mode 100644 index 0000000000..097be36376 --- /dev/null +++ b/src/vs/workbench/electron-browser/actions/workspaceActions.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Action } from 'vs/base/common/actions'; +import * as nls from 'vs/nls'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; +import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; + +export class SaveWorkspaceAsAction extends Action { + + static readonly ID = 'workbench.action.saveWorkspaceAs'; + static LABEL = nls.localize('saveWorkspaceAsAction', "Save Workspace As..."); + + constructor( + id: string, + label: string, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService + + ) { + super(id, label); + } + + async run(): Promise { + const configPathUri = await this.workspaceEditingService.pickNewWorkspacePath(); + if (configPathUri) { + switch (this.contextService.getWorkbenchState()) { + case WorkbenchState.EMPTY: + case WorkbenchState.FOLDER: + const folders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri })); + return this.workspaceEditingService.createAndEnterWorkspace(folders, configPathUri); + case WorkbenchState.WORKSPACE: + return this.workspaceEditingService.saveAndEnterWorkspace(configPathUri); + } + } + } +} + +export class DuplicateWorkspaceInNewWindowAction extends Action { + + static readonly ID = 'workbench.action.duplicateWorkspaceInNewWindow'; + static readonly LABEL = nls.localize('duplicateWorkspaceInNewWindow', "Duplicate Workspace in New Window"); + + constructor( + id: string, + label: string, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, + @IWindowService private readonly windowService: IWindowService, + @IWorkspacesService private readonly workspacesService: IWorkspacesService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + ) { + super(id, label); + } + + async run(): Promise { + const folders = this.workspaceContextService.getWorkspace().folders; + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + + const newWorkspace = await this.workspacesService.createUntitledWorkspace(folders, remoteAuthority); + await this.workspaceEditingService.copyWorkspaceSettings(newWorkspace); + + return this.windowService.openWindow([{ workspaceUri: newWorkspace.configPath }], { forceNewWindow: true }); + } +} + +export class CloseWorkspaceAction extends Action { + + static readonly ID = 'workbench.action.closeFolder'; + static LABEL = nls.localize('closeWorkspace', "Close Workspace"); + + constructor( + id: string, + label: string, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @INotificationService private readonly notificationService: INotificationService, + @IWindowService private readonly windowService: IWindowService + ) { + super(id, label); + } + + run(): Promise { + if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { + this.notificationService.info(nls.localize('noWorkspaceOpened', "There is currently no workspace opened in this instance to close.")); + + return Promise.resolve(undefined); + } + + return this.windowService.closeWorkspace(); + } +} diff --git a/src/vs/workbench/electron-browser/desktop.contribution.ts b/src/vs/workbench/electron-browser/desktop.contribution.ts index 605cfa64a7..aa3da69af5 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -11,15 +11,13 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; -import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction, OpenNewsletterSignupUrlAction } from 'vs/workbench/electron-browser/actions/helpActions'; import { ToggleSharedProcessAction, ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions'; import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, QuickSwitchWindow, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions'; -import { AddRootFolderAction, GlobalRemoveRootFolderAction, SaveWorkspaceAsAction, DuplicateWorkspaceInNewWindowAction, CloseWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; +import { SaveWorkspaceAsAction, DuplicateWorkspaceInNewWindowAction, CloseWorkspaceAction } from 'vs/workbench/electron-browser/actions/workspaceActions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { 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 } from 'vs/workbench/browser/contextkeys'; import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; @@ -34,7 +32,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten (function registerFileActions(): void { const fileCategory = nls.localize('file', "File"); - registry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'File: Close Workspace', fileCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'File: Close Workspace', fileCategory, SupportsWorkspacesContext); })(); // Actions: View @@ -81,10 +79,8 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten (function registerWorkspaceActions(): void { const workspacesCategory = nls.localize('workspaces', "Workspaces"); - registry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory, SupportsWorkspacesContext); - registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory, SupportsWorkspacesContext); - registry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory, SupportsWorkspacesContext); })(); // Actions: macOS Native Tabs @@ -123,47 +119,6 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_I } }); })(); - - // Actions: help - (function registerHelpActions(): void { - const helpCategory = nls.localize('help', "Help"); - - if (KeybindingsReferenceAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(KeybindingsReferenceAction, KeybindingsReferenceAction.ID, KeybindingsReferenceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_R) }), 'Help: Keyboard Shortcuts Reference', helpCategory); - } - - if (OpenDocumentationUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDocumentationUrlAction, OpenDocumentationUrlAction.ID, OpenDocumentationUrlAction.LABEL), 'Help: Documentation', helpCategory); - } - - if (OpenIntroductoryVideosUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenIntroductoryVideosUrlAction, OpenIntroductoryVideosUrlAction.ID, OpenIntroductoryVideosUrlAction.LABEL), 'Help: Introductory Videos', helpCategory); - } - - if (OpenTipsAndTricksUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTipsAndTricksUrlAction, OpenTipsAndTricksUrlAction.ID, OpenTipsAndTricksUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); - } - - if (OpenNewsletterSignupUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNewsletterSignupUrlAction, OpenNewsletterSignupUrlAction.ID, OpenNewsletterSignupUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); - } - - if (OpenTwitterUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTwitterUrlAction, OpenTwitterUrlAction.ID, OpenTwitterUrlAction.LABEL), 'Help: Join Us on Twitter', helpCategory); - } - - if (OpenRequestFeatureUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory); - } - - if (OpenLicenseUrlAction.AVAILABLE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory); - } - - if (OpenPrivacyStatementUrlAction.AVAILABE) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPrivacyStatementUrlAction, OpenPrivacyStatementUrlAction.ID, OpenPrivacyStatementUrlAction.LABEL), 'Help: Privacy Statement', helpCategory); - } - })(); })(); // Menu @@ -186,16 +141,6 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten } }); - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '3_workspace', - command: { - id: ADD_ROOT_FOLDER_COMMAND_ID, - title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") - }, - order: 1, - when: SupportsWorkspacesContext - }); - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '3_workspace', command: { @@ -224,7 +169,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten title: nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace") }, order: 3, - when: WorkbenchStateContext.isEqualTo('workspace') + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), SupportsWorkspacesContext) }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { @@ -275,87 +220,6 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten order: 3 }); - // Help - - if (OpenDocumentationUrlAction.AVAILABLE) { - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '1_welcome', - command: { - id: OpenDocumentationUrlAction.ID, - title: nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation") - }, - order: 3 - }); - } - - /* // {{SQL CARBON EDIT}} - Disable unused menu item - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '1_welcome', - command: { - id: 'update.showCurrentReleaseNotes', - title: nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes") - }, - order: 4 - }); - - // Reference - if (KeybindingsReferenceAction.AVAILABLE) { - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '2_reference', - command: { - id: KeybindingsReferenceAction.ID, - title: nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference") - }, - order: 1 - }); - } - - if (OpenIntroductoryVideosUrlAction.AVAILABLE) { - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '2_reference', - command: { - id: OpenIntroductoryVideosUrlAction.ID, - title: nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos") - }, - order: 2 - }); - } - - if (OpenTipsAndTricksUrlAction.AVAILABLE) { - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '2_reference', - command: { - id: OpenTipsAndTricksUrlAction.ID, - title: nls.localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "Tips and Tri&&cks") - }, - order: 3 - }); - } - - // Feedback - if (OpenTwitterUrlAction.AVAILABLE) { - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '3_feedback', - command: { - id: OpenTwitterUrlAction.ID, - title: nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join Us on Twitter") - }, - order: 1 - }); - } - - if (OpenRequestFeatureUrlAction.AVAILABLE) { - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '3_feedback', - command: { - id: OpenRequestFeatureUrlAction.ID, - title: nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests") - }, - order: 2 - }); - } - */ - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '3_feedback', command: { @@ -365,29 +229,6 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten order: 3 }); - // Legal - if (OpenLicenseUrlAction.AVAILABLE) { - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '4_legal', - command: { - id: OpenLicenseUrlAction.ID, - title: nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License") - }, - order: 1 - }); - } - - if (OpenPrivacyStatementUrlAction.AVAILABE) { - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '4_legal', - command: { - id: OpenPrivacyStatementUrlAction.ID, - title: nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "Privac&&y Statement") - }, - order: 2 - }); - } - // Tools MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { group: '5_tools', diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index 10fab9a16a..dd0daa47d9 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -23,9 +23,9 @@ import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { webFrame } from 'electron'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log'; +import { ConsoleLogService, MultiplexLogService, ILogService, ConsoleLogInMainService } from 'vs/platform/log/common/log'; import { StorageService } from 'vs/platform/storage/node/storageService'; -import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { Schemas } from 'vs/base/common/network'; import { sanitizeFilePath } from 'vs/base/common/extpath'; import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc'; @@ -51,8 +51,8 @@ import { SignService } from 'vs/platform/sign/node/signService'; import { ISignService } from 'vs/platform/sign/common/sign'; import { FileUserDataProvider } from 'vs/workbench/services/userData/common/fileUserDataProvider'; import { basename } from 'vs/base/common/resources'; -import { IProductService } from 'vs/platform/product/common/product'; -import product from 'vs/platform/product/node/product'; +import { IProductService } from 'vs/platform/product/common/productService'; +import product from 'vs/platform/product/common/product'; class CodeRendererMain extends Disposable { @@ -345,12 +345,25 @@ class CodeRendererMain extends Disposable { } private createLogService(mainProcessService: IMainProcessService, environmentService: IWorkbenchEnvironmentService): ILogService { - const spdlogService = new SpdLogService(`renderer${this.environmentService.configuration.windowId}`, environmentService.logsPath, this.environmentService.configuration.logLevel); - const consoleLogService = new ConsoleLogService(this.environmentService.configuration.logLevel); - const logService = new MultiplexLogService([consoleLogService, spdlogService]); - const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel')); + const loggerClient = new LoggerChannelClient(mainProcessService.getChannel('logger')); - return new FollowerLogService(logLevelClient, logService); + // Extension development test CLI: forward everything to main side + const loggers: ILogService[] = []; + if (environmentService.isExtensionDevelopment && !!environmentService.extensionTestsLocationURI) { + loggers.push( + new ConsoleLogInMainService(loggerClient, this.environmentService.configuration.logLevel) + ); + } + + // Normal logger: spdylog and console + else { + loggers.push( + new ConsoleLogService(this.environmentService.configuration.logLevel), + new SpdLogService(`renderer${this.environmentService.configuration.windowId}`, environmentService.logsPath, this.environmentService.configuration.logLevel) + ); + } + + return new FollowerLogService(loggerClient, new MultiplexLogService(loggers)); } } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 5631818262..5b5ae4db09 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -19,12 +19,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IWorkbenchThemeService, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as browser from 'vs/base/browser/browser'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import { ipcRenderer as ipc, webFrame, crashReporter, Event } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -32,9 +32,8 @@ import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecyc import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; -import { isRootUser, isWindows, isMacintosh, isLinux, isWeb } from 'vs/base/common/platform'; -import product from 'vs/platform/product/node/product'; -import pkg from 'vs/platform/product/node/package'; +import { isRootUser, isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; +import product from 'vs/platform/product/common/product'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -56,6 +55,9 @@ import { IMenubarService, IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenub import { withNullAsUndefined } from 'vs/base/common/types'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Schemas } from 'vs/base/common/network'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { posix, dirname } from 'vs/base/common/path'; +import { getBaseLabel } from 'vs/base/common/labels'; const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), undefined, true, () => Promise.resolve(document.execCommand('undo'))), @@ -74,6 +76,8 @@ export class ElectronWindow extends Disposable { private readonly touchBarDisposables = this._register(new DisposableStore()); private lastInstalledTouchedBar: ICommandAction[][] | undefined; + private customTitleContextMenuDisposable = this._register(new DisposableStore()); + private previousConfiguredZoomLevel: number | undefined; private addFoldersScheduler: RunOnceScheduler; @@ -103,7 +107,8 @@ export class ElectronWindow extends Disposable { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ITextFileService private readonly textFileService: ITextFileService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IOpenerService private readonly openerService: IOpenerService + @IOpenerService private readonly openerService: IOpenerService, + @IElectronService private readonly electronService: IElectronService ) { super(); @@ -244,6 +249,11 @@ export class ElectronWindow extends Disposable { this._register(this.trackClosedWaitFiles(waitMarkerFile, resourcesToWaitFor)); } + + // macOS custom title menu + if (isMacintosh) { + this._register(this.editorService.onDidActiveEditorChange(() => this.provideCustomTitleContextMenu())); + } } private onDidVisibleEditorsChange(): void { @@ -307,6 +317,43 @@ export class ElectronWindow extends Disposable { } } + private provideCustomTitleContextMenu(): void { + + // Clear old menu + this.customTitleContextMenuDisposable.clear(); + + // Provide new menu if a file is opened and we are on a custom title + const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file }); + if (!fileResource || getTitleBarStyle(this.configurationService, this.environmentService) !== 'custom') { + return; + } + + // Split up filepath into segments + const filePath = fileResource.fsPath; + const segments = filePath.split(posix.sep); + for (let i = segments.length; i > 0; i--) { + const isFile = (i === segments.length); + + let pathOffset = i; + if (!isFile) { + pathOffset++; // for segments which are not the file name we want to open the folder + } + + const path = segments.slice(0, pathOffset).join(posix.sep); + + let label: string; + if (!isFile) { + label = getBaseLabel(dirname(path)); + } else { + label = getBaseLabel(path); + } + + const commandId = `workbench.action.revealPathInFinder${i}`; + this.customTitleContextMenuDisposable.add(CommandsRegistry.registerCommand(commandId, () => this.electronService.showItemInFolder(path))); + this.customTitleContextMenuDisposable.add(MenuRegistry.appendMenuItem(MenuId.TitleBarContext, { command: { id: commandId, title: label || posix.sep }, order: -i })); + } + } + private create(): void { // Native menu controller @@ -372,7 +419,7 @@ export class ElectronWindow extends Disposable { const success = await $this.windowsService.openExternal(encodeURI(resource.toString(true))); if (!success && resource.scheme === Schemas.file) { // if opening failed, and this is a file, we can still try to reveal it - await $this.windowsService.showItemInFolder(resource); + await $this.electronService.showItemInFolder(resource.fsPath); } return true; @@ -460,7 +507,7 @@ export class ElectronWindow extends Disposable { productName, submitURL: isWindows ? hockeyAppConfig[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? hockeyAppConfig[`linux-x64`] : hockeyAppConfig.darwin, extra: { - vscode_version: pkg.version, + vscode_version: product.version, vscode_commit: product.commit } }; @@ -615,7 +662,7 @@ class NativeMenubarControl extends MenubarControl { environmentService, accessibilityService); - if (isMacintosh && !isWeb) { + if (isMacintosh) { this.menus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService)); this.topLevelTitles['Preferences'] = nls.localize('mPreferences', "Preferences"); } diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 35888ab8f2..1c9ab98412 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -337,7 +337,7 @@ export class WorkspaceConfiguration extends Disposable { setFolders(folders: IStoredWorkspaceFolder[], jsonEditingService: JSONEditingService): Promise { if (this._workspaceIdentifier) { - return jsonEditingService.write(this._workspaceIdentifier.configPath, { key: 'folders', value: folders }, true) + return jsonEditingService.write(this._workspaceIdentifier.configPath, [{ key: 'folders', value: folders }], true) .then(() => this.reload()); } return Promise.resolve(); diff --git a/src/vs/workbench/services/configuration/common/jsonEditing.ts b/src/vs/workbench/services/configuration/common/jsonEditing.ts index 4f1240d555..e6994e1a35 100644 --- a/src/vs/workbench/services/configuration/common/jsonEditing.ts +++ b/src/vs/workbench/services/configuration/common/jsonEditing.ts @@ -36,5 +36,5 @@ export interface IJSONEditingService { _serviceBrand: undefined; - write(resource: URI, value: IJSONValue, save: boolean): Promise; + write(resource: URI, values: IJSONValue[], save: boolean): Promise; } diff --git a/src/vs/workbench/services/configuration/common/jsonEditingService.ts b/src/vs/workbench/services/configuration/common/jsonEditingService.ts index 77e425fe91..ad91a72bb7 100644 --- a/src/vs/workbench/services/configuration/common/jsonEditingService.ts +++ b/src/vs/workbench/services/configuration/common/jsonEditingService.ts @@ -35,20 +35,24 @@ export class JSONEditingService implements IJSONEditingService { this.queue = new Queue(); } - write(resource: URI, value: IJSONValue, save: boolean): Promise { - return Promise.resolve(this.queue.queue(() => this.doWriteConfiguration(resource, value, save))); // queue up writes to prevent race conditions + write(resource: URI, values: IJSONValue[], save: boolean): Promise { + return Promise.resolve(this.queue.queue(() => this.doWriteConfiguration(resource, values, save))); // queue up writes to prevent race conditions } - private async doWriteConfiguration(resource: URI, value: IJSONValue, save: boolean): Promise { + private async doWriteConfiguration(resource: URI, values: IJSONValue[], save: boolean): Promise { const reference = await this.resolveAndValidate(resource, save); - await this.writeToBuffer(reference.object.textEditorModel, value); + await this.writeToBuffer(reference.object.textEditorModel, values); reference.dispose(); } - private async writeToBuffer(model: ITextModel, value: IJSONValue): Promise { - const edit = this.getEdits(model, value)[0]; - if (this.applyEditsToBuffer(edit, model)) { + private async writeToBuffer(model: ITextModel, values: IJSONValue[]): Promise { + let hasEdits: boolean = false; + for (const value of values) { + const edit = this.getEdits(model, value)[0]; + hasEdits = this.applyEditsToBuffer(edit, model); + } + if (hasEdits) { return this.textFileService.save(model.uri); } } @@ -133,4 +137,4 @@ export class JSONEditingService implements IJSONEditingService { } } -registerSingleton(IJSONEditingService, JSONEditingService, true); \ No newline at end of file +registerSingleton(IJSONEditingService, JSONEditingService, true); diff --git a/src/vs/workbench/services/configuration/node/configurationExportHelper.ts b/src/vs/workbench/services/configuration/node/configurationExportHelper.ts index 04d9c2eee8..eccadbcfed 100644 --- a/src/vs/workbench/services/configuration/node/configurationExportHelper.ts +++ b/src/vs/workbench/services/configuration/node/configurationExportHelper.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { writeFile } from 'vs/base/node/pfs'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationNode, IConfigurationRegistry, Extensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index a0574238d0..a0e4c658a0 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -1161,21 +1161,21 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED test('application settings are not read from workspace', () => { fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.applicationSetting": "userValue" }'); - return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, { key: 'settings', value: { 'configurationService.workspace.applicationSetting': 'workspaceValue' } }, true) + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'settings', value: { 'configurationService.workspace.applicationSetting': 'workspaceValue' } }], true) .then(() => testObject.reloadConfiguration()) .then(() => assert.equal(testObject.getValue('configurationService.workspace.applicationSetting'), 'userValue')); }); test('machine settings are not read from workspace', () => { fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.machineSetting": "userValue" }'); - return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, { key: 'settings', value: { 'configurationService.workspace.machineSetting': 'workspaceValue' } }, true) + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'settings', value: { 'configurationService.workspace.machineSetting': 'workspaceValue' } }], true) .then(() => testObject.reloadConfiguration()) .then(() => assert.equal(testObject.getValue('configurationService.workspace.machineSetting'), 'userValue')); }); test('workspace settings override user settings after defaults are registered ', () => { fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.newSetting": "userValue" }'); - return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, { key: 'settings', value: { 'configurationService.workspace.newSetting': 'workspaceValue' } }, true) + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'settings', value: { 'configurationService.workspace.newSetting': 'workspaceValue' } }], true) .then(() => testObject.reloadConfiguration()) .then(() => { configurationRegistry.registerConfiguration({ @@ -1194,7 +1194,7 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED test('workspace settings override user settings after defaults are registered for machine overridable settings ', () => { fs.writeFileSync(globalSettingsFile, '{ "configurationService.workspace.newMachineOverridableSetting": "userValue" }'); - return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, { key: 'settings', value: { 'configurationService.workspace.newMachineOverridableSetting': 'workspaceValue' } }, true) + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'settings', value: { 'configurationService.workspace.newMachineOverridableSetting': 'workspaceValue' } }], true) .then(() => testObject.reloadConfiguration()) .then(() => { configurationRegistry.registerConfiguration({ @@ -1268,7 +1268,7 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED test('resource setting in folder is read after it is registered later', () => { fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.testNewResourceSetting2": "workspaceFolderValue" }'); - return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, { key: 'settings', value: { 'configurationService.workspace.testNewResourceSetting2': 'workspaceValue' } }, true) + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'settings', value: { 'configurationService.workspace.testNewResourceSetting2': 'workspaceValue' } }], true) .then(() => testObject.reloadConfiguration()) .then(() => { configurationRegistry.registerConfiguration({ @@ -1288,7 +1288,7 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED test('machine overridable setting in folder is read after it is registered later', () => { fs.writeFileSync(workspaceContextService.getWorkspace().folders[0].toResource('.vscode/settings.json').fsPath, '{ "configurationService.workspace.testNewMachineOverridableSetting2": "workspaceFolderValue" }'); - return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, { key: 'settings', value: { 'configurationService.workspace.testNewMachineOverridableSetting2': 'workspaceValue' } }, true) + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'settings', value: { 'configurationService.workspace.testNewMachineOverridableSetting2': 'workspaceValue' } }], true) .then(() => testObject.reloadConfiguration()) .then(() => { configurationRegistry.registerConfiguration({ @@ -1331,7 +1331,7 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED assert.equal(actual.workspaceFolder, undefined); assert.equal(actual.value, 'userValue'); - return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, { key: 'settings', value: { 'configurationService.workspace.testResourceSetting': 'workspaceValue' } }, true) + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'settings', value: { 'configurationService.workspace.testResourceSetting': 'workspaceValue' } }], true) .then(() => testObject.reloadConfiguration()) .then(() => { actual = testObject.inspect('configurationService.workspace.testResourceSetting'); @@ -1373,7 +1373,7 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED } ] }; - return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, { key: 'launch', value: expectedLaunchConfiguration }, true) + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'launch', value: expectedLaunchConfiguration }], true) .then(() => testObject.reloadConfiguration()) .then(() => { const actual = testObject.getValue('launch'); @@ -1398,7 +1398,7 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED } ] }; - return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, { key: 'launch', value: expectedLaunchConfiguration }, true) + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'launch', value: expectedLaunchConfiguration }], true) .then(() => testObject.reloadConfiguration()) .then(() => { const actual = testObject.inspect('launch').workspace; @@ -1496,7 +1496,7 @@ suite.skip('WorkspaceConfigurationService-Multiroot', () => { // {{SQL CARBON ED }); test('task configurations are not read from workspace', () => { - return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, { key: 'tasks', value: { 'version': '1.0' } }, true) + return jsonEditingServce.write(workspaceContextService.getWorkspace().configuration!, [{ key: 'tasks', value: { 'version': '1.0' } }], true) .then(() => testObject.reloadConfiguration()) .then(() => { const actual = testObject.inspect('tasks.version'); diff --git a/src/vs/workbench/services/credentials/browser/credentialsService.ts b/src/vs/workbench/services/credentials/browser/credentialsService.ts index 30db14a5ea..8303a976fa 100644 --- a/src/vs/workbench/services/credentials/browser/credentialsService.ts +++ b/src/vs/workbench/services/credentials/browser/credentialsService.ts @@ -3,14 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; +import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export interface ICredentialsProvider { getPassword(service: string, account: string): Promise; setPassword(service: string, account: string, password: string): Promise; + deletePassword(service: string, account: string): Promise; + findPassword(service: string): Promise; findCredentials(service: string): Promise>; } diff --git a/src/vs/workbench/services/credentials/node/credentialsService.ts b/src/vs/workbench/services/credentials/node/credentialsService.ts index d6c20bdf9a..d20bad0457 100644 --- a/src/vs/workbench/services/credentials/node/credentialsService.ts +++ b/src/vs/workbench/services/credentials/node/credentialsService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICredentialsService } from 'vs/workbench/services/credentials/common/credentials'; +import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { IdleValue } from 'vs/base/common/async'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; diff --git a/src/vs/platform/dialogs/browser/dialogService.ts b/src/vs/workbench/services/dialogs/browser/dialogService.ts similarity index 82% rename from src/vs/platform/dialogs/browser/dialogService.ts rename to src/vs/workbench/services/dialogs/browser/dialogService.ts index 975de2e60a..5509c5d3e0 100644 --- a/src/vs/platform/dialogs/browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/dialogService.ts @@ -15,8 +15,12 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventHelper } from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class DialogService implements IDialogService { + _serviceBrand: undefined; private allowableCommands = ['copy', 'cut']; @@ -25,7 +29,9 @@ export class DialogService implements IDialogService { @ILogService private readonly logService: ILogService, @ILayoutService private readonly layoutService: ILayoutService, @IThemeService private readonly themeService: IThemeService, - @IKeybindingService private readonly keybindingService: IKeybindingService + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IProductService private readonly productService: IProductService, + @IClipboardService private readonly clipboardService: IClipboardService ) { } async confirm(confirmation: IConfirmation): Promise { @@ -113,4 +119,22 @@ export class DialogService implements IDialogService { checkboxChecked: result.checkboxChecked }; } + + async about(): Promise { + const detail = nls.localize('aboutDetail', + "Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}", + this.productService.version || 'Unknown', + this.productService.commit || 'Unknown', + this.productService.date || 'Unknown', + navigator.userAgent + ); + + const { choice } = await this.show(Severity.Info, this.productService.nameLong, [nls.localize('copy', "Copy"), nls.localize('ok', "OK")], { detail }); + + if (choice === 0) { + this.clipboardService.writeText(detail); + } + } } + +registerSingleton(IDialogService, DialogService, true); diff --git a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts index fb56e8531d..798daf5773 100644 --- a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts @@ -23,7 +23,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { equalsIgnoreCase, format, startsWithIgnoreCase } from 'vs/base/common/strings'; -import { OpenLocalFileCommand, OpenLocalFileFolderCommand, OpenLocalFolderCommand, SaveLocalFileCommand } 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'; @@ -32,6 +31,60 @@ import { Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ICommandHandler } from 'vs/platform/commands/common/commands'; +import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { toResource } from 'vs/workbench/common/editor'; + +export namespace OpenLocalFileCommand { + export const ID = 'workbench.action.files.openLocalFile'; + export const LABEL = nls.localize('openLocalFile', "Open Local File..."); + export function handler(): ICommandHandler { + return accessor => { + const dialogService = accessor.get(IFileDialogService); + return dialogService.pickFileAndOpen({ forceNewWindow: false, availableFileSystems: [Schemas.file] }); + }; + } +} + +export namespace SaveLocalFileCommand { + export const ID = 'workbench.action.files.saveLocalFile'; + export const LABEL = nls.localize('saveLocalFile', "Save Local File..."); + export function handler(): ICommandHandler { + return accessor => { + const textFileService = accessor.get(ITextFileService); + const editorService = accessor.get(IEditorService); + let resource: URI | undefined = toResource(editorService.activeEditor); + const options: ISaveOptions = { force: true, availableFileSystems: [Schemas.file] }; + if (resource) { + return textFileService.saveAs(resource, undefined, options); + } + return Promise.resolve(undefined); + }; + } +} + +export namespace OpenLocalFolderCommand { + export const ID = 'workbench.action.files.openLocalFolder'; + export const LABEL = nls.localize('openLocalFolder', "Open Local Folder..."); + export function handler(): ICommandHandler { + return accessor => { + const dialogService = accessor.get(IFileDialogService); + return dialogService.pickFolderAndOpen({ forceNewWindow: false, availableFileSystems: [Schemas.file] }); + }; + } +} + +export namespace OpenLocalFileFolderCommand { + export const ID = 'workbench.action.files.openLocalFileFolder'; + export const LABEL = nls.localize('openLocalFileFolder', "Open Local..."); + export function handler(): ICommandHandler { + return accessor => { + const dialogService = accessor.get(IFileDialogService); + return dialogService.pickFileFolderAndOpen({ forceNewWindow: false, availableFileSystems: [Schemas.file] }); + }; + } +} interface FileQuickPickItem extends IQuickPickItem { uri: URI; diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts index 0f34cfaaa1..982ba620c0 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts @@ -4,21 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import product from 'vs/platform/product/node/product'; +import * as os from 'os'; +import product from 'vs/platform/product/common/product'; import Severity from 'vs/base/common/severity'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IDialogService, IConfirmation, IConfirmationResult, IDialogOptions, IShowResult } from 'vs/platform/dialogs/common/dialogs'; -import { DialogService as HTMLDialogService } from 'vs/platform/dialogs/browser/dialogService'; +import { DialogService as HTMLDialogService } from 'vs/workbench/services/dialogs/browser/dialogService'; import { ILogService } from 'vs/platform/log/common/log'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; -import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc'; +import { DialogChannel } from 'vs/platform/dialogs/electron-browser/dialogIpc'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IElectronService } from 'vs/platform/electron/node/electron'; interface IMassagedMessageBoxOptions { @@ -36,6 +40,7 @@ interface IMassagedMessageBoxOptions { } export class DialogService implements IDialogService { + _serviceBrand: undefined; private impl: IDialogService; @@ -47,25 +52,33 @@ export class DialogService implements IDialogService { @IThemeService themeService: IThemeService, @IWindowService windowService: IWindowService, @ISharedProcessService sharedProcessService: ISharedProcessService, - @IKeybindingService keybindingService: IKeybindingService + @IKeybindingService keybindingService: IKeybindingService, + @IProductService productService: IProductService, + @IClipboardService clipboardService: IClipboardService, + @IElectronService electronService: IElectronService ) { // Use HTML based dialogs if (configurationService.getValue('workbench.dialogs.customEnabled') === true) { - this.impl = new HTMLDialogService(logService, layoutService, themeService, keybindingService); + this.impl = new HTMLDialogService(logService, layoutService, themeService, keybindingService, productService, clipboardService); } // Electron dialog service else { - this.impl = new NativeDialogService(windowService, logService, sharedProcessService); + this.impl = new NativeDialogService(windowService, logService, sharedProcessService, electronService, clipboardService); } } confirm(confirmation: IConfirmation): Promise { return this.impl.confirm(confirmation); } + show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions | undefined): Promise { return this.impl.show(severity, message, buttons, options); } + + about(): Promise { + return this.impl.about(); + } } class NativeDialogService implements IDialogService { @@ -75,7 +88,9 @@ class NativeDialogService implements IDialogService { constructor( @IWindowService private readonly windowService: IWindowService, @ILogService private readonly logService: ILogService, - @ISharedProcessService sharedProcessService: ISharedProcessService + @ISharedProcessService sharedProcessService: ISharedProcessService, + @IElectronService private readonly electronService: IElectronService, + @IClipboardService private readonly clipboardService: IClipboardService ) { sharedProcessService.registerChannel('dialog', new DialogChannel(this)); } @@ -189,6 +204,51 @@ class NativeDialogService implements IDialogService { return { options, buttonIndexMap }; } + + async about(): Promise { + let version = product.version; + if (product.target) { + version = `${version} (${product.target} setup)`; + } + + const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION; + const detail = nls.localize('aboutDetail', // {{SQL CARBON EDIT}} update about dialog + "Version: {0}\nCommit: {1}\nDate: {2}\nVS Code: {8}\nElectron: {3}\nChrome: {4}\nNode.js: {5}\nV8: {6}\nOS: {7}", + version, + product.commit || 'Unknown', + product.date || 'Unknown', + process.versions['electron'], + process.versions['chrome'], + process.versions['node'], + process.versions['v8'], + `${os.type()} ${os.arch()} ${os.release()}${isSnap ? ' snap' : ''}`, + product.vscodeVersion + ); + + const ok = nls.localize('okButton', "OK"); + const copy = mnemonicButtonLabel(nls.localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy")); + let buttons: string[]; + if (isLinux) { + buttons = [copy, ok]; + } else { + buttons = [ok, copy]; + } + + const result = await this.electronService.showMessageBox({ + title: product.nameLong, + type: 'info', + message: product.nameLong, + detail: `\n${detail}`, + buttons, + noLink: true, + defaultId: buttons.indexOf(ok), + cancelId: buttons.indexOf(ok) + }); + + if (buttons[result.response] === copy) { + this.clipboardService.writeText(detail); + } + } } registerSingleton(IDialogService, DialogService, true); diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index f706be9631..ae07deca71 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -3,18 +3,19 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWindowConfiguration, IPath, IPathsToWaitFor } from 'vs/platform/windows/common/windows'; -import { IExtensionHostDebugParams, IDebugParams, BACKUPS } from 'vs/platform/environment/common/environment'; -import { URI } from 'vs/base/common/uri'; -import { IProcessEnvironment } from 'vs/base/common/platform'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { ExportData } from 'vs/base/common/performance'; -import { LogLevel } from 'vs/platform/log/common/log'; -import { joinPath } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; +import { ExportData } from 'vs/base/common/performance'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { joinPath } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { BACKUPS, IDebugParams, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; +import { LogLevel } from 'vs/platform/log/common/log'; +import { IPath, IPathsToWaitFor, IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkbenchConstructionOptions } from 'vs/workbench/workbench.web.api'; -import { generateUuid } from 'vs/base/common/uuid'; +import product from 'vs/platform/product/common/product'; export class BrowserWindowConfiguration implements IWindowConfiguration { @@ -83,6 +84,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment this.configuration.machineId = generateUuid(); this.userRoamingDataHome = URI.file('/User').with({ scheme: Schemas.userData }); this.settingsResource = joinPath(this.userRoamingDataHome, 'settings.json'); + this.settingsSyncPreviewResource = joinPath(this.userRoamingDataHome, '.settings.json'); this.keybindingsResource = joinPath(this.userRoamingDataHome, 'keybindings.json'); this.keyboardLayoutResource = joinPath(this.userRoamingDataHome, 'keyboardLayout.json'); this.localeResource = joinPath(this.userRoamingDataHome, 'locale.json'); @@ -140,6 +142,7 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment appSettingsHome: URI; userRoamingDataHome: URI; settingsResource: URI; + settingsSyncPreviewResource: URI; keybindingsResource: URI; keyboardLayoutResource: URI; localeResource: URI; @@ -178,12 +181,19 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment galleryMachineIdResource?: URI; readonly logFile: URI; + get webviewExternalEndpoint(): string { + // TODO: get fallback from product.json + return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}') + .replace('{{commit}}', product.commit || '211fa02efe8c041fd7baa8ec3dce199d5185aa44'); + } + get webviewResourceRoot(): string { - return this.options.webviewEndpoint ? `${this.options.webviewEndpoint}/vscode-resource{{resource}}` : 'vscode-resource:{{resource}}'; + return `${this.webviewExternalEndpoint}/vscode-resource{{resource}}`; } get webviewCspSource(): string { - return this.options.webviewEndpoint ? this.options.webviewEndpoint : 'vscode-resource:'; + return this.webviewExternalEndpoint + .replace('{{uuid}}', '*'); } } diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts index 4682637295..a1ab511ae9 100644 --- a/src/vs/workbench/services/environment/common/environmentService.ts +++ b/src/vs/workbench/services/environment/common/environmentService.ts @@ -24,6 +24,7 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService { readonly debugSearch: IDebugParams; + readonly webviewExternalEndpoint: string; readonly webviewResourceRoot: string; readonly webviewCspSource: string; diff --git a/src/vs/workbench/services/environment/node/environmentService.ts b/src/vs/workbench/services/environment/node/environmentService.ts index e59ba5d06b..0aa16f3054 100644 --- a/src/vs/workbench/services/environment/node/environmentService.ts +++ b/src/vs/workbench/services/environment/node/environmentService.ts @@ -12,11 +12,17 @@ import { Schemas } from 'vs/base/common/network'; import { toBackupWorkspaceResource } from 'vs/workbench/services/backup/common/backup'; import { join } from 'vs/base/common/path'; import { IDebugParams } from 'vs/platform/environment/common/environment'; +import product from 'vs/platform/product/common/product'; export class WorkbenchEnvironmentService extends EnvironmentService implements IWorkbenchEnvironmentService { _serviceBrand: undefined; + get webviewExternalEndpoint(): string { + const baseEndpoint = 'https://{{uuid}}.vscode-webview-test.com/{{commit}}'; + return baseEndpoint.replace('{{commit}}', product.commit || '211fa02efe8c041fd7baa8ec3dce199d5185aa44'); + } + readonly webviewResourceRoot = 'vscode-resource:{{resource}}'; readonly webviewCspSource = 'vscode-resource:'; diff --git a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts index 51afdf2086..1ede8267a2 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts @@ -17,7 +17,7 @@ import { ExtensionType, IExtension } from 'vs/platform/extensions/common/extensi import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; const DISABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/disabled'; const ENABLED_EXTENSIONS_STORAGE_PATH = 'extensionsIdentifiers/enabled'; @@ -377,4 +377,4 @@ class StorageManager extends Disposable { } } -registerSingleton(IExtensionEnablementService, ExtensionEnablementService, true); \ No newline at end of file +registerSingleton(IExtensionEnablementService, ExtensionEnablementService, true); diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index b905413cdd..6d23174711 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -16,7 +16,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localize } from 'vs/nls'; import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { Schemas } from 'vs/base/common/network'; import { IDownloadService } from 'vs/platform/download/common/download'; diff --git a/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts index 3d02ce9ffb..1902a29fcb 100644 --- a/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService.ts @@ -17,7 +17,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { RemoteExtensionManagementChannelClient } from 'vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { ILabelService } from 'vs/platform/label/common/label'; const localExtensionManagementServerAuthority: string = 'vscode-local'; @@ -63,4 +63,4 @@ export class ExtensionManagementServerService implements IExtensionManagementSer } } -registerSingleton(IExtensionManagementServerService, ExtensionManagementServerService); \ No newline at end of file +registerSingleton(IExtensionManagementServerService, ExtensionManagementServerService); diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index dcb29119b8..bc43981dbd 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -12,7 +12,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { AbstractExtensionService } from 'vs/workbench/services/extensions/common/abstractExtensionService'; import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager'; import { RemoteExtensionHostClient, IInitDataProvider } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient'; diff --git a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts index 66b906eb23..6d6e839588 100644 --- a/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts +++ b/src/vs/workbench/services/extensions/browser/webWorkerExtensionHostStarter.ts @@ -9,7 +9,7 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { VSBuffer } from 'vs/base/common/buffer'; import { createMessageOfType, MessageType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; -import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; +import { IInitData, UIKind } from 'vs/workbench/api/common/extHost.protocol'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -18,7 +18,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class WebWorkerExtensionHostStarter implements IExtensionHostStarter { @@ -151,6 +151,7 @@ export class WebWorkerExtensionHostStarter implements IExtensionHostStarter { authority: this._environmentService.configuration.remoteAuthority, isRemote: false }, + uiKind: platform.isWeb ? UIKind.Web : UIKind.Desktop }; } } diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 9226810981..8ab0417334 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -23,7 +23,7 @@ import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/co import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; const hasOwnProperty = Object.hasOwnProperty; const NO_OP_VOID_PROMISE = Promise.resolve(undefined); diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index d4d1459b75..3aff7c62a1 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -267,6 +267,11 @@ export const schema = { body: 'onView:${5:viewId}', description: nls.localize('vscode.extension.activationEvents.onView', 'An activation event emitted whenever the specified view is expanded.'), }, + { + label: 'onIdentity', + body: 'onIdentity:${8:identity}', + description: nls.localize('vscode.extension.activationEvents.onIdentity', 'An activation event emitted whenever the specified user identity.'), + }, { label: 'onUri', body: 'onUri', diff --git a/src/vs/workbench/services/extensions/common/extensionsUtil.ts b/src/vs/workbench/services/extensions/common/extensionsUtil.ts index d54748d0e0..043f5e2837 100644 --- a/src/vs/workbench/services/extensions/common/extensionsUtil.ts +++ b/src/vs/workbench/services/extensions/common/extensionsUtil.ts @@ -8,7 +8,7 @@ import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { getGalleryExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; export function isWebExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean { const extensionKind = getExtensionKind(manifest, configurationService); diff --git a/src/vs/workbench/services/extensions/common/remoteConsoleUtil.ts b/src/vs/workbench/services/extensions/common/remoteConsoleUtil.ts new file mode 100644 index 0000000000..96c5da0760 --- /dev/null +++ b/src/vs/workbench/services/extensions/common/remoteConsoleUtil.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRemoteConsoleLog, parse } from 'vs/base/common/console'; +import { ILogService } from 'vs/platform/log/common/log'; + +export function logRemoteEntry(logService: ILogService, entry: IRemoteConsoleLog): void { + const args = parse(entry).args; + const firstArg = args.shift(); + if (typeof firstArg !== 'string') { + return; + } + + if (!entry.severity) { + entry.severity = 'info'; + } + + switch (entry.severity) { + case 'log': + case 'info': + logService.info(firstArg, ...args); + break; + case 'warn': + logService.warn(firstArg, ...args); + break; + case 'error': + logService.error(firstArg, ...args); + break; + } +} diff --git a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts index 637b7c0660..1026b8259e 100644 --- a/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts +++ b/src/vs/workbench/services/extensions/common/remoteExtensionHostClient.ts @@ -11,7 +11,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { connectRemoteAgentExtensionHost, IRemoteExtensionHostStartParams, IConnectionOptions, ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; +import { IInitData, UIKind } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; @@ -25,7 +25,7 @@ import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { VSBuffer } from 'vs/base/common/buffer'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { ISignService } from 'vs/platform/sign/common/sign'; export interface IInitDataProvider { @@ -216,6 +216,7 @@ export class RemoteExtensionHostClient extends Disposable implements IExtensionH logLevel: this._logService.getLevel(), logsLocation: remoteExtensionHostData.extensionHostLogsPath, autoStart: true, + uiKind: platform.isWeb ? UIKind.Web : UIKind.Desktop }; return r; }); diff --git a/src/vs/workbench/services/extensions/common/staticExtensions.ts b/src/vs/workbench/services/extensions/common/staticExtensions.ts index c12d8c9753..7ba961cd94 100644 --- a/src/vs/workbench/services/extensions/common/staticExtensions.ts +++ b/src/vs/workbench/services/extensions/common/staticExtensions.ts @@ -5,7 +5,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionDescription, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { URI } from 'vs/base/common/uri'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -27,7 +26,7 @@ export class StaticExtensionsService implements IStaticExtensionsService { this._descriptions = staticExtensions.map(data => { identifier: new ExtensionIdentifier(`${data.packageJSON.publisher}.${data.packageJSON.name}`), - extensionLocation: URI.revive(data.extensionLocation), + extensionLocation: data.extensionLocation, ...data.packageJSON, }); } diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index d7a847e09b..6cd22d2073 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -17,8 +17,7 @@ import * as pfs from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE, ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import pkg from 'vs/platform/product/node/package'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, IRelaxedExtensionDescription } from 'vs/workbench/services/extensions/node/extensionPoints'; @@ -68,7 +67,7 @@ export class CachedExtensionScanner { public async scanSingleExtension(path: string, isBuiltin: boolean, log: ILog): Promise { const translations = await this.translationConfig; - const version = pkg.version; + const version = product.version; const commit = product.commit; const devMode = !!process.env['VSCODE_DEV']; const locale = platform.language; @@ -243,7 +242,7 @@ export class CachedExtensionScanner { translations: Translations ): Promise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> { - const version = pkg.version; + const version = product.version; const commit = product.commit; const devMode = !!process.env['VSCODE_DEV']; const locale = platform.language; diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 8a02af3982..bd49669f73 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -13,9 +13,9 @@ import { Emitter, Event } from 'vs/base/common/event'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; -import pkg from 'vs/platform/product/node/package'; import { URI } from 'vs/base/common/uri'; -import { IRemoteConsoleLog, log, parse } from 'vs/base/common/console'; +import { IRemoteConsoleLog, log } from 'vs/base/common/console'; +import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil'; import { findFreePort, randomPort } from 'vs/base/node/ports'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; @@ -24,12 +24,12 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { ILabelService } from 'vs/platform/label/common/label'; import { ILifecycleService, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; +import { IInitData, UIKind } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -68,7 +68,6 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { private readonly _extensionHostLogsLocation: URI, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @INotificationService private readonly _notificationService: INotificationService, - @IWindowsService private readonly _windowsService: IWindowsService, @IWindowService private readonly _windowService: IWindowService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @@ -388,7 +387,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { const workspace = this._contextService.getWorkspace(); const r: IInitData = { commit: product.commit, - version: pkg.version, + version: product.version, vscodeVersion: product.vscodeVersion, // {{SQL CARBON EDIT}} add vscode version parentPid: process.pid, environment: { @@ -421,7 +420,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { telemetryInfo, logLevel: this._logService.getLevel(), logsLocation: this._extensionHostLogsLocation, - autoStart: this._autoStart + autoStart: this._autoStart, + uiKind: UIKind.Desktop }; return r; }); @@ -436,7 +436,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { // Log on main side if running tests from cli if (this._isExtensionDevTestFromCli) { - this._windowsService.log(entry.severity, parse(entry).args); + logRemoteEntry(this._logService, entry); } // Broadcast to other windows if we are in development mode diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 42115130ac..3fe1fcdbcc 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -32,7 +32,7 @@ import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } import { Schemas } from 'vs/base/common/network'; import { IFileService } from 'vs/platform/files/common/files'; import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { Logger } from 'vs/workbench/services/extensions/common/extensionPoints'; import { flatten } from 'vs/base/common/arrays'; import { IStaticExtensionsService } from 'vs/workbench/services/extensions/common/staticExtensions'; diff --git a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts index b85e5295b5..3e74d33c9c 100644 --- a/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts +++ b/src/vs/workbench/services/extensions/electron-browser/remoteExtensionManagementIpc.ts @@ -16,7 +16,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { values } from 'vs/base/common/map'; import { CancellationToken } from 'vs/base/common/cancellation'; import { localize } from 'vs/nls'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 4c9fa62687..042f465e43 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -11,7 +11,7 @@ import { Event } from 'vs/base/common/event'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { PersistentProtocol, ProtocolConstants, BufferedEmitter } from 'vs/base/parts/ipc/common/ipc.net'; import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { IInitData } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { ExtensionHostMain, IExitFn } from 'vs/workbench/services/extensions/common/extensionHostMain'; diff --git a/src/vs/workbench/services/extensions/worker/extHost.services.ts b/src/vs/workbench/services/extensions/worker/extHost.services.ts index 4b6481be30..de6124dc48 100644 --- a/src/vs/workbench/services/extensions/worker/extHost.services.ts +++ b/src/vs/workbench/services/extensions/worker/extHost.services.ts @@ -11,7 +11,7 @@ import { IExtHostConfiguration, ExtHostConfiguration } from 'vs/workbench/api/co import { IExtHostCommands, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { IExtHostTerminalService, WorkerExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; -import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; +import { IExtHostTask, WorkerExtHostTask } from 'vs/workbench/api/common/extHostTask'; import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; @@ -49,7 +49,7 @@ function NotImplementedProxy(name: ServiceIdentifier): { new(): T } { }; } registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService); -registerSingleton(IExtHostTask, class extends NotImplementedProxy(IExtHostTask) { }); +registerSingleton(IExtHostTask, WorkerExtHostTask); registerSingleton(IExtHostDebugService, class extends NotImplementedProxy(IExtHostDebugService) { }); registerSingleton(IExtHostSearch, class extends NotImplementedProxy(IExtHostSearch) { }); registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy(IExtensionStoragePaths) { diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts new file mode 100644 index 0000000000..9b2ce6b2ad --- /dev/null +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class BrowserHostService implements IHostService { + + _serviceBrand: undefined; + + //#region Window + + readonly windowCount = Promise.resolve(1); + + //#endregion +} + +registerSingleton(IHostService, BrowserHostService, true); diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts new file mode 100644 index 0000000000..02fa64f309 --- /dev/null +++ b/src/vs/workbench/services/host/browser/host.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IHostService = createDecorator('hostService'); + +export interface IHostService { + + _serviceBrand: undefined; + + //#region Window + + /** + * The number of windows that belong to the current client session. + */ + readonly windowCount: Promise; + + //#endregion +} diff --git a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts new file mode 100644 index 0000000000..e1f3c3b6a5 --- /dev/null +++ b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class DesktopHostService implements IHostService { + + _serviceBrand: undefined; + + constructor(@IElectronService private readonly electronService: IElectronService) { } + + //#region Window + + get windowCount() { return this.electronService.windowCount(); } + + //#endregion +} + +registerSingleton(IHostService, DesktopHostService, true); diff --git a/src/vs/workbench/services/integrity/node/integrityService.ts b/src/vs/workbench/services/integrity/node/integrityService.ts index 756340e270..90414ab786 100644 --- a/src/vs/workbench/services/integrity/node/integrityService.ts +++ b/src/vs/workbench/services/integrity/node/integrityService.ts @@ -10,7 +10,7 @@ import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { ChecksumPair, IIntegrityService, IntegrityTestResult } from 'vs/workbench/services/integrity/common/integrity'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index c6676efd0a..aaa710d096 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -557,7 +557,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.textFileService.read(workspaceConfig) .then(content => { if (Object.keys(parse(content.value)).indexOf('settings') === -1) { - return this.jsonEditingService.write(resource, { key: 'settings', value: {} }, true).then(undefined, () => { }); + return this.jsonEditingService.write(resource, [{ key: 'settings', value: {} }], true).then(undefined, () => { }); } return undefined; }); diff --git a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts index e124d390ae..e31c39fb98 100644 --- a/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/browser/remoteAgentServiceImpl.ts @@ -7,7 +7,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { AbstractRemoteAgentService, RemoteAgentConnection } from 'vs/workbench/services/remote/common/abstractRemoteAgentService'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { IWebSocketFactory, BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory'; import { ISignService } from 'vs/platform/sign/common/sign'; import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; diff --git a/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts index 8ede3c2029..881997341c 100644 --- a/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts @@ -7,7 +7,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; import { AbstractRemoteAgentService, RemoteAgentConnection } from 'vs/workbench/services/remote/common/abstractRemoteAgentService'; import { ISignService } from 'vs/platform/sign/common/sign'; diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index 055dbb7817..e7e94e847f 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -8,7 +8,7 @@ import { Barrier } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import { NodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { connectRemoteAgentTunnel, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 5279cf20d5..7a6f63299c 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -252,7 +252,10 @@ export class TextSearchMatch implements ITextSearchMatch { constructor(text: string, range: ISearchRange | ISearchRange[], previewOptions?: ITextSearchPreviewOptions) { this.ranges = range; - if (previewOptions && previewOptions.matchLines === 1 && (!Array.isArray(range) || range.length === 1)) { + // Trim preview if this is one match and a single-line match with a preview requested. + // Otherwise send the full text, like for replace or for showing multiple previews. + // TODO this is fishy. + if (previewOptions && previewOptions.matchLines === 1 && (!Array.isArray(range) || range.length === 1) && isSingleLineRange(range)) { const oneRange = Array.isArray(range) ? range[0] : range; // 1 line preview requested @@ -273,7 +276,6 @@ export class TextSearchMatch implements ITextSearchMatch { } else { const firstMatchLine = Array.isArray(range) ? range[0].startLineNumber : range.startLineNumber; - // n line, no preview requested, or multiple matches in the preview this.preview = { text, matches: mapArrayOrNot(range, r => new SearchRange(r.startLineNumber - firstMatchLine, r.startColumn, r.endLineNumber - firstMatchLine, r.endColumn)) @@ -282,6 +284,12 @@ export class TextSearchMatch implements ITextSearchMatch { } } +function isSingleLineRange(range: ISearchRange | ISearchRange[]): boolean { + return Array.isArray(range) ? + range[0].startLineNumber === range[0].endLineNumber : + range.startLineNumber === range.endLineNumber; +} + export class SearchRange implements ISearchRange { startLineNumber: number; startColumn: number; diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index c42ab60aa1..d54eb867e2 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -125,7 +125,7 @@ export function rgErrorMsgForDisplay(msg: string): Maybe { const firstLine = lines[0].trim(); if (lines.some(l => startsWith(l, 'regex parse error'))) { - return new SearchError('Regex parse error', SearchErrorCode.regexParseError); + return new SearchError(buildRegexParseError(lines), SearchErrorCode.regexParseError); } const match = firstLine.match(/grep config error: unknown encoding: (.*)/); @@ -150,6 +150,21 @@ export function rgErrorMsgForDisplay(msg: string): Maybe { return undefined; } +export function buildRegexParseError(lines: string[]): string { + let errorMessage: string[] = ['Regex parse error']; + let pcre2ErrorLine = lines.filter(l => (startsWith(l, 'PCRE2:'))); + if (pcre2ErrorLine.length >= 1) { + let pcre2ErrorMessage = pcre2ErrorLine[0].replace('PCRE2:', ''); + if (pcre2ErrorMessage.indexOf(':') !== -1 && pcre2ErrorMessage.split(':').length >= 2) { + let pcre2ActualErrorMessage = pcre2ErrorMessage.split(':')[1]; + errorMessage.push(':' + pcre2ActualErrorMessage); + } + } + + return errorMessage.join(''); +} + + export class RipgrepParser extends EventEmitter { private remainder = ''; private isDone = false; diff --git a/src/vs/workbench/services/search/test/common/search.test.ts b/src/vs/workbench/services/search/test/common/search.test.ts index 5bad58b1fc..9968bca614 100644 --- a/src/vs/workbench/services/search/test/common/search.test.ts +++ b/src/vs/workbench/services/search/test/common/search.test.ts @@ -12,7 +12,7 @@ suite('TextSearchResult', () => { charsPerLine: 100 }; - function assertPreviewRangeText(text: string, result: TextSearchMatch): void { + function assertOneLinePreviewRangeText(text: string, result: TextSearchMatch): void { assert.equal( result.preview.text.substring((result.preview.matches).startColumn, (result.preview.matches).endColumn), text); @@ -22,49 +22,49 @@ suite('TextSearchResult', () => { const range = new OneLineRange(5, 0, 0); const result = new TextSearchMatch('', range); assert.deepEqual(result.ranges, range); - assertPreviewRangeText('', result); + assertOneLinePreviewRangeText('', result); }); test('empty with preview options', () => { const range = new OneLineRange(5, 0, 0); const result = new TextSearchMatch('', range, previewOptions1); assert.deepEqual(result.ranges, range); - assertPreviewRangeText('', result); + assertOneLinePreviewRangeText('', result); }); test('short without preview options', () => { const range = new OneLineRange(5, 4, 7); const result = new TextSearchMatch('foo bar', range); assert.deepEqual(result.ranges, range); - assertPreviewRangeText('bar', result); + assertOneLinePreviewRangeText('bar', result); }); test('short with preview options', () => { const range = new OneLineRange(5, 4, 7); const result = new TextSearchMatch('foo bar', range, previewOptions1); assert.deepEqual(result.ranges, range); - assertPreviewRangeText('bar', result); + assertOneLinePreviewRangeText('bar', result); }); test('leading', () => { const range = new OneLineRange(5, 25, 28); const result = new TextSearchMatch('long text very long text foo', range, previewOptions1); assert.deepEqual(result.ranges, range); - assertPreviewRangeText('foo', result); + assertOneLinePreviewRangeText('foo', result); }); test('trailing', () => { const range = new OneLineRange(5, 0, 3); const result = new TextSearchMatch('foo long text very long text long text very long text long text very long text long text very long text long text very long text', range, previewOptions1); assert.deepEqual(result.ranges, range); - assertPreviewRangeText('foo', result); + assertOneLinePreviewRangeText('foo', result); }); test('middle', () => { const range = new OneLineRange(5, 30, 33); const result = new TextSearchMatch('long text very long text long foo text very long text long text very long text long text very long text long text very long text', range, previewOptions1); assert.deepEqual(result.ranges, range); - assertPreviewRangeText('foo', result); + assertOneLinePreviewRangeText('foo', result); }); test('truncating match', () => { @@ -76,7 +76,7 @@ suite('TextSearchResult', () => { const range = new OneLineRange(0, 4, 7); const result = new TextSearchMatch('foo bar', range, previewOptions); assert.deepEqual(result.ranges, range); - assertPreviewRangeText('b', result); + assertOneLinePreviewRangeText('b', result); }); test('one line of multiline match', () => { @@ -88,7 +88,11 @@ suite('TextSearchResult', () => { const range = new SearchRange(5, 4, 6, 3); const result = new TextSearchMatch('foo bar\nfoo bar', range, previewOptions); assert.deepEqual(result.ranges, range); - assertPreviewRangeText('bar', result); + assert.equal(result.preview.text, 'foo bar\nfoo bar'); + assert.equal((result.preview.matches).startLineNumber, 0); + assert.equal((result.preview.matches).startColumn, 4); + assert.equal((result.preview.matches).endLineNumber, 1); + assert.equal((result.preview.matches).endColumn, 3); }); // test('all lines of multiline match', () => { @@ -102,4 +106,4 @@ suite('TextSearchResult', () => { // assert.deepEqual(result.range, range); // assertPreviewRangeText('bar\nfoo', result); // }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts index f300ea3b2d..7b96bc286f 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -376,7 +376,7 @@ suite('Search-integration', function () { }); }); - test('invalid regex', () => { + test('invalid regex case 1', () => { const config: ITextQuery = { type: QueryType.Text, folderQueries: ROOT_FOLDER_QUERY, @@ -387,11 +387,30 @@ suite('Search-integration', function () { throw new Error('expected fail'); }, err => { const searchError = deserializeSearchError(err.message); - assert.equal(searchError.message, 'Regex parse error'); + let regexParseErrorForUnclosedParenthesis = 'Regex parse error: unmatched closing parenthesis'; + assert.equal(searchError.message, regexParseErrorForUnclosedParenthesis); assert.equal(searchError.code, SearchErrorCode.regexParseError); }); }); + test('invalid regex case 2', () => { + const config: ITextQuery = { + type: QueryType.Text, + folderQueries: ROOT_FOLDER_QUERY, + contentPattern: { pattern: '(? { + throw new Error('expected fail'); + }, err => { + const searchError = deserializeSearchError(err.message); + let regexParseErrorForLookAround = 'Regex parse error: lookbehind assertion is not fixed length'; + assert.equal(searchError.message, regexParseErrorForLookAround); + assert.equal(searchError.code, SearchErrorCode.regexParseError); + }); + }); + + test('invalid glob', () => { const config: ITextQuery = { type: QueryType.Text, diff --git a/src/vs/workbench/services/telemetry/browser/telemetryService.ts b/src/vs/workbench/services/telemetry/browser/telemetryService.ts index f4e28399c1..9d0c92ab2d 100644 --- a/src/vs/workbench/services/telemetry/browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/browser/telemetryService.ts @@ -14,7 +14,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ClassifiedEvent, StrictPropertyCheck, GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/browser/workbenchCommonProperties'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { ApplicationInsights } from '@microsoft/applicationinsights-web'; export class WebTelemetryAppender implements ITelemetryAppender { diff --git a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts index 04746c1296..49781cbd4e 100644 --- a/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-browser/telemetryService.ts @@ -8,7 +8,7 @@ import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IProductService } from 'vs/platform/product/common/product'; +import { IProductService } from 'vs/platform/product/common/productService'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; import { ILogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts new file mode 100644 index 0000000000..b112e5fa7d --- /dev/null +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; +import { ITextFileService, IResourceEncodings, IResourceEncoding, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; + +export class BrowserTextFileService extends AbstractTextFileService { + + readonly encoding: IResourceEncodings = { + getPreferredWriteEncoding(): IResourceEncoding { + return { encoding: 'utf8', hasBOM: false }; + } + }; + + protected onBeforeShutdown(reason: ShutdownReason): boolean { + // Web: we cannot perform long running in the shutdown phase + // As such we need to check sync if there are any dirty files + // that have not been backed up yet and then prevent the shutdown + // if that is the case. + return this.doBeforeShutdownSync(); + } + + private doBeforeShutdownSync(): boolean { + if (this.models.getAll().some(model => model.hasState(ModelState.PENDING_SAVE) || model.hasState(ModelState.PENDING_AUTO_SAVE))) { + return true; // files are pending to be saved: veto + } + + const dirtyResources = this.getDirty(); + if (!dirtyResources.length) { + return false; // no dirty: no veto + } + + if (!this.isHotExitEnabled) { + return true; // dirty without backup: veto + } + + for (const dirtyResource of dirtyResources) { + let hasBackup = false; + + if (this.fileService.canHandleResource(dirtyResource)) { + const model = this.models.get(dirtyResource); + hasBackup = !!(model && model.hasBackup()); + } else if (dirtyResource.scheme === Schemas.untitled) { + hasBackup = this.untitledEditorService.hasBackup(dirtyResource); + } + + if (!hasBackup) { + console.warn('Unload prevented: pending backups'); + return true; // dirty without backup: veto + } + } + + return false; // dirty with backups: no veto + } +} + +registerSingleton(ITextFileService, BrowserTextFileService); diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index db8a8aae02..1033bf5dc0 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -3,60 +3,1066 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; -import { ITextFileService, IResourceEncodings, IResourceEncoding, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import * as nls from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import * as errors from 'vs/base/common/errors'; +import * as objects from 'vs/base/common/objects'; +import { Event, Emitter } from 'vs/base/common/event'; +import * as platform from 'vs/base/common/platform'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor'; +import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IFileService, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions } from 'vs/platform/files/common/files'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { isEqualOrParent, isEqual, joinPath, dirname, extname, basename, toLocalResource } from 'vs/base/common/resources'; +import { getConfirmMessage, IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { coalesce } from 'vs/base/common/arrays'; +import { trim } from 'vs/base/common/strings'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ITextSnapshot } from 'vs/editor/common/model'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; -export class BrowserTextFileService extends TextFileService { +/** + * The workbench file service implementation implements the raw file service spec and adds additional methods on top. + */ +export abstract class AbstractTextFileService extends Disposable implements ITextFileService { - readonly encoding: IResourceEncodings = { - getPreferredWriteEncoding(): IResourceEncoding { - return { encoding: 'utf8', hasBOM: false }; - } - }; + _serviceBrand: undefined; - protected onBeforeShutdown(reason: ShutdownReason): boolean { - // Web: we cannot perform long running in the shutdown phase - // As such we need to check sync if there are any dirty files - // that have not been backed up yet and then prevent the shutdown - // if that is the case. - return this.doBeforeShutdownSync(); + private readonly _onAutoSaveConfigurationChange: Emitter = this._register(new Emitter()); + readonly onAutoSaveConfigurationChange: Event = this._onAutoSaveConfigurationChange.event; + + private readonly _onFilesAssociationChange: Emitter = this._register(new Emitter()); + readonly onFilesAssociationChange: Event = this._onFilesAssociationChange.event; + + private readonly _onWillMove = this._register(new Emitter()); + readonly onWillMove: Event = this._onWillMove.event; + + private _models: TextFileEditorModelManager; + get models(): ITextFileEditorModelManager { return this._models; } + + abstract get encoding(): IResourceEncodings; + + private currentFilesAssociationConfig: { [key: string]: string; }; + private configuredAutoSaveDelay?: number; + private configuredAutoSaveOnFocusChange: boolean | undefined; + private configuredAutoSaveOnWindowChange: boolean | undefined; + private configuredHotExit: string | undefined; + private autoSaveContext: IContextKey; + + constructor( + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IFileService protected readonly fileService: IFileService, + @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IModeService private readonly modeService: IModeService, + @IModelService private readonly modelService: IModelService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @INotificationService private readonly notificationService: INotificationService, + @IBackupFileService private readonly backupFileService: IBackupFileService, + @IHostService private readonly hostService: IHostService, + @IHistoryService private readonly historyService: IHistoryService, + @IContextKeyService contextKeyService: IContextKeyService, + @IDialogService private readonly dialogService: IDialogService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IEditorService private readonly editorService: IEditorService, + @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService + ) { + super(); + + this._models = this._register(instantiationService.createInstance(TextFileEditorModelManager)); + this.autoSaveContext = AutoSaveContext.bindTo(contextKeyService); + + const configuration = configurationService.getValue(); + this.currentFilesAssociationConfig = configuration && configuration.files && configuration.files.associations; + + this.onFilesConfigurationChange(configuration); + + this.registerListeners(); } - private doBeforeShutdownSync(): boolean { - if (this.models.getAll().some(model => model.hasState(ModelState.PENDING_SAVE) || model.hasState(ModelState.PENDING_AUTO_SAVE))) { - return true; // files are pending to be saved: veto - } + //#region event handling - const dirtyResources = this.getDirty(); - if (!dirtyResources.length) { - return false; // no dirty: no veto - } + private registerListeners(): void { - if (!this.isHotExitEnabled) { - return true; // dirty without backup: veto - } + // Lifecycle + this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason))); + this.lifecycleService.onShutdown(this.dispose, this); - for (const dirtyResource of dirtyResources) { - let hasBackup = false; + // Files configuration changes + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('files')) { + this.onFilesConfigurationChange(this.configurationService.getValue()); + } + })); + } - if (this.fileService.canHandleResource(dirtyResource)) { - const model = this.models.get(dirtyResource); - hasBackup = !!(model && model.hasBackup()); - } else if (dirtyResource.scheme === Schemas.untitled) { - hasBackup = this.untitledEditorService.hasBackup(dirtyResource); + protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { + + // Dirty files need treatment on shutdown + const dirty = this.getDirty(); + if (dirty.length) { + + // If auto save is enabled, save all files and then check again for dirty files + // We DO NOT run any save participant if we are in the shutdown phase for performance reasons + if (this.getAutoSaveMode() !== AutoSaveMode.OFF) { + return this.saveAll(false /* files only */, { skipSaveParticipants: true }).then(() => { + + // If we still have dirty files, we either have untitled ones or files that cannot be saved + const remainingDirty = this.getDirty(); + if (remainingDirty.length) { + return this.handleDirtyBeforeShutdown(remainingDirty, reason); + } + + return false; + }); } - if (!hasBackup) { - console.warn('Unload prevented: pending backups'); - return true; // dirty without backup: veto + // Auto save is not enabled + return this.handleDirtyBeforeShutdown(dirty, reason); + } + + // No dirty files: no veto + return this.noVeto({ cleanUpBackups: true }); + } + + private handleDirtyBeforeShutdown(dirty: URI[], reason: ShutdownReason): boolean | Promise { + + // If hot exit is enabled, backup dirty files and allow to exit without confirmation + if (this.isHotExitEnabled) { + return this.backupBeforeShutdown(dirty, reason).then(didBackup => { + if (didBackup) { + return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful) + } + + // since a backup did not happen, we have to confirm for the dirty files now + return this.confirmBeforeShutdown(); + }, error => { + this.notificationService.error(nls.localize('files.backup.failSave', "Files that are dirty could not be written to the backup location (Error: {0}). Try saving your files first and then exit.", error.message)); + + return true; // veto, the backups failed + }); + } + + // Otherwise just confirm from the user what to do with the dirty files + return this.confirmBeforeShutdown(); + } + + private async backupBeforeShutdown(dirtyToBackup: URI[], reason: ShutdownReason): Promise { + const windowCount = await this.hostService.windowCount; + + // When quit is requested skip the confirm callback and attempt to backup all workspaces. + // When quit is not requested the confirm callback should be shown when the window being + // closed is the only VS Code window open, except for on Mac where hot exit is only + // ever activated when quit is requested. + + let doBackup: boolean | undefined; + switch (reason) { + case ShutdownReason.CLOSE: + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + } else if (windowCount > 1 || platform.isMacintosh) { + doBackup = false; // do not backup if a window is closed that does not cause quitting of the application + } else { + doBackup = true; // backup if last window is closed on win/linux where the application quits right after + } + break; + + case ShutdownReason.QUIT: + doBackup = true; // backup because next start we restore all backups + break; + + case ShutdownReason.RELOAD: + doBackup = true; // backup because after window reload, backups restore + break; + + case ShutdownReason.LOAD: + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + } else { + doBackup = false; // do not backup because we are switching contexts + } + break; + } + + if (!doBackup) { + return false; + } + + await this.backupAll(dirtyToBackup); + + return true; + } + + private backupAll(dirtyToBackup: URI[]): Promise { + + // split up between files and untitled + const filesToBackup: ITextFileEditorModel[] = []; + const untitledToBackup: URI[] = []; + dirtyToBackup.forEach(dirty => { + if (this.fileService.canHandleResource(dirty)) { + const model = this.models.get(dirty); + if (model) { + filesToBackup.push(model); + } + } else if (dirty.scheme === Schemas.untitled) { + untitledToBackup.push(dirty); + } + }); + + return this.doBackupAll(filesToBackup, untitledToBackup); + } + + private async doBackupAll(dirtyFileModels: ITextFileEditorModel[], untitledResources: URI[]): Promise { + + // Handle file resources first + await Promise.all(dirtyFileModels.map(model => model.backup())); + + // Handle untitled resources + await Promise.all(untitledResources + .filter(untitled => this.untitledEditorService.exists(untitled)) + .map(async untitled => (await this.untitledEditorService.loadOrCreate({ resource: untitled })).backup())); + } + + private async confirmBeforeShutdown(): Promise { + const confirm = await this.confirmSave(); + + // Save + if (confirm === ConfirmResult.SAVE) { + const result = await this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }); + + if (result.results.some(r => !r.success)) { + return true; // veto if some saves failed + } + + return this.noVeto({ cleanUpBackups: true }); + } + + // Don't Save + else if (confirm === ConfirmResult.DONT_SAVE) { + + // Make sure to revert untitled so that they do not restore + // see https://github.com/Microsoft/vscode/issues/29572 + this.untitledEditorService.revertAll(); + + return this.noVeto({ cleanUpBackups: true }); + } + + // Cancel + else if (confirm === ConfirmResult.CANCEL) { + return true; // veto + } + + return false; + } + + private noVeto(options: { cleanUpBackups: boolean }): boolean | Promise { + if (!options.cleanUpBackups) { + return false; + } + + if (this.lifecycleService.phase < LifecyclePhase.Restored) { + return false; // if editors have not restored, we are not up to speed with backups and thus should not clean them + } + + return this.cleanupBackupsBeforeShutdown().then(() => false, () => false); + } + + protected async cleanupBackupsBeforeShutdown(): Promise { + if (this.environmentService.isExtensionDevelopment) { + return; + } + + await this.backupFileService.discardAllWorkspaceBackups(); + } + + protected onFilesConfigurationChange(configuration: IFilesConfiguration): void { + const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF); + + const autoSaveMode = (configuration && configuration.files && configuration.files.autoSave) || AutoSaveConfiguration.OFF; + this.autoSaveContext.set(autoSaveMode); + switch (autoSaveMode) { + case AutoSaveConfiguration.AFTER_DELAY: + this.configuredAutoSaveDelay = configuration && configuration.files && configuration.files.autoSaveDelay; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = false; + break; + + case AutoSaveConfiguration.ON_FOCUS_CHANGE: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = true; + this.configuredAutoSaveOnWindowChange = false; + break; + + case AutoSaveConfiguration.ON_WINDOW_CHANGE: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = true; + break; + + default: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = false; + break; + } + + // Emit as event + this._onAutoSaveConfigurationChange.fire(this.getAutoSaveConfiguration()); + + // save all dirty when enabling auto save + if (!wasAutoSaveEnabled && this.getAutoSaveMode() !== AutoSaveMode.OFF) { + this.saveAll(); + } + + // Check for change in files associations + const filesAssociation = configuration && configuration.files && configuration.files.associations; + if (!objects.equals(this.currentFilesAssociationConfig, filesAssociation)) { + this.currentFilesAssociationConfig = filesAssociation; + this._onFilesAssociationChange.fire(); + } + + // Hot exit + const hotExitMode = configuration && configuration.files && configuration.files.hotExit; + if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + this.configuredHotExit = hotExitMode; + } else { + this.configuredHotExit = HotExitConfiguration.ON_EXIT; + } + } + + //#endregion + + //#region primitives (read, create, move, delete, update) + + async read(resource: URI, options?: IReadTextFileOptions): Promise { + const content = await this.fileService.readFile(resource, options); + + // in case of acceptTextOnly: true, we check the first + // chunk for possibly being binary by looking for 0-bytes + // we limit this check to the first 512 bytes + this.validateBinary(content.value, options); + + return { + ...content, + encoding: 'utf8', + value: content.value.toString() + }; + } + + async readStream(resource: URI, options?: IReadTextFileOptions): Promise { + const stream = await this.fileService.readFileStream(resource, options); + + // in case of acceptTextOnly: true, we check the first + // chunk for possibly being binary by looking for 0-bytes + // we limit this check to the first 512 bytes + let checkedForBinary = false; + const throwOnBinary = (data: VSBuffer): Error | undefined => { + if (!checkedForBinary) { + checkedForBinary = true; + + this.validateBinary(data, options); + } + + return undefined; + }; + + return { + ...stream, + encoding: 'utf8', + value: await createTextBufferFactoryFromStream(stream.value, undefined, options && options.acceptTextOnly ? throwOnBinary : undefined) + }; + } + + private validateBinary(buffer: VSBuffer, options?: IReadTextFileOptions): void { + if (!options || !options.acceptTextOnly) { + return; // no validation needed + } + + // in case of acceptTextOnly: true, we check the first + // chunk for possibly being binary by looking for 0-bytes + // we limit this check to the first 512 bytes + for (let i = 0; i < buffer.byteLength && i < 512; i++) { + if (buffer.readUInt8(i) === 0) { + throw new TextFileOperationError(nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options); + } + } + } + + async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { + const stat = await this.doCreate(resource, value, options); + + // If we had an existing model for the given resource, load + // it again to make sure it is up to date with the contents + // we just wrote into the underlying resource by calling + // revert() + const existingModel = this.models.get(resource); + if (existingModel && !existingModel.isDisposed()) { + await existingModel.revert(); + } + + return stat; + } + + protected doCreate(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { + return this.fileService.createFile(resource, toBufferOrReadable(value), options); + } + + async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { + return this.fileService.writeFile(resource, toBufferOrReadable(value), options); + } + + async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { + const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource)); + + await this.revertAll(dirtyFiles, { soft: true }); + + return this.fileService.del(resource, options); + } + + async move(source: URI, target: URI, overwrite?: boolean): Promise { + + // await onWillMove event joiners + await this.notifyOnWillMove(source, target); + + // find all models that related to either source or target (can be many if resource is a folder) + const sourceModels: ITextFileEditorModel[] = []; + const conflictingModels: ITextFileEditorModel[] = []; + for (const model of this.getFileModels()) { + const resource = model.getResource(); + + if (isEqualOrParent(resource, target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { + conflictingModels.push(model); + } + + if (isEqualOrParent(resource, source)) { + sourceModels.push(model); } } - return false; // dirty with backups: no veto + // remember each source model to load again after move is done + // with optional content to restore if it was dirty + type ModelToRestore = { resource: URI; snapshot?: ITextSnapshot }; + const modelsToRestore: ModelToRestore[] = []; + for (const sourceModel of sourceModels) { + const sourceModelResource = sourceModel.getResource(); + + // If the source is the actual model, just use target as new resource + let modelToRestoreResource: URI; + if (isEqual(sourceModelResource, source)) { + modelToRestoreResource = target; + } + + // Otherwise a parent folder of the source is being moved, so we need + // to compute the target resource based on that + else { + modelToRestoreResource = joinPath(target, sourceModelResource.path.substr(source.path.length + 1)); + } + + const modelToRestore: ModelToRestore = { resource: modelToRestoreResource }; + if (sourceModel.isDirty()) { + modelToRestore.snapshot = sourceModel.createSnapshot(); + } + + modelsToRestore.push(modelToRestore); + } + + // in order to move, we need to soft revert all dirty models, + // both from the source as well as the target if any + const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty()); + await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.getResource()), { soft: true }); + + // now we can rename the source to target via file operation + let stat: IFileStatWithMetadata; + try { + stat = await this.fileService.move(source, target, overwrite); + } catch (error) { + + // in case of any error, ensure to set dirty flag back + dirtyModels.forEach(dirtyModel => dirtyModel.makeDirty()); + + throw error; + } + + // finally, restore models that we had loaded previously + await Promise.all(modelsToRestore.map(async modelToRestore => { + + // restore the model, forcing a reload. this is important because + // we know the file has changed on disk after the move and the + // model might have still existed with the previous state. this + // ensures we are not tracking a stale state. + const restoredModel = await this.models.loadOrCreate(modelToRestore.resource, { reload: { async: false } }); + + // restore previous dirty content if any and ensure to mark + // the model as dirty + if (modelToRestore.snapshot && restoredModel.isResolved()) { + this.modelService.updateModel(restoredModel.textEditorModel, createTextBufferFactoryFromSnapshot(modelToRestore.snapshot)); + + restoredModel.makeDirty(); + } + })); + + return stat; + } + + private async notifyOnWillMove(source: URI, target: URI): Promise { + const waitForPromises: Promise[] = []; + + // fire event + this._onWillMove.fire({ + oldResource: source, + newResource: target, + waitUntil(promise: Promise) { + waitForPromises.push(promise.then(undefined, errors.onUnexpectedError)); + } + }); + + // prevent async waitUntil-calls + Object.freeze(waitForPromises); + + await Promise.all(waitForPromises); + } + + //#endregion + + //#region save/revert + + async save(resource: URI, options?: ISaveOptions): Promise { + + // Run a forced save if we detect the file is not dirty so that save participants can still run + if (options && options.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) { + const model = this._models.get(resource); + if (model) { + options.reason = SaveReason.EXPLICIT; + + await model.save(options); + + return !model.isDirty(); + } + } + + const result = await this.saveAll([resource], options); + + return result.results.length === 1 && !!result.results[0].success; + } + + async confirmSave(resources?: URI[]): Promise { + if (this.environmentService.isExtensionDevelopment) { + if (!this.environmentService.args['extension-development-confirm-save']) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + } + } + + const resourcesToConfirm = this.getDirty(resources); + if (resourcesToConfirm.length === 0) { + return ConfirmResult.DONT_SAVE; + } + return promptSave(this.dialogService, resourcesToConfirm); + } + + async confirmOverwrite(resource: URI): Promise { + const confirm: IConfirmation = { + message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), + detail: nls.localize('irreversible', "A file or folder with the same name already exists in the folder {0}. Replacing it will overwrite its current contents.", basename(dirname(resource))), + primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), + type: 'warning' + }; + + return (await this.dialogService.confirm(confirm)).confirmed; + } + + saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise; + saveAll(resources: URI[], options?: ISaveOptions): Promise; + saveAll(arg1?: boolean | URI[], options?: ISaveOptions): Promise { + + // get all dirty + let toSave: URI[] = []; + if (Array.isArray(arg1)) { + toSave = this.getDirty(arg1); + } else { + toSave = this.getDirty(); + } + + // split up between files and untitled + const filesToSave: URI[] = []; + const untitledToSave: URI[] = []; + toSave.forEach(resourceToSave => { + if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && resourceToSave.scheme === Schemas.untitled) { + untitledToSave.push(resourceToSave); + } else { + filesToSave.push(resourceToSave); + } + }); + + return this.doSaveAll(filesToSave, untitledToSave, options); + } + + private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ISaveOptions): Promise { + + // Handle files first that can just be saved + const result = await this.doSaveAllFiles(fileResources, options); + + // Preflight for untitled to handle cancellation from the dialog + const targetsForUntitled: URI[] = []; + for (const untitled of untitledResources) { + if (this.untitledEditorService.exists(untitled)) { + let targetUri: URI; + + // Untitled with associated file path don't need to prompt + if (this.untitledEditorService.hasAssociatedFilePath(untitled)) { + targetUri = toLocalResource(untitled, this.environmentService.configuration.remoteAuthority); + } + + // Otherwise ask user + else { + const targetPath = await this.promptForPath(untitled, this.suggestFileName(untitled)); + if (!targetPath) { + return { results: [...fileResources, ...untitledResources].map(r => ({ source: r })) }; + } + + targetUri = targetPath; + } + + targetsForUntitled.push(targetUri); + } + } + + // Handle untitled + await Promise.all(targetsForUntitled.map(async (target, index) => { + const uri = await this.saveAs(untitledResources[index], target); + + result.results.push({ + source: untitledResources[index], + target: uri, + success: !!uri + }); + })); + + return result; + } + + protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { + + // Help user to find a name for the file by opening it first + await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); + + return this.fileDialogService.pickFileToSave(this.getSaveDialogOptions(defaultUri, availableFileSystems)); + } + + private getSaveDialogOptions(defaultUri: URI, availableFileSystems?: string[]): ISaveDialogOptions { + const options: ISaveDialogOptions = { + defaultUri, + title: nls.localize('saveAsTitle', "Save As"), + availableFileSystems, + }; + + // Filters are only enabled on Windows where they work properly + if (!platform.isWindows) { + return options; + } + + interface IFilter { name: string; extensions: string[]; } + + // Build the file filter by using our known languages + const ext: string | undefined = defaultUri ? extname(defaultUri) : undefined; + let matchingFilter: IFilter | undefined; + const filters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => { + const extensions = this.modeService.getExtensions(languageName); + if (!extensions || !extensions.length) { + return null; + } + + const filter: IFilter = { name: languageName, extensions: extensions.slice(0, 10).map(e => trim(e, '.')) }; + + if (ext && extensions.indexOf(ext) >= 0) { + matchingFilter = filter; + + return null; // matching filter will be added last to the top + } + + return filter; + })); + + // Filters are a bit weird on Windows, based on having a match or not: + // Match: we put the matching filter first so that it shows up selected and the all files last + // No match: we put the all files filter first + const allFilesFilter = { name: nls.localize('allFiles', "All Files"), extensions: ['*'] }; + if (matchingFilter) { + filters.unshift(matchingFilter); + filters.unshift(allFilesFilter); + } else { + filters.unshift(allFilesFilter); + } + + // Allow to save file without extension + filters.push({ name: nls.localize('noExt', "No Extension"), extensions: [''] }); + + options.filters = filters; + + return options; + } + + private async doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): Promise { + const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : undefined /* Save All */) + .filter(model => { + if ((model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) { + return false; // if model is in save conflict or error, do not save unless save reason is explicit or not provided at all + } + + return true; + }); + + const mapResourceToResult = new ResourceMap(); + dirtyFileModels.forEach(m => { + mapResourceToResult.set(m.getResource(), { + source: m.getResource() + }); + }); + + await Promise.all(dirtyFileModels.map(async model => { + await model.save(options); + + if (!model.isDirty()) { + const result = mapResourceToResult.get(model.getResource()); + if (result) { + result.success = true; + } + } + })); + + return { results: mapResourceToResult.values() }; + } + + private getFileModels(arg1?: URI | URI[]): ITextFileEditorModel[] { + if (Array.isArray(arg1)) { + const models: ITextFileEditorModel[] = []; + arg1.forEach(resource => { + models.push(...this.getFileModels(resource)); + }); + + return models; + } + + return this._models.getAll(arg1); + } + + private getDirtyFileModels(resources?: URI | URI[]): ITextFileEditorModel[] { + return this.getFileModels(resources).filter(model => model.isDirty()); + } + + async saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise { + + // Get to target resource + if (!targetResource) { + let dialogPath = resource; + if (resource.scheme === Schemas.untitled) { + dialogPath = this.suggestFileName(resource); + } + + targetResource = await this.promptForPath(resource, dialogPath, options ? options.availableFileSystems : undefined); + } + + if (!targetResource) { + return undefined; // user canceled // {{SQL CARBON EDIT}} strict-null-check + } + + // Just save if target is same as models own resource + if (resource.toString() === targetResource.toString()) { + await this.save(resource, options); + + return resource; + } + + // Do it + return this.doSaveAs(resource, targetResource, options); + } + + private async doSaveAs(resource: URI, target: URI, options?: ISaveOptions): Promise { + + // Retrieve text model from provided resource if any + let model: ITextFileEditorModel | UntitledEditorModel | undefined; + if (this.fileService.canHandleResource(resource)) { + model = this._models.get(resource); + } else if (resource.scheme === Schemas.untitled && this.untitledEditorService.exists(resource)) { + model = await this.untitledEditorService.loadOrCreate({ resource }); + } + + // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) + let result: boolean; + if (model) { + result = await this.doSaveTextFileAs(model, resource, target, options); + } + + // Otherwise we can only copy + else { + await this.fileService.copy(resource, target); + + result = true; + } + + // Return early if the operation was not running + if (!result) { + return target; + } + + // Revert the source + await this.revert(resource); + + return target; + } + + private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI, options?: ISaveOptions): Promise { + + // Prefer an existing model if it is already loaded for the given target resource + let targetExists: boolean = false; + let targetModel = this.models.get(target); + if (targetModel && targetModel.isResolved()) { + targetExists = true; + } + + // Otherwise create the target file empty if it does not exist already and resolve it from there + else { + targetExists = await this.fileService.exists(target); + + // create target model adhoc if file does not exist yet + if (!targetExists) { + await this.create(target, ''); + } + + targetModel = await this.models.loadOrCreate(target); + } + + try { + + // Confirm to overwrite if we have an untitled file with associated file where + // the file actually exists on disk and we are instructed to save to that file + // path. This can happen if the file was created after the untitled file was opened. + // See https://github.com/Microsoft/vscode/issues/67946 + let write: boolean; + if (sourceModel instanceof UntitledEditorModel && sourceModel.hasAssociatedFilePath && targetExists && isEqual(target, toLocalResource(sourceModel.getResource(), this.environmentService.configuration.remoteAuthority))) { + write = await this.confirmOverwrite(target); + } else { + write = true; + } + + if (!write) { + return false; + } + + // take over model value, encoding and mode (only if more specific) from source model + targetModel.updatePreferredEncoding(sourceModel.getEncoding()); + if (sourceModel.isResolved() && targetModel.isResolved()) { + this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); + + const sourceMode = sourceModel.textEditorModel.getLanguageIdentifier(); + const targetMode = targetModel.textEditorModel.getLanguageIdentifier(); + if (sourceMode.language !== PLAINTEXT_MODE_ID && targetMode.language === PLAINTEXT_MODE_ID) { + targetModel.textEditorModel.setMode(sourceMode); // only use if more specific than plain/text + } + } + + // save model + await targetModel.save(options); + + return true; + } catch (error) { + + // binary model: delete the file and run the operation again + if ( + (error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY || + (error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE + ) { + await this.fileService.del(target); + + return this.doSaveTextFileAs(sourceModel, resource, target, options); + } + + throw error; + } + } + + private suggestFileName(untitledResource: URI): URI { + const untitledFileName = this.untitledEditorService.suggestFileName(untitledResource); + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + const schemeFilter = remoteAuthority ? Schemas.vscodeRemote : Schemas.file; + + const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter); + if (lastActiveFile) { + const lastDir = dirname(lastActiveFile); + return joinPath(lastDir, untitledFileName); + } + + const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); + if (lastActiveFolder) { + return joinPath(lastActiveFolder, untitledFileName); + } + + return untitledResource.with({ path: untitledFileName }); + } + + async revert(resource: URI, options?: IRevertOptions): Promise { + const result = await this.revertAll([resource], options); + + return result.results.length === 1 && !!result.results[0].success; + } + + async revertAll(resources?: URI[], options?: IRevertOptions): Promise { + + // Revert files first + const revertOperationResult = await this.doRevertAllFiles(resources, options); + + // Revert untitled + const untitledReverted = this.untitledEditorService.revertAll(resources); + untitledReverted.forEach(untitled => revertOperationResult.results.push({ source: untitled, success: true })); + + return revertOperationResult; + } + + private async doRevertAllFiles(resources?: URI[], options?: IRevertOptions): Promise { + const fileModels = options && options.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); + + const mapResourceToResult = new ResourceMap(); + fileModels.forEach(m => { + mapResourceToResult.set(m.getResource(), { + source: m.getResource() + }); + }); + + await Promise.all(fileModels.map(async model => { + try { + await model.revert(options && options.soft); + + if (!model.isDirty()) { + const result = mapResourceToResult.get(model.getResource()); + if (result) { + result.success = true; + } + } + } catch (error) { + + // FileNotFound means the file got deleted meanwhile, so still record as successful revert + if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + const result = mapResourceToResult.get(model.getResource()); + if (result) { + result.success = true; + } + } + + // Otherwise bubble up the error + else { + throw error; + } + } + })); + + return { results: mapResourceToResult.values() }; + } + + getDirty(resources?: URI[]): URI[] { + + // Collect files + const dirty = this.getDirtyFileModels(resources).map(m => m.getResource()); + + // Add untitled ones + dirty.push(...this.untitledEditorService.getDirty(resources)); + + return dirty; + } + + isDirty(resource?: URI): boolean { + + // Check for dirty file + if (this._models.getAll(resource).some(model => model.isDirty())) { + return true; + } + + // Check for dirty untitled + return this.untitledEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); + } + + //#endregion + + //#region config + + getAutoSaveMode(): AutoSaveMode { + if (this.configuredAutoSaveOnFocusChange) { + return AutoSaveMode.ON_FOCUS_CHANGE; + } + + if (this.configuredAutoSaveOnWindowChange) { + return AutoSaveMode.ON_WINDOW_CHANGE; + } + + if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) { + return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY; + } + + return AutoSaveMode.OFF; + } + + getAutoSaveConfiguration(): IAutoSaveConfiguration { + return { + autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined, + autoSaveFocusChange: !!this.configuredAutoSaveOnFocusChange, + autoSaveApplicationChange: !!this.configuredAutoSaveOnWindowChange + }; + } + + get isHotExitEnabled(): boolean { + return !this.environmentService.isExtensionDevelopment && this.configuredHotExit !== HotExitConfiguration.OFF; + } + + //#endregion + + dispose(): void { + + // Clear all caches + this._models.clear(); + + super.dispose(); } } -registerSingleton(ITextFileService, BrowserTextFileService); +export async function promptSave(dialogService: IDialogService, resourcesToConfirm: readonly URI[]) { + const message = resourcesToConfirm.length === 1 + ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", basename(resourcesToConfirm[0])) + : getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm); + + const buttons: string[] = [ + resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), + nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), + nls.localize('cancel', "Cancel") + ]; + + const { choice } = await dialogService.show(Severity.Warning, message, buttons, { + cancelId: 2, + detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") + }); + + switch (choice) { + case 0: return ConfirmResult.SAVE; + case 1: return ConfirmResult.DONT_SAVE; + default: return ConfirmResult.CANCEL; + } +} diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts deleted file mode 100644 index f555441133..0000000000 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ /dev/null @@ -1,1065 +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 nls from 'vs/nls'; -import { URI } from 'vs/base/common/uri'; -import * as errors from 'vs/base/common/errors'; -import * as objects from 'vs/base/common/objects'; -import { Event, Emitter } from 'vs/base/common/event'; -import * as platform from 'vs/base/common/platform'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; -import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor'; -import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IFileService, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions } from 'vs/platform/files/common/files'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; -import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ResourceMap } from 'vs/base/common/map'; -import { Schemas } from 'vs/base/common/network'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { isEqualOrParent, isEqual, joinPath, dirname, extname, basename, toLocalResource } from 'vs/base/common/resources'; -import { getConfirmMessage, IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { coalesce } from 'vs/base/common/arrays'; -import { trim } from 'vs/base/common/strings'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { ITextSnapshot } from 'vs/editor/common/model'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; - -/** - * The workbench file service implementation implements the raw file service spec and adds additional methods on top. - */ -export abstract class TextFileService extends Disposable implements ITextFileService { - - _serviceBrand: undefined; - - private readonly _onAutoSaveConfigurationChange: Emitter = this._register(new Emitter()); - readonly onAutoSaveConfigurationChange: Event = this._onAutoSaveConfigurationChange.event; - - private readonly _onFilesAssociationChange: Emitter = this._register(new Emitter()); - readonly onFilesAssociationChange: Event = this._onFilesAssociationChange.event; - - private readonly _onWillMove = this._register(new Emitter()); - readonly onWillMove: Event = this._onWillMove.event; - - private _models: TextFileEditorModelManager; - get models(): ITextFileEditorModelManager { return this._models; } - - abstract get encoding(): IResourceEncodings; - - private currentFilesAssociationConfig: { [key: string]: string; }; - private configuredAutoSaveDelay?: number; - private configuredAutoSaveOnFocusChange: boolean | undefined; - private configuredAutoSaveOnWindowChange: boolean | undefined; - private configuredHotExit: string | undefined; - private autoSaveContext: IContextKey; - - constructor( - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IFileService protected readonly fileService: IFileService, - @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IModeService private readonly modeService: IModeService, - @IModelService private readonly modelService: IModelService, - @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, - @INotificationService private readonly notificationService: INotificationService, - @IBackupFileService private readonly backupFileService: IBackupFileService, - @IWindowsService private readonly windowsService: IWindowsService, - @IHistoryService private readonly historyService: IHistoryService, - @IContextKeyService contextKeyService: IContextKeyService, - @IDialogService private readonly dialogService: IDialogService, - @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IEditorService private readonly editorService: IEditorService, - @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService - ) { - super(); - - this._models = this._register(instantiationService.createInstance(TextFileEditorModelManager)); - this.autoSaveContext = AutoSaveContext.bindTo(contextKeyService); - - const configuration = configurationService.getValue(); - this.currentFilesAssociationConfig = configuration && configuration.files && configuration.files.associations; - - this.onFilesConfigurationChange(configuration); - - this.registerListeners(); - } - - //#region event handling - - private registerListeners(): void { - - // Lifecycle - this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason))); - this.lifecycleService.onShutdown(this.dispose, this); - - // Files configuration changes - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('files')) { - this.onFilesConfigurationChange(this.configurationService.getValue()); - } - })); - } - - protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { - - // Dirty files need treatment on shutdown - const dirty = this.getDirty(); - if (dirty.length) { - - // If auto save is enabled, save all files and then check again for dirty files - // We DO NOT run any save participant if we are in the shutdown phase for performance reasons - if (this.getAutoSaveMode() !== AutoSaveMode.OFF) { - return this.saveAll(false /* files only */, { skipSaveParticipants: true }).then(() => { - - // If we still have dirty files, we either have untitled ones or files that cannot be saved - const remainingDirty = this.getDirty(); - if (remainingDirty.length) { - return this.handleDirtyBeforeShutdown(remainingDirty, reason); - } - - return false; - }); - } - - // Auto save is not enabled - return this.handleDirtyBeforeShutdown(dirty, reason); - } - - // No dirty files: no veto - return this.noVeto({ cleanUpBackups: true }); - } - - private handleDirtyBeforeShutdown(dirty: URI[], reason: ShutdownReason): boolean | Promise { - - // If hot exit is enabled, backup dirty files and allow to exit without confirmation - if (this.isHotExitEnabled) { - return this.backupBeforeShutdown(dirty, reason).then(didBackup => { - if (didBackup) { - return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful) - } - - // since a backup did not happen, we have to confirm for the dirty files now - return this.confirmBeforeShutdown(); - }, error => { - this.notificationService.error(nls.localize('files.backup.failSave', "Files that are dirty could not be written to the backup location (Error: {0}). Try saving your files first and then exit.", error.message)); - - return true; // veto, the backups failed - }); - } - - // Otherwise just confirm from the user what to do with the dirty files - return this.confirmBeforeShutdown(); - } - - private async backupBeforeShutdown(dirtyToBackup: URI[], reason: ShutdownReason): Promise { - const windowCount = await this.windowsService.getWindowCount(); - - // When quit is requested skip the confirm callback and attempt to backup all workspaces. - // When quit is not requested the confirm callback should be shown when the window being - // closed is the only VS Code window open, except for on Mac where hot exit is only - // ever activated when quit is requested. - - let doBackup: boolean | undefined; - switch (reason) { - case ShutdownReason.CLOSE: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured - } else if (windowCount > 1 || platform.isMacintosh) { - doBackup = false; // do not backup if a window is closed that does not cause quitting of the application - } else { - doBackup = true; // backup if last window is closed on win/linux where the application quits right after - } - break; - - case ShutdownReason.QUIT: - doBackup = true; // backup because next start we restore all backups - break; - - case ShutdownReason.RELOAD: - doBackup = true; // backup because after window reload, backups restore - break; - - case ShutdownReason.LOAD: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured - } else { - doBackup = false; // do not backup because we are switching contexts - } - break; - } - - if (!doBackup) { - return false; - } - - await this.backupAll(dirtyToBackup); - - return true; - } - - private backupAll(dirtyToBackup: URI[]): Promise { - - // split up between files and untitled - const filesToBackup: ITextFileEditorModel[] = []; - const untitledToBackup: URI[] = []; - dirtyToBackup.forEach(dirty => { - if (this.fileService.canHandleResource(dirty)) { - const model = this.models.get(dirty); - if (model) { - filesToBackup.push(model); - } - } else if (dirty.scheme === Schemas.untitled) { - untitledToBackup.push(dirty); - } - }); - - return this.doBackupAll(filesToBackup, untitledToBackup); - } - - private async doBackupAll(dirtyFileModels: ITextFileEditorModel[], untitledResources: URI[]): Promise { - - // Handle file resources first - await Promise.all(dirtyFileModels.map(model => model.backup())); - - // Handle untitled resources - await Promise.all(untitledResources - .filter(untitled => this.untitledEditorService.exists(untitled)) - .map(async untitled => (await this.untitledEditorService.loadOrCreate({ resource: untitled })).backup())); - } - - private async confirmBeforeShutdown(): Promise { - const confirm = await this.confirmSave(); - - // Save - if (confirm === ConfirmResult.SAVE) { - const result = await this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }); - - if (result.results.some(r => !r.success)) { - return true; // veto if some saves failed - } - - return this.noVeto({ cleanUpBackups: true }); - } - - // Don't Save - else if (confirm === ConfirmResult.DONT_SAVE) { - - // Make sure to revert untitled so that they do not restore - // see https://github.com/Microsoft/vscode/issues/29572 - this.untitledEditorService.revertAll(); - - return this.noVeto({ cleanUpBackups: true }); - } - - // Cancel - else if (confirm === ConfirmResult.CANCEL) { - return true; // veto - } - - return false; - } - - private noVeto(options: { cleanUpBackups: boolean }): boolean | Promise { - if (!options.cleanUpBackups) { - return false; - } - - if (this.lifecycleService.phase < LifecyclePhase.Restored) { - return false; // if editors have not restored, we are not up to speed with backups and thus should not clean them - } - - return this.cleanupBackupsBeforeShutdown().then(() => false, () => false); - } - - protected async cleanupBackupsBeforeShutdown(): Promise { - if (this.environmentService.isExtensionDevelopment) { - return; - } - - await this.backupFileService.discardAllWorkspaceBackups(); - } - - protected onFilesConfigurationChange(configuration: IFilesConfiguration): void { - const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF); - - const autoSaveMode = (configuration && configuration.files && configuration.files.autoSave) || AutoSaveConfiguration.OFF; - this.autoSaveContext.set(autoSaveMode); - switch (autoSaveMode) { - case AutoSaveConfiguration.AFTER_DELAY: - this.configuredAutoSaveDelay = configuration && configuration.files && configuration.files.autoSaveDelay; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = false; - break; - - case AutoSaveConfiguration.ON_FOCUS_CHANGE: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = true; - this.configuredAutoSaveOnWindowChange = false; - break; - - case AutoSaveConfiguration.ON_WINDOW_CHANGE: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = true; - break; - - default: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = false; - break; - } - - // Emit as event - this._onAutoSaveConfigurationChange.fire(this.getAutoSaveConfiguration()); - - // save all dirty when enabling auto save - if (!wasAutoSaveEnabled && this.getAutoSaveMode() !== AutoSaveMode.OFF) { - this.saveAll(); - } - - // Check for change in files associations - const filesAssociation = configuration && configuration.files && configuration.files.associations; - if (!objects.equals(this.currentFilesAssociationConfig, filesAssociation)) { - this.currentFilesAssociationConfig = filesAssociation; - this._onFilesAssociationChange.fire(); - } - - // Hot exit - const hotExitMode = configuration && configuration.files && configuration.files.hotExit; - if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - this.configuredHotExit = hotExitMode; - } else { - this.configuredHotExit = HotExitConfiguration.ON_EXIT; - } - } - - //#endregion - - //#region primitives (read, create, move, delete, update) - - async read(resource: URI, options?: IReadTextFileOptions): Promise { - const content = await this.fileService.readFile(resource, options); - - // in case of acceptTextOnly: true, we check the first - // chunk for possibly being binary by looking for 0-bytes - // we limit this check to the first 512 bytes - this.validateBinary(content.value, options); - - return { - ...content, - encoding: 'utf8', - value: content.value.toString() - }; - } - - async readStream(resource: URI, options?: IReadTextFileOptions): Promise { - const stream = await this.fileService.readFileStream(resource, options); - - // in case of acceptTextOnly: true, we check the first - // chunk for possibly being binary by looking for 0-bytes - // we limit this check to the first 512 bytes - let checkedForBinary = false; - const throwOnBinary = (data: VSBuffer): Error | undefined => { - if (!checkedForBinary) { - checkedForBinary = true; - - this.validateBinary(data, options); - } - - return undefined; - }; - - return { - ...stream, - encoding: 'utf8', - value: await createTextBufferFactoryFromStream(stream.value, undefined, options && options.acceptTextOnly ? throwOnBinary : undefined) - }; - } - - private validateBinary(buffer: VSBuffer, options?: IReadTextFileOptions): void { - if (!options || !options.acceptTextOnly) { - return; // no validation needed - } - - // in case of acceptTextOnly: true, we check the first - // chunk for possibly being binary by looking for 0-bytes - // we limit this check to the first 512 bytes - for (let i = 0; i < buffer.byteLength && i < 512; i++) { - if (buffer.readUInt8(i) === 0) { - throw new TextFileOperationError(nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options); - } - } - } - - async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { - const stat = await this.doCreate(resource, value, options); - - // If we had an existing model for the given resource, load - // it again to make sure it is up to date with the contents - // we just wrote into the underlying resource by calling - // revert() - const existingModel = this.models.get(resource); - if (existingModel && !existingModel.isDisposed()) { - await existingModel.revert(); - } - - return stat; - } - - protected doCreate(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { - return this.fileService.createFile(resource, toBufferOrReadable(value), options); - } - - async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { - return this.fileService.writeFile(resource, toBufferOrReadable(value), options); - } - - async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { - const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource)); - - await this.revertAll(dirtyFiles, { soft: true }); - - return this.fileService.del(resource, options); - } - - async move(source: URI, target: URI, overwrite?: boolean): Promise { - - // await onWillMove event joiners - await this.notifyOnWillMove(source, target); - - // find all models that related to either source or target (can be many if resource is a folder) - const sourceModels: ITextFileEditorModel[] = []; - const conflictingModels: ITextFileEditorModel[] = []; - for (const model of this.getFileModels()) { - const resource = model.getResource(); - - if (isEqualOrParent(resource, target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { - conflictingModels.push(model); - } - - if (isEqualOrParent(resource, source)) { - sourceModels.push(model); - } - } - - // remember each source model to load again after move is done - // with optional content to restore if it was dirty - type ModelToRestore = { resource: URI; snapshot?: ITextSnapshot }; - const modelsToRestore: ModelToRestore[] = []; - for (const sourceModel of sourceModels) { - const sourceModelResource = sourceModel.getResource(); - - // If the source is the actual model, just use target as new resource - let modelToRestoreResource: URI; - if (isEqual(sourceModelResource, source)) { - modelToRestoreResource = target; - } - - // Otherwise a parent folder of the source is being moved, so we need - // to compute the target resource based on that - else { - modelToRestoreResource = joinPath(target, sourceModelResource.path.substr(source.path.length + 1)); - } - - const modelToRestore: ModelToRestore = { resource: modelToRestoreResource }; - if (sourceModel.isDirty()) { - modelToRestore.snapshot = sourceModel.createSnapshot(); - } - - modelsToRestore.push(modelToRestore); - } - - // in order to move, we need to soft revert all dirty models, - // both from the source as well as the target if any - const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty()); - await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.getResource()), { soft: true }); - - // now we can rename the source to target via file operation - let stat: IFileStatWithMetadata; - try { - stat = await this.fileService.move(source, target, overwrite); - } catch (error) { - - // in case of any error, ensure to set dirty flag back - dirtyModels.forEach(dirtyModel => dirtyModel.makeDirty()); - - throw error; - } - - // finally, restore models that we had loaded previously - await Promise.all(modelsToRestore.map(async modelToRestore => { - - // restore the model, forcing a reload. this is important because - // we know the file has changed on disk after the move and the - // model might have still existed with the previous state. this - // ensures we are not tracking a stale state. - const restoredModel = await this.models.loadOrCreate(modelToRestore.resource, { reload: { async: false } }); - - // restore previous dirty content if any and ensure to mark - // the model as dirty - if (modelToRestore.snapshot && restoredModel.isResolved()) { - this.modelService.updateModel(restoredModel.textEditorModel, createTextBufferFactoryFromSnapshot(modelToRestore.snapshot)); - - restoredModel.makeDirty(); - } - })); - - return stat; - } - - private async notifyOnWillMove(source: URI, target: URI): Promise { - const waitForPromises: Promise[] = []; - - // fire event - this._onWillMove.fire({ - oldResource: source, - newResource: target, - waitUntil(promise: Promise) { - waitForPromises.push(promise.then(undefined, errors.onUnexpectedError)); - } - }); - - // prevent async waitUntil-calls - Object.freeze(waitForPromises); - - await Promise.all(waitForPromises); - } - - //#endregion - - //#region save/revert - - async save(resource: URI, options?: ISaveOptions): Promise { - - // Run a forced save if we detect the file is not dirty so that save participants can still run - if (options && options.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) { - const model = this._models.get(resource); - if (model) { - options.reason = SaveReason.EXPLICIT; - - await model.save(options); - - return !model.isDirty(); - } - } - - const result = await this.saveAll([resource], options); - - return result.results.length === 1 && !!result.results[0].success; - } - - async confirmSave(resources?: URI[]): Promise { - if (this.environmentService.isExtensionDevelopment) { - if (!this.environmentService.args['extension-development-confirm-save']) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) - } - } - - const resourcesToConfirm = this.getDirty(resources); - if (resourcesToConfirm.length === 0) { - return ConfirmResult.DONT_SAVE; - } - - const message = resourcesToConfirm.length === 1 ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", basename(resourcesToConfirm[0])) - : getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm); - - const buttons: string[] = [ - resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), - nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), - nls.localize('cancel', "Cancel") - ]; - - const { choice } = await this.dialogService.show(Severity.Warning, message, buttons, { - cancelId: 2, - detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") - }); - - switch (choice) { - case 0: return ConfirmResult.SAVE; - case 1: return ConfirmResult.DONT_SAVE; - default: return ConfirmResult.CANCEL; - } - } - - async confirmOverwrite(resource: URI): Promise { - const confirm: IConfirmation = { - message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), - detail: nls.localize('irreversible', "A file or folder with the same name already exists in the folder {0}. Replacing it will overwrite its current contents.", basename(dirname(resource))), - primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), - type: 'warning' - }; - - return (await this.dialogService.confirm(confirm)).confirmed; - } - - saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise; - saveAll(resources: URI[], options?: ISaveOptions): Promise; - saveAll(arg1?: boolean | URI[], options?: ISaveOptions): Promise { - - // get all dirty - let toSave: URI[] = []; - if (Array.isArray(arg1)) { - toSave = this.getDirty(arg1); - } else { - toSave = this.getDirty(); - } - - // split up between files and untitled - const filesToSave: URI[] = []; - const untitledToSave: URI[] = []; - toSave.forEach(resourceToSave => { - if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && resourceToSave.scheme === Schemas.untitled) { - untitledToSave.push(resourceToSave); - } else { - filesToSave.push(resourceToSave); - } - }); - - return this.doSaveAll(filesToSave, untitledToSave, options); - } - - private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ISaveOptions): Promise { - - // Handle files first that can just be saved - const result = await this.doSaveAllFiles(fileResources, options); - - // Preflight for untitled to handle cancellation from the dialog - const targetsForUntitled: URI[] = []; - for (const untitled of untitledResources) { - if (this.untitledEditorService.exists(untitled)) { - let targetUri: URI; - - // Untitled with associated file path don't need to prompt - if (this.untitledEditorService.hasAssociatedFilePath(untitled)) { - targetUri = toLocalResource(untitled, this.environmentService.configuration.remoteAuthority); - } - - // Otherwise ask user - else { - const targetPath = await this.promptForPath(untitled, this.suggestFileName(untitled)); - if (!targetPath) { - return { results: [...fileResources, ...untitledResources].map(r => ({ source: r })) }; - } - - targetUri = targetPath; - } - - targetsForUntitled.push(targetUri); - } - } - - // Handle untitled - await Promise.all(targetsForUntitled.map(async (target, index) => { - const uri = await this.saveAs(untitledResources[index], target); - - result.results.push({ - source: untitledResources[index], - target: uri, - success: !!uri - }); - })); - - return result; - } - - protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { - - // Help user to find a name for the file by opening it first - await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - - return this.fileDialogService.pickFileToSave(this.getSaveDialogOptions(defaultUri, availableFileSystems)); - } - - private getSaveDialogOptions(defaultUri: URI, availableFileSystems?: string[]): ISaveDialogOptions { - const options: ISaveDialogOptions = { - defaultUri, - title: nls.localize('saveAsTitle', "Save As"), - availableFileSystems, - }; - - // Filters are only enabled on Windows where they work properly - if (!platform.isWindows) { - return options; - } - - interface IFilter { name: string; extensions: string[]; } - - // Build the file filter by using our known languages - const ext: string | undefined = defaultUri ? extname(defaultUri) : undefined; - let matchingFilter: IFilter | undefined; - const filters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => { - const extensions = this.modeService.getExtensions(languageName); - if (!extensions || !extensions.length) { - return null; - } - - const filter: IFilter = { name: languageName, extensions: extensions.slice(0, 10).map(e => trim(e, '.')) }; - - if (ext && extensions.indexOf(ext) >= 0) { - matchingFilter = filter; - - return null; // matching filter will be added last to the top - } - - return filter; - })); - - // Filters are a bit weird on Windows, based on having a match or not: - // Match: we put the matching filter first so that it shows up selected and the all files last - // No match: we put the all files filter first - const allFilesFilter = { name: nls.localize('allFiles', "All Files"), extensions: ['*'] }; - if (matchingFilter) { - filters.unshift(matchingFilter); - filters.unshift(allFilesFilter); - } else { - filters.unshift(allFilesFilter); - } - - // Allow to save file without extension - filters.push({ name: nls.localize('noExt', "No Extension"), extensions: [''] }); - - options.filters = filters; - - return options; - } - - private async doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): Promise { - const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : undefined /* Save All */) - .filter(model => { - if ((model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) { - return false; // if model is in save conflict or error, do not save unless save reason is explicit or not provided at all - } - - return true; - }); - - const mapResourceToResult = new ResourceMap(); - dirtyFileModels.forEach(m => { - mapResourceToResult.set(m.getResource(), { - source: m.getResource() - }); - }); - - await Promise.all(dirtyFileModels.map(async model => { - await model.save(options); - - if (!model.isDirty()) { - const result = mapResourceToResult.get(model.getResource()); - if (result) { - result.success = true; - } - } - })); - - return { results: mapResourceToResult.values() }; - } - - private getFileModels(arg1?: URI | URI[]): ITextFileEditorModel[] { - if (Array.isArray(arg1)) { - const models: ITextFileEditorModel[] = []; - arg1.forEach(resource => { - models.push(...this.getFileModels(resource)); - }); - - return models; - } - - return this._models.getAll(arg1); - } - - private getDirtyFileModels(resources?: URI | URI[]): ITextFileEditorModel[] { - return this.getFileModels(resources).filter(model => model.isDirty()); - } - - async saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise { - - // Get to target resource - if (!targetResource) { - let dialogPath = resource; - if (resource.scheme === Schemas.untitled) { - dialogPath = this.suggestFileName(resource); - } - - targetResource = await this.promptForPath(resource, dialogPath, options ? options.availableFileSystems : undefined); - } - - if (!targetResource) { - // {{SQL CARBON EDIT}} @todo anthonydresser 4/12/19 necessary to add undefined till we enable strict null check - return undefined; // user canceled - } - - // Just save if target is same as models own resource - if (resource.toString() === targetResource.toString()) { - await this.save(resource, options); - - return resource; - } - - // Do it - return this.doSaveAs(resource, targetResource, options); - } - - private async doSaveAs(resource: URI, target: URI, options?: ISaveOptions): Promise { - - // Retrieve text model from provided resource if any - let model: ITextFileEditorModel | UntitledEditorModel | undefined; - if (this.fileService.canHandleResource(resource)) { - model = this._models.get(resource); - } else if (resource.scheme === Schemas.untitled && this.untitledEditorService.exists(resource)) { - model = await this.untitledEditorService.loadOrCreate({ resource }); - } - - // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) - let result: boolean; - if (model) { - result = await this.doSaveTextFileAs(model, resource, target, options); - } - - // Otherwise we can only copy - else { - await this.fileService.copy(resource, target); - - result = true; - } - - // Return early if the operation was not running - if (!result) { - return target; - } - - // Revert the source - await this.revert(resource); - - return target; - } - - private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI, options?: ISaveOptions): Promise { - - // Prefer an existing model if it is already loaded for the given target resource - let targetExists: boolean = false; - let targetModel = this.models.get(target); - if (targetModel && targetModel.isResolved()) { - targetExists = true; - } - - // Otherwise create the target file empty if it does not exist already and resolve it from there - else { - targetExists = await this.fileService.exists(target); - - // create target model adhoc if file does not exist yet - if (!targetExists) { - await this.create(target, ''); - } - - targetModel = await this.models.loadOrCreate(target); - } - - try { - - // Confirm to overwrite if we have an untitled file with associated file where - // the file actually exists on disk and we are instructed to save to that file - // path. This can happen if the file was created after the untitled file was opened. - // See https://github.com/Microsoft/vscode/issues/67946 - let write: boolean; - if (sourceModel instanceof UntitledEditorModel && sourceModel.hasAssociatedFilePath && targetExists && isEqual(target, toLocalResource(sourceModel.getResource(), this.environmentService.configuration.remoteAuthority))) { - write = await this.confirmOverwrite(target); - } else { - write = true; - } - - if (!write) { - return false; - } - - // take over model value, encoding and mode (only if more specific) from source model - targetModel.updatePreferredEncoding(sourceModel.getEncoding()); - if (sourceModel.isResolved() && targetModel.isResolved()) { - this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); - - const sourceMode = sourceModel.textEditorModel.getLanguageIdentifier(); - const targetMode = targetModel.textEditorModel.getLanguageIdentifier(); - if (sourceMode.language !== PLAINTEXT_MODE_ID && targetMode.language === PLAINTEXT_MODE_ID) { - targetModel.textEditorModel.setMode(sourceMode); // only use if more specific than plain/text - } - } - - // save model - await targetModel.save(options); - - return true; - } catch (error) { - - // binary model: delete the file and run the operation again - if ( - (error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY || - (error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE - ) { - await this.fileService.del(target); - - return this.doSaveTextFileAs(sourceModel, resource, target, options); - } - - throw error; - } - } - - private suggestFileName(untitledResource: URI): URI { - const untitledFileName = this.untitledEditorService.suggestFileName(untitledResource); - const remoteAuthority = this.environmentService.configuration.remoteAuthority; - const schemeFilter = remoteAuthority ? Schemas.vscodeRemote : Schemas.file; - - const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter); - if (lastActiveFile) { - const lastDir = dirname(lastActiveFile); - return joinPath(lastDir, untitledFileName); - } - - const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); - if (lastActiveFolder) { - return joinPath(lastActiveFolder, untitledFileName); - } - - return untitledResource.with({ path: untitledFileName }); - } - - async revert(resource: URI, options?: IRevertOptions): Promise { - const result = await this.revertAll([resource], options); - - return result.results.length === 1 && !!result.results[0].success; - } - - async revertAll(resources?: URI[], options?: IRevertOptions): Promise { - - // Revert files first - const revertOperationResult = await this.doRevertAllFiles(resources, options); - - // Revert untitled - const untitledReverted = this.untitledEditorService.revertAll(resources); - untitledReverted.forEach(untitled => revertOperationResult.results.push({ source: untitled, success: true })); - - return revertOperationResult; - } - - private async doRevertAllFiles(resources?: URI[], options?: IRevertOptions): Promise { - const fileModels = options && options.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); - - const mapResourceToResult = new ResourceMap(); - fileModels.forEach(m => { - mapResourceToResult.set(m.getResource(), { - source: m.getResource() - }); - }); - - await Promise.all(fileModels.map(async model => { - try { - await model.revert(options && options.soft); - - if (!model.isDirty()) { - const result = mapResourceToResult.get(model.getResource()); - if (result) { - result.success = true; - } - } - } catch (error) { - - // FileNotFound means the file got deleted meanwhile, so still record as successful revert - if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { - const result = mapResourceToResult.get(model.getResource()); - if (result) { - result.success = true; - } - } - - // Otherwise bubble up the error - else { - throw error; - } - } - })); - - return { results: mapResourceToResult.values() }; - } - - getDirty(resources?: URI[]): URI[] { - - // Collect files - const dirty = this.getDirtyFileModels(resources).map(m => m.getResource()); - - // Add untitled ones - dirty.push(...this.untitledEditorService.getDirty(resources)); - - return dirty; - } - - isDirty(resource?: URI): boolean { - - // Check for dirty file - if (this._models.getAll(resource).some(model => model.isDirty())) { - return true; - } - - // Check for dirty untitled - return this.untitledEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); - } - - //#endregion - - //#region config - - getAutoSaveMode(): AutoSaveMode { - if (this.configuredAutoSaveOnFocusChange) { - return AutoSaveMode.ON_FOCUS_CHANGE; - } - - if (this.configuredAutoSaveOnWindowChange) { - return AutoSaveMode.ON_WINDOW_CHANGE; - } - - if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) { - return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY; - } - - return AutoSaveMode.OFF; - } - - getAutoSaveConfiguration(): IAutoSaveConfiguration { - return { - autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined, - autoSaveFocusChange: !!this.configuredAutoSaveOnFocusChange, - autoSaveApplicationChange: !!this.configuredAutoSaveOnWindowChange - }; - } - - get isHotExitEnabled(): boolean { - return !this.environmentService.isExtensionDevelopment && this.configuredHotExit !== HotExitConfiguration.OFF; - } - - //#endregion - - dispose(): void { - - // Clear all caches - this._models.clear(); - - super.dispose(); - } -} diff --git a/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts b/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts index 2fd4ef389d..ef0a5ed015 100644 --- a/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts +++ b/src/vs/workbench/services/textfile/common/textResourcePropertiesService.ts @@ -29,7 +29,7 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer remoteAgentService.getEnvironment().then(remoteEnv => this.remoteEnvironment = remoteEnv); } - getEOL(resource: URI, language?: string): string { + getEOL(resource?: URI, language?: string): string { const filesConfiguration = this.configurationService.getValue<{ eol: string }>('files', { overrideIdentifier: language, resource }); if (filesConfiguration && filesConfiguration.eol && filesConfiguration.eol !== 'auto') { return filesConfiguration.eol; @@ -38,12 +38,12 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer return os === OperatingSystem.Linux || os === OperatingSystem.Macintosh ? '\n' : '\r\n'; } - private getOS(resource: URI): OperatingSystem { + private getOS(resource?: URI): OperatingSystem { let os = OS; const remoteAuthority = this.environmentService.configuration.remoteAuthority; if (remoteAuthority) { - if (resource.scheme !== Schemas.file) { + if (resource && resource.scheme !== Schemas.file) { const osCacheKey = `resource.authority.os.${remoteAuthority}`; os = this.remoteEnvironment ? this.remoteEnvironment.os : /* Get it from cache */ this.storageService.getNumber(osCacheKey, StorageScope.WORKSPACE, OS); this.storageService.store(osCacheKey, os, StorageScope.WORKSPACE); diff --git a/src/vs/workbench/services/textfile/node/textFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts similarity index 98% rename from src/vs/workbench/services/textfile/node/textFileService.ts rename to src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index 0046c4ff53..a60a2c2e57 100644 --- a/src/vs/workbench/services/textfile/node/textFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -5,7 +5,7 @@ import { tmpdir } from 'os'; import { localize } from 'vs/nls'; -import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; +import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; import { ITextFileService, ITextFileStreamContent, ITextFileContent, IResourceEncodings, IResourceEncoding, IReadTextFileOptions, IWriteTextFileOptions, stringToSnapshot, TextFileOperationResult, TextFileOperationError } from 'vs/workbench/services/textfile/common/textfiles'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { URI } from 'vs/base/common/uri'; @@ -14,7 +14,7 @@ import { Schemas } from 'vs/base/common/network'; 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 } from 'vs/base/common/platform'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, encodeStream, UTF8_BOM, toDecodeStream, IDecodeStreamResult, detectEncodingByBOMFromBuffer, isUTFEncoding } from 'vs/base/node/encoding'; @@ -28,7 +28,7 @@ import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textMo import { ITextSnapshot } from 'vs/editor/common/model'; import { nodeReadableToString, streamToNodeReadable, nodeStreamToVSBufferReadable } from 'vs/base/node/stream'; -export class NodeTextFileService extends TextFileService { +export class NativeTextFileService extends AbstractTextFileService { private _encoding!: EncodingOracle; get encoding(): EncodingOracle { @@ -404,4 +404,4 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { } } -registerSingleton(ITextFileService, NodeTextFileService); +registerSingleton(ITextFileService, NativeTextFileService); diff --git a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts index 63bcab0664..e82195a408 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts @@ -22,7 +22,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { join, basename } from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { UTF16be, UTF16le, UTF8_with_bom, UTF8 } from 'vs/base/node/encoding'; -import { NodeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/node/textFileService'; +import { NativeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { isWindows } from 'vs/base/common/platform'; @@ -37,7 +37,7 @@ class ServiceAccessor { } } -class TestNodeTextFileService extends NodeTextFileService { +class TestNativeTextFileService extends NativeTextFileService { private _testEncoding: TestEncodingOracle | undefined; get encoding(): TestEncodingOracle { @@ -84,7 +84,7 @@ suite('Files - TextFileService i/o', () => { const collection = new ServiceCollection(); collection.set(IFileService, fileService); - service = instantiationService.createChild(collection).createInstance(TestNodeTextFileService); + service = instantiationService.createChild(collection).createInstance(TestNativeTextFileService); const id = generateUuid(); testDir = join(parentDir, id); diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index aae5525f64..2d22e06330 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -7,7 +7,7 @@ import * as sinon from 'sinon'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; -import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService, TestFileService, TestHostService } from 'vs/workbench/test/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWindowsService } from 'vs/platform/windows/common/windows'; @@ -21,6 +21,7 @@ import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/commo import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { Schemas } from 'vs/base/common/network'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; class ServiceAccessor { constructor( @@ -30,7 +31,8 @@ class ServiceAccessor { @IWindowsService public windowsService: TestWindowsService, @IWorkspaceContextService public contextService: TestContextService, @IModelService public modelService: ModelServiceImpl, - @IFileService public fileService: TestFileService + @IFileService public fileService: TestFileService, + @IHostService public hostService: TestHostService ) { } } @@ -424,7 +426,7 @@ suite('Files - TextFileService', () => { } // Set multiple windows if required if (multipleWindows) { - accessor.windowsService.windowCount = 2; + accessor.hostService.windowCount = Promise.resolve(2); } // Set cancel to force a veto if hot exit does not trigger service.setConfirmResult(ConfirmResult.CANCEL); diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index a7e48a7dde..38a697680a 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -7,7 +7,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { virtualMachineHint } from 'vs/base/node/id'; import * as perf from 'vs/base/common/performance'; import * as os from 'os'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -308,7 +308,7 @@ class TimerService implements ITimerService { private _startupMetrics?: Promise; constructor( - @IWindowsService private readonly _windowsService: IWindowsService, + @IHostService private readonly _hostService: IHostService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @@ -380,7 +380,7 @@ class TimerService implements ITimerService { isLatestVersion: Boolean(await this._updateService.isLatestVersion()), didUseCachedData: didUseCachedData(), windowKind: this._lifecycleService.startupKind, - windowCount: await this._windowsService.getWindowCount(), + windowCount: await this._hostService.windowCount, viewletId: activeViewlet ? activeViewlet.getId() : undefined, editorIds: this._editorService.visibleEditors.map(input => input.getTypeId()), panelId: activePanel ? activePanel.getId() : undefined, diff --git a/src/vs/workbench/services/title/common/titleService.ts b/src/vs/workbench/services/title/common/titleService.ts index 88e905c9a3..948b179bd7 100644 --- a/src/vs/workbench/services/title/common/titleService.ts +++ b/src/vs/workbench/services/title/common/titleService.ts @@ -14,6 +14,7 @@ export interface ITitleProperties { } export interface ITitleService { + _serviceBrand: undefined; /** @@ -25,4 +26,4 @@ export interface ITitleService { * Update some environmental title properties. */ updateProperties(properties: ITitleProperties): void; -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/url/electron-browser/urlService.ts b/src/vs/workbench/services/url/electron-browser/urlService.ts index 5130f7d307..9250a4f9d1 100644 --- a/src/vs/workbench/services/url/electron-browser/urlService.ts +++ b/src/vs/workbench/services/url/electron-browser/urlService.ts @@ -9,7 +9,7 @@ import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProces import { URLServiceChannelClient, URLHandlerChannel } from 'vs/platform/url/common/urlIpc'; import { URLService } from 'vs/platform/url/node/urlService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import product from 'vs/platform/product/node/product'; +import product from 'vs/platform/product/common/product'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWindowService } from 'vs/platform/windows/common/windows'; diff --git a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts index 359ede6728..5960981111 100644 --- a/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts +++ b/src/vs/workbench/services/userData/common/inMemoryUserDataProvider.ts @@ -50,7 +50,7 @@ class Directory implements IStat { export type Entry = File | Directory; -export class InMemoryUserDataProvider extends Disposable implements IFileSystemProvider { +export class InMemoryFileSystemProvider extends Disposable implements IFileSystemProvider { readonly capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite; readonly onDidChangeCapabilities: Event = Event.None; diff --git a/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts new file mode 100644 index 0000000000..1a1636da55 --- /dev/null +++ b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as objects from 'vs/base/common/objects'; +import { parse, findNodeAtLocation, parseTree } from 'vs/base/common/json'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { ITextModel } from 'vs/editor/common/model'; +import { setProperty } from 'vs/base/common/jsonEdit'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { Position } from 'vs/editor/common/core/position'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; + +class SettingsMergeService implements ISettingsMergeService { + + _serviceBrand: undefined; + + constructor( + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService + ) { } + + async merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { + const local = parse(localContent); + const remote = parse(remoteContent); + const base = baseContent ? parse(baseContent) : null; + + const localToRemote = this.compare(local, remote); + if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { + // No changes found between local and remote. + return { mergeContent: localContent, hasChanges: false, hasConflicts: false }; + } + + const conflicts: Set = new Set(); + const baseToLocal = base ? this.compare(base, local) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToRemote = base ? this.compare(base, remote) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const settingsPreviewModel = this.modelService.createModel(localContent, this.modeService.create('jsonc')); + + // Removed settings in Local + for (const key of baseToLocal.removed.keys()) { + // Got updated in remote + if (baseToRemote.updated.has(key)) { + conflicts.add(key); + } + } + + // Removed settings in Remote + for (const key of baseToRemote.removed.keys()) { + if (conflicts.has(key)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(key)) { + conflicts.add(key); + } else { + this.editSetting(settingsPreviewModel, key, undefined); + } + } + + // Added settings in Local + for (const key of baseToLocal.added.keys()) { + if (conflicts.has(key)) { + continue; + } + // Got added in remote + if (baseToRemote.added.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } + } + + // Added settings in remote + for (const key of baseToRemote.added.keys()) { + if (conflicts.has(key)) { + continue; + } + // Got added in local + if (baseToLocal.added.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } else { + this.editSetting(settingsPreviewModel, key, remote[key]); + } + } + + // Updated settings in Local + for (const key of baseToLocal.updated.keys()) { + if (conflicts.has(key)) { + continue; + } + // Got updated in remote + if (baseToRemote.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } + } + + // Updated settings in Remote + for (const key of baseToRemote.updated.keys()) { + if (conflicts.has(key)) { + continue; + } + // Got updated in local + if (baseToLocal.updated.has(key)) { + // Has different value + if (localToRemote.updated.has(key)) { + conflicts.add(key); + } + } else { + this.editSetting(settingsPreviewModel, key, remote[key]); + } + } + + for (const key of conflicts.keys()) { + const tree = parseTree(settingsPreviewModel.getValue()); + const valueNode = findNodeAtLocation(tree, [key]); + const eol = settingsPreviewModel.getEOL(); + const remoteEdit = setProperty(`{${eol}\t${eol}}`, [key], remote[key], { tabSize: 4, insertSpaces: false, eol: eol })[0]; + const remoteContent = remoteEdit ? `${remoteEdit.content.substring(remoteEdit.offset + remoteEdit.length + 1)},${eol}` : ''; + if (valueNode) { + // Updated in Local and Remote with different value + const keyPosition = settingsPreviewModel.getPositionAt(valueNode.parent!.offset); + const valuePosition = settingsPreviewModel.getPositionAt(valueNode.offset + valueNode.length); + const editOperations = [ + EditOperation.insert(new Position(keyPosition.lineNumber - 1, settingsPreviewModel.getLineMaxColumn(keyPosition.lineNumber - 1)), `${eol}<<<<<<< local`), + EditOperation.insert(new Position(valuePosition.lineNumber, settingsPreviewModel.getLineMaxColumn(valuePosition.lineNumber)), `${eol}=======${eol}${remoteContent}>>>>>>> remote`) + ]; + settingsPreviewModel.pushEditOperations([new Selection(keyPosition.lineNumber, keyPosition.column, keyPosition.lineNumber, keyPosition.column)], editOperations, () => []); + } else { + // Removed in Local, but updated in Remote + const position = new Position(settingsPreviewModel.getLineCount() - 1, settingsPreviewModel.getLineMaxColumn(settingsPreviewModel.getLineCount() - 1)); + const editOperations = [ + EditOperation.insert(position, `${eol}<<<<<<< local${eol}=======${eol}${remoteContent}>>>>>>> remote`) + ]; + settingsPreviewModel.pushEditOperations([new Selection(position.lineNumber, position.column, position.lineNumber, position.column)], editOperations, () => []); + } + } + return { mergeContent: settingsPreviewModel.getValue(), hasChanges: true, hasConflicts: conflicts.size > 0 }; + } + + private editSetting(model: ITextModel, key: string, value: any | undefined): void { + const insertSpaces = false; + const tabSize = 4; + const eol = model.getEOL(); + const edit = setProperty(model.getValue(), [key], value, { tabSize, insertSpaces, eol })[0]; + if (edit) { + const startPosition = model.getPositionAt(edit.offset); + const endPosition = model.getPositionAt(edit.offset + edit.length); + const range = new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column); + let currentText = model.getValueInRange(range); + if (edit.content !== currentText) { + const editOperation = currentText ? EditOperation.replace(range, edit.content) : EditOperation.insert(startPosition, edit.content); + model.pushEditOperations([new Selection(startPosition.lineNumber, startPosition.column, startPosition.lineNumber, startPosition.column)], [editOperation], () => []); + } + } + } + + private compare(from: { [key: string]: any }, to: { [key: string]: any }): { added: Set, removed: Set, updated: Set } { + const fromKeys = Object.keys(from); + const toKeys = Object.keys(to); + const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set()); + const updated: Set = new Set(); + + for (const key of fromKeys) { + if (removed.has(key)) { + continue; + } + const value1 = from[key]; + const value2 = to[key]; + if (!objects.equals(value1, value2)) { + updated.add(key); + } + } + + return { added, removed, updated }; + } + +} + +registerSingleton(ISettingsMergeService, SettingsMergeService); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts new file mode 100644 index 0000000000..830ac59b59 --- /dev/null +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { SyncStatus, SyncSource, IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class UserDataSyncService extends Disposable implements IUserDataSyncService { + + _serviceBrand: undefined; + + private readonly channel: IChannel; + + private _status: SyncStatus = SyncStatus.Uninitialized; + get status(): SyncStatus { return this._status; } + private _onDidChangeStatus: Emitter = this._register(new Emitter()); + readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + + get onDidChangeLocal(): Event { return this.channel.listen('onDidChangeLocal'); } + + private _conflictsSource: SyncSource | null = null; + get conflictsSource(): SyncSource | null { return this._conflictsSource; } + + constructor( + @ISharedProcessService sharedProcessService: ISharedProcessService + ) { + super(); + this.channel = sharedProcessService.getChannel('userDataSync'); + this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); + } + + sync(_continue?: boolean): Promise { + return this.channel.call('sync', [_continue]); + } + + private async updateStatus(status: SyncStatus): Promise { + this._conflictsSource = await this.channel.call('getConflictsSource'); + this._status = status; + this._onDidChangeStatus.fire(status); + } + +} + +registerSingleton(IUserDataSyncService, UserDataSyncService); diff --git a/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts index 855e4a4312..9e8d015aa7 100644 --- a/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts @@ -31,6 +31,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class WorkspaceEditingService implements IWorkspaceEditingService { @@ -54,7 +55,8 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { @IFileDialogService private readonly fileDialogService: IFileDialogService, @IDialogService private readonly dialogService: IDialogService, @ILifecycleService readonly lifecycleService: ILifecycleService, - @ILabelService readonly labelService: ILabelService + @ILabelService readonly labelService: ILabelService, + @IHostService private readonly hostService: IHostService ) { this.registerListeners(); } @@ -82,7 +84,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { return false; // only care about untitled workspaces to ask for saving } - const windowCount = await this.windowsService.getWindowCount(); + const windowCount = await this.hostService.windowCount; if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) { return false; // Windows/Linux: quits when last window is closed, so do not ask then @@ -448,7 +450,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } } - return this.jsonEditingService.write(toWorkspace.configPath, { key: 'settings', value: targetWorkspaceConfiguration }, true); + return this.jsonEditingService.write(toWorkspace.configPath, [{ key: 'settings', value: targetWorkspaceConfiguration }], true); } private getCurrentWorkspaceIdentifier(): IWorkspaceIdentifier | undefined { diff --git a/src/vs/workbench/services/workspace/browser/workspacesService.ts b/src/vs/workbench/services/workspace/browser/workspacesService.ts new file mode 100644 index 0000000000..94034b80a9 --- /dev/null +++ b/src/vs/workbench/services/workspace/browser/workspacesService.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWorkspacesService, IWorkspaceFolderCreationData, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { URI } from 'vs/base/common/uri'; + +export class WorkspacesService implements IWorkspacesService { + + _serviceBrand: undefined; + + async createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { + throw new Error('Untitled workspaces are currently unsupported in Web'); + } + + async deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise { + throw new Error('Untitled workspaces are currently unsupported in Web'); + } + + async getWorkspaceIdentifier(workspacePath: URI): Promise { + throw new Error('Untitled workspaces are currently unsupported in Web'); + } +} + +registerSingleton(IWorkspacesService, WorkspacesService, true); diff --git a/src/vs/platform/workspaces/electron-browser/workspacesService.ts b/src/vs/workbench/services/workspace/electron-browser/workspacesService.ts similarity index 91% rename from src/vs/platform/workspaces/electron-browser/workspacesService.ts rename to src/vs/workbench/services/workspace/electron-browser/workspacesService.ts index 6107be9d05..0249e060ee 100644 --- a/src/vs/platform/workspaces/electron-browser/workspacesService.ts +++ b/src/vs/workbench/services/workspace/electron-browser/workspacesService.ts @@ -7,6 +7,7 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IWorkspacesService, IWorkspaceIdentifier, IWorkspaceFolderCreationData, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { URI } from 'vs/base/common/uri'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class WorkspacesService implements IWorkspacesService { @@ -30,3 +31,5 @@ export class WorkspacesService implements IWorkspacesService { return this.channel.call('getWorkspaceIdentifier', configPath).then(reviveWorkspaceIdentifier); } } + +registerSingleton(IWorkspacesService, WorkspacesService, true); diff --git a/src/vs/workbench/test/contrib/linkProtection.test.ts b/src/vs/workbench/test/contrib/linkProtection.test.ts index 77e4076ac9..0facf78c49 100644 --- a/src/vs/workbench/test/contrib/linkProtection.test.ts +++ b/src/vs/workbench/test/contrib/linkProtection.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; -import { isURLDomainTrusted } from 'vs/workbench/contrib/url/common/url.contribution'; +import { isURLDomainTrusted } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; import { URI } from 'vs/base/common/uri'; suite('Link protection domain matching', () => { @@ -32,11 +32,11 @@ suite('Link protection domain matching', () => { test('* star', () => { assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://*.x.org'])); + assert.ok(isURLDomainTrusted(URI.parse('https://a.b.x.org'), ['https://*.x.org'])); assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://a.x.*'])); assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://a.*.org'])); assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://*.*.org'])); - - assert.ok(!isURLDomainTrusted(URI.parse('https://a.b.c.org'), ['https://*.*.org'])); - assert.ok(isURLDomainTrusted(URI.parse('https://a.b.c.org'), ['https://*.*.*.org'])); + assert.ok(isURLDomainTrusted(URI.parse('https://a.b.x.org'), ['https://*.b.*.org'])); + assert.ok(isURLDomainTrusted(URI.parse('https://a.a.b.x.org'), ['https://*.b.*.org'])); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts index 24bb5ec4c7..0f3ff60ce4 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts @@ -20,6 +20,10 @@ const emptyDialogService = new class implements IDialogService { confirm(): never { throw new Error('not implemented'); } + + about(): never { + throw new Error('not implemented'); + } }; const emptyCommandService: ICommandService = { diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 8d87c7a3c4..9f4dcb28dd 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -82,10 +82,11 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedPr import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; -import { NodeTextFileService } from 'vs/workbench/services/textfile/node/textFileService'; +import { NativeTextFileService } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { Schemas } from 'vs/base/common/network'; -import { IProductService } from 'vs/platform/product/common/product'; -import product from 'vs/platform/product/node/product'; +import { IProductService } from 'vs/platform/product/common/productService'; +import product from 'vs/platform/product/common/product'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); @@ -180,7 +181,7 @@ export class TestContextService implements IWorkspaceContextService { } } -export class TestTextFileService extends NodeTextFileService { +export class TestTextFileService extends NativeTextFileService { public cleanupBackupsBeforeShutdownCalled: boolean; private promptPath: URI; @@ -199,7 +200,7 @@ export class TestTextFileService extends NodeTextFileService { @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @INotificationService notificationService: INotificationService, @IBackupFileService backupFileService: IBackupFileService, - @IWindowsService windowsService: IWindowsService, + @IHostService hostService: IHostService, @IHistoryService historyService: IHistoryService, @IContextKeyService contextKeyService: IContextKeyService, @IDialogService dialogService: IDialogService, @@ -219,7 +220,7 @@ export class TestTextFileService extends NodeTextFileService { environmentService, notificationService, backupFileService, - windowsService, + hostService, historyService, contextKeyService, dialogService, @@ -311,6 +312,7 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(IDecorationsService, new TestDecorationsService()); instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IWindowsService, new TestWindowsService()); + instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); instantiationService.stub(IThemeService, new TestThemeService()); @@ -402,6 +404,10 @@ export class TestDialogService implements IDialogService { public show(_severity: Severity, _message: string, _buttons: string[], _options?: IDialogOptions): Promise { return Promise.resolve({ choice: 0 }); } + + public about(): Promise { + return Promise.resolve(); + } } export class TestFileDialogService implements IFileDialogService { @@ -1345,8 +1351,6 @@ export class TestWindowsService implements IWindowsService { _serviceBrand: undefined; - public windowCount = 1; - readonly onWindowOpen: Event = Event.None; readonly onWindowFocus: Event = Event.None; readonly onWindowBlur: Event = Event.None; @@ -1486,18 +1490,6 @@ export class TestWindowsService implements IWindowsService { throw new Error('not implemented'); } - getWindowCount(): Promise { - return Promise.resolve(this.windowCount); - } - - log(_severity: string, _args: string[]): Promise { - return Promise.resolve(); - } - - showItemInFolder(_path: URI): Promise { - return Promise.resolve(); - } - newWindowTab(): Promise { return Promise.resolve(); } @@ -1553,10 +1545,6 @@ export class TestWindowsService implements IWindowsService { throw new Error('not implemented'); } - openAboutDialog(): Promise { - return Promise.resolve(); - } - resolveProxy(windowId: number, url: string): Promise { return Promise.resolve(undefined); } @@ -1642,3 +1630,10 @@ export class RemoteFileSystemProvider implements IFileSystemProvider { } export const productService: IProductService = { _serviceBrand: undefined, ...product }; + +export class TestHostService implements IHostService { + + _serviceBrand: undefined; + + windowCount = Promise.resolve(1); +} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 2086891b4d..5f572bdf26 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -16,11 +16,15 @@ import 'vs/workbench/browser/workbench.contribution'; //#region --- workbench actions -import 'vs/workbench/browser/actions/layoutActions'; -import 'vs/workbench/browser/actions/windowActions'; import 'vs/workbench/browser/actions/developerActions'; +import 'vs/workbench/browser/actions/helpActions'; +import 'vs/workbench/browser/actions/layoutActions'; import 'vs/workbench/browser/actions/listCommands'; import 'vs/workbench/browser/actions/navigationActions'; +import 'vs/workbench/browser/actions/windowActions'; +import 'vs/workbench/browser/actions/workspaceActions'; +import 'vs/workbench/browser/actions/workspaceCommands'; + import 'vs/workbench/browser/parts/quickopen/quickOpenActions'; import 'vs/workbench/browser/parts/quickinput/quickInputActions'; @@ -76,7 +80,9 @@ import 'vs/workbench/services/label/common/labelService'; import 'vs/workbench/services/extensionManagement/common/extensionEnablementService'; import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/extensions/common/staticExtensions'; +import 'vs/workbench/services/userDataSync/common/settingsMergeService'; import 'vs/workbench/services/workspace/browser/workspaceEditingService'; +import 'vs/workbench/services/host/browser/browserHostService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; @@ -159,6 +165,7 @@ import 'vs/workbench/contrib/scm/browser/scmViewlet'; import 'vs/workbench/contrib/debug/browser/debug.contribution'; import 'vs/workbench/contrib/debug/browser/debugQuickOpen'; import 'vs/workbench/contrib/debug/browser/debugEditorContribution'; +import 'vs/workbench/contrib/debug/browser/breakpointEditorContribution'; import 'vs/workbench/contrib/debug/browser/repl'; import 'vs/workbench/contrib/debug/browser/debugViewlet'; */ @@ -228,8 +235,15 @@ import 'vs/workbench/contrib/update/browser/update.contribution'; // Watermark import 'vs/workbench/contrib/watermark/browser/watermark'; +// Surveys +import 'vs/workbench/contrib/surveys/browser/nps.contribution'; +import 'vs/workbench/contrib/surveys/browser/languageSurveys.contribution'; + // Welcome import 'vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay'; +import 'vs/workbench/contrib/welcome/page/browser/welcomePage.contribution'; +import 'vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution'; +import 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution'; // Call Hierarchy import 'vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution'; @@ -243,4 +257,7 @@ import 'vs/workbench/contrib/experiments/browser/experiments.contribution'; // Send a Smile import 'vs/workbench/contrib/feedback/browser/feedback.contribution'; +// User Data Sync +import 'vs/workbench/contrib/userDataSync/browser/userDataSync.contribution'; + //#endregion diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 8d3373ec9e..19ab8df13a 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -32,7 +32,7 @@ import 'vs/workbench/services/integrity/node/integrityService'; import 'vs/workbench/services/textMate/electron-browser/textMateService'; import 'vs/workbench/services/search/node/searchService'; import 'vs/workbench/services/output/node/outputChannelModelService'; -import 'vs/workbench/services/textfile/node/textFileService'; +import 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import 'vs/workbench/services/dialogs/electron-browser/dialogService'; import 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import 'vs/workbench/services/keybinding/electron-browser/keybinding.contribution'; @@ -49,6 +49,9 @@ import 'vs/workbench/services/remote/node/tunnelService'; import 'vs/workbench/services/backup/node/backupFileService'; import 'vs/workbench/services/credentials/node/credentialsService'; import 'vs/workbench/services/url/electron-browser/urlService'; +import 'vs/workbench/services/workspace/electron-browser/workspacesService'; +import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService'; +import 'vs/workbench/services/host/electron-browser/desktopHostService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -66,10 +69,10 @@ import { IUpdateService } from 'vs/platform/update/common/update'; import { UpdateService } from 'vs/platform/update/electron-browser/updateService'; import { IIssueService } from 'vs/platform/issue/node/issue'; import { IssueService } from 'vs/platform/issue/electron-browser/issueService'; -import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { WorkspacesService } from 'vs/platform/workspaces/electron-browser/workspacesService'; import { IMenubarService } from 'vs/platform/menubar/node/menubar'; import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { ElectronService } from 'vs/platform/electron/electron-browser/electronService'; registerSingleton(IClipboardService, ClipboardService, true); registerSingleton(IRequestService, RequestService, true); @@ -79,8 +82,8 @@ registerSingleton(ISharedProcessService, SharedProcessService, true); registerSingleton(IWindowsService, WindowsService); registerSingleton(IUpdateService, UpdateService); registerSingleton(IIssueService, IssueService); -registerSingleton(IWorkspacesService, WorkspacesService); registerSingleton(IMenubarService, MenubarService); +registerSingleton(IElectronService, ElectronService, true); //#endregion @@ -198,6 +201,9 @@ registerSingleton(IQueryHistoryService, QueryHistoryService); // Localizations import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; +// Logs +import 'vs/workbench/contrib/logs/electron-browser/logs.contribution'; + // Stats import 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; @@ -205,6 +211,9 @@ import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; // Rapid Render Splash import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; +// Explorer +import 'vs/workbench/contrib/files/electron-browser/fileActions.contribution'; + // Debug // import 'vs/workbench/contrib/debug/node/debugHelperService'; {{SQL CARBON EDIT}} import 'vs/workbench/contrib/debug/electron-browser/extensionHostDebugService'; @@ -230,10 +239,6 @@ import 'vs/workbench/contrib/externalTerminal/node/externalTerminalService'; // Update import 'vs/workbench/contrib/update/electron-browser/update.contribution'; -// Surveys -import 'vs/workbench/contrib/surveys/electron-browser/nps.contribution'; -import 'vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution'; - // Performance import 'vs/workbench/contrib/performance/electron-browser/performance.contribution'; @@ -243,17 +248,17 @@ import 'vs/workbench/contrib/cli/node/cli.contribution'; // Themes Support import 'vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution'; -// Welcome -import 'vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution'; -import 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution'; -import 'vs/workbench/contrib/welcome/page/browser/welcomePage.contribution'; - // Issues import 'vs/workbench/contrib/issue/electron-browser/issue.contribution'; // Tasks import 'vs/workbench/contrib/tasks/electron-browser/taskService'; +// User Data Sync +import 'vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution'; + +//#endregion + // {{SQL CARBON EDIT}} // SQL import 'sql/workbench/parts/tasks/browser/tasks.contribution'; diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index af9b82b6f7..0c7e797d42 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -6,15 +6,17 @@ import 'vs/workbench/workbench.web.main'; import { main } from 'vs/workbench/browser/web.main'; import { UriComponents, URI } from 'vs/base/common/uri'; -import { IFileSystemProvider } from 'vs/platform/files/common/files'; -import { IWebSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory'; +import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, FileChangeType } from 'vs/platform/files/common/files'; +import { IWebSocketFactory, IWebSocket } from 'vs/platform/remote/browser/browserSocketFactory'; import { ICredentialsProvider } from 'vs/workbench/services/credentials/browser/credentialsService'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService'; import { LogLevel } from 'vs/platform/log/common/log'; -import { IUpdateProvider } from 'vs/workbench/services/update/browser/updateService'; +import { IUpdateProvider, IUpdate } from 'vs/workbench/services/update/browser/updateService'; +import { Event, Emitter } from 'vs/base/common/event'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -export interface IWorkbenchConstructionOptions { +interface IWorkbenchConstructionOptions { /** * Experimental: the remote authority is the IP:PORT from where the workbench is served @@ -36,12 +38,12 @@ export interface IWorkbenchConstructionOptions { /** * Experimental: An optional folder that is set as workspace context for the workbench. */ - folderUri?: UriComponents; + folderUri?: URI; /** * Experimental: An optional workspace that is set as workspace context for the workbench. */ - workspaceUri?: UriComponents; + workspaceUri?: URI; /** * Experimental: The userDataProvider is used to handle user specific application @@ -57,7 +59,7 @@ export interface IWorkbenchConstructionOptions { /** * A provider for resource URIs. */ - resourceUriProvider?: (uri: URI) => UriComponents; + resourceUriProvider?: (uri: URI) => URI; /** * Experimental: Whether to enable the smoke test driver. @@ -72,7 +74,7 @@ export interface IWorkbenchConstructionOptions { /** * Experimental: Add static extensions that cannot be uninstalled but only be disabled. */ - staticExtensions?: { packageJSON: IExtensionManifest, extensionLocation: UriComponents }[]; + staticExtensions?: { packageJSON: IExtensionManifest, extensionLocation: URI }[]; /** * Experimental: Support for URL callbacks. @@ -88,6 +90,11 @@ export interface IWorkbenchConstructionOptions { * Experimental: Support for update reporting. */ updateProvider?: IUpdateProvider; + + /** + * Experimental: Resolves an external uri before it is opened. + */ + readonly resolveExternalUri?: (uri: URI) => Promise; } /** @@ -101,5 +108,42 @@ function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions) } export { - create + + // Factory + create, + IWorkbenchConstructionOptions, + + // Basic Types + URI, + UriComponents, + Event, + Emitter, + IDisposable, + Disposable, + + // FileSystem + IFileSystemProvider, + FileSystemProviderCapabilities, + IFileChange, + FileChangeType, + + // WebSockets + IWebSocketFactory, + IWebSocket, + + // Credentials + ICredentialsProvider, + + // Static Extensions + IExtensionManifest, + + // Callbacks + IURLCallbackProvider, + + // LogLevel + LogLevel, + + // Updates + IUpdateProvider, + IUpdate }; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 52ab83b14f..36e57cac6c 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -30,7 +30,7 @@ import 'vs/workbench/services/integrity/browser/integrityService'; import 'vs/workbench/services/textMate/browser/textMateService'; import 'vs/workbench/services/search/common/searchService'; import 'vs/workbench/services/output/common/outputChannelModelService'; -import 'vs/workbench/services/textfile/browser/textFileService'; +import 'vs/workbench/services/textfile/browser/browserTextFileService'; import 'vs/workbench/services/keybinding/browser/keymapService'; import 'vs/workbench/services/extensions/browser/extensionService'; import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService'; @@ -40,6 +40,8 @@ import 'vs/workbench/services/credentials/browser/credentialsService'; import 'vs/workbench/services/url/browser/urlService'; import 'vs/workbench/services/update/browser/updateService'; import 'vs/workbench/contrib/stats/browser/workspaceStatsService'; +import 'vs/workbench/services/workspace/browser/workspacesService'; +import 'vs/workbench/services/dialogs/browser/dialogService'; import 'vs/workbench/browser/web.simpleservices'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -52,8 +54,6 @@ import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/ac import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { BrowserLifecycleService } from 'vs/platform/lifecycle/browser/lifecycleService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { DialogService } from 'vs/platform/dialogs/browser/dialogService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -61,16 +61,20 @@ import { BackupFileService } from 'vs/workbench/services/backup/common/backupFil import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { NoOpTunnelService } from 'vs/platform/remote/common/tunnelService'; +import { IUserDataSyncStoreService, IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; registerSingleton(IRequestService, RequestService, true); registerSingleton(IExtensionManagementService, ExtensionManagementService); registerSingleton(IBackupFileService, BackupFileService); -registerSingleton(IDialogService, DialogService, true); registerSingleton(IClipboardService, BrowserClipboardService, true); registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); registerSingleton(ILifecycleService, BrowserLifecycleService); registerSingleton(IContextMenuService, ContextMenuService); registerSingleton(ITunnelService, NoOpTunnelService, true); +registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService); +registerSingleton(IUserDataSyncService, UserDataSyncService); //#endregion diff --git a/test/automation/src/keybindings.ts b/test/automation/src/keybindings.ts index b5f9ff684a..55ac104f08 100644 --- a/test/automation/src/keybindings.ts +++ b/test/automation/src/keybindings.ts @@ -24,7 +24,7 @@ export class KeybindingsEditor { await this.code.waitAndClick('.keybindings-list-container .monaco-list-row.keybinding-item'); await this.code.waitForElement('.keybindings-list-container .monaco-list-row.keybinding-item.focused.selected'); - await this.code.waitAndClick('.keybindings-list-container .monaco-list-row.keybinding-item .action-item .icon.add'); + await this.code.waitAndClick('.keybindings-list-container .monaco-list-row.keybinding-item .action-item .codicon.add'); await this.code.waitForActiveElement('.defineKeybindingWidget .monaco-inputbox input'); await this.code.dispatchKeybinding(keybinding); diff --git a/test/electron/index.js b/test/electron/index.js index 9e5e46c278..c26a8fdc65 100644 --- a/test/electron/index.js +++ b/test/electron/index.js @@ -9,7 +9,7 @@ const { join } = require('path'); const path = require('path'); const mocha = require('mocha'); const events = require('events'); -const MochaJUnitReporter = require('mocha-junit-reporter'); +// const MochaJUnitReporter = require('mocha-junit-reporter'); const url = require('url'); const defaultReporterName = process.platform === 'win32' ? 'list' : 'spec'; @@ -133,12 +133,13 @@ app.on('ready', () => { if (argv.tfs) { new mocha.reporters.Spec(runner); - new MochaJUnitReporter(runner, { - reporterOptions: { - testsuitesTitle: `${argv.tfs} ${process.platform}`, - mochaFile: process.env.BUILD_ARTIFACTSTAGINGDIRECTORY ? path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${argv.tfs.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) : undefined - } - }); + // TODO@deepak the mocha Junit reporter seems to cause a hang when running with Electron 6 inside docker container + // new MochaJUnitReporter(runner, { + // reporterOptions: { + // testsuitesTitle: `${argv.tfs} ${process.platform}`, + // mochaFile: process.env.BUILD_ARTIFACTSTAGINGDIRECTORY ? path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${argv.tfs.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) : undefined + // } + // }); } else { const reporterPath = path.join(path.dirname(require.resolve('mocha')), 'lib', 'reporters', argv.reporter); let Reporter; diff --git a/yarn.lock b/yarn.lock index 434c1b0ba5..e1f51cfd9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -677,6 +677,14 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" +anymatch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.0.tgz#e609350e50a9313b472789b2f14ef35808ee14d6" + integrity sha512-Ozz7l4ixzI7Oxj2+cw+p0tVUt27BpaJ+1+q1TCeANWxHpvyn2+Un+YamBdfKu0uh8xLodGhoa1v7595NhKDAuA== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + append-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" @@ -1066,6 +1074,11 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" integrity sha1-muuabF6IY4qtFx4Wf1kAq+JINdA= +binary-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" + integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== + binary-search-bounds@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/binary-search-bounds/-/binary-search-bounds-2.0.3.tgz#5ff8616d6dd2ca5388bc85b2d6266e2b9da502dc" @@ -1196,6 +1209,13 @@ braces@^2.3.0, braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" @@ -1518,6 +1538,21 @@ cheerio@^1.0.0-rc.1: lodash "^4.15.0" parse5 "^3.0.1" +chokidar@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.1.0.tgz#ff23d077682a90eadd209bfa76eb10ed6d359668" + integrity sha512-6vZfo+7W0EOlbSo0nhVKMz4yyssrwiPbBZ8wj1lq8/+l4ZhGZ2U4Md7PspvmijXp1a26D3B7AHEBmIB7aVtaOQ== + dependencies: + anymatch "^3.1.0" + braces "^3.0.2" + glob-parent "^5.0.0" + is-binary-path "^2.1.0" + is-glob "^4.0.1" + normalize-path "^3.0.0" + readdirp "^3.1.1" + optionalDependencies: + fsevents "^2.0.6" + chokidar@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.0.tgz#5fcb70d0b28ebe0867eb0f09d5f6a08f29a1efa0" @@ -3081,6 +3116,13 @@ fill-range@^4.0.0: repeat-string "^1.6.1" to-regex-range "^2.1.0" +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + finalhandler@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" @@ -3353,6 +3395,11 @@ fsevents@^1.2.7: nan "^2.9.2" node-pre-gyp "^0.10.0" +fsevents@^2.0.6: + version "2.0.7" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a" + integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ== + fstream@^1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" @@ -3468,6 +3515,13 @@ glob-parent@^3.0.0, glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" +glob-parent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954" + integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg== + dependencies: + is-glob "^4.0.1" + glob-stream@^5.3.2: version "5.3.5" resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-5.3.5.tgz#a55665a9a8ccdc41915a87c701e32d4e016fad22" @@ -3855,10 +3909,10 @@ gulp-symdest@^1.1.1: queue "^3.1.0" vinyl-fs "^2.4.3" -gulp-tsb@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/gulp-tsb/-/gulp-tsb-4.0.2.tgz#8717a18f1ce032147e010028f0a59863bd552b96" - integrity sha512-xF88h0vFH8JkunSnmVrYfrR3LGTMAY+KTkHHF/S9BAOCsdaC83/hv4EmpcLxev7B+2yd3+xcitlsDFMBSo/gSw== +gulp-tsb@4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/gulp-tsb/-/gulp-tsb-4.0.4.tgz#c0486534e2f86cd4a11c2393593d9eae3a426ac4" + integrity sha512-BIIls2PpT3+JR1Svvd0SLjkBj2AmlHIRnum/ajf1XQUgbVA9wwsZuTjpVXU/K06KbTYfGsSSYAPRK2dICNAMZQ== dependencies: ansi-colors "^1.0.1" fancy-log "^1.3.2" @@ -4404,6 +4458,13 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" +is-binary-path@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-buffer@^1.0.2: version "1.1.4" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.4.tgz#cfc86ccd5dc5a52fa80489111c6920c457e2d98b" @@ -4520,6 +4581,13 @@ is-glob@^4.0.0: dependencies: is-extglob "^2.1.1" +is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + is-my-ip-valid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" @@ -4560,6 +4628,11 @@ is-number@^4.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" @@ -7292,6 +7365,13 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" +readdirp@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.2.tgz#fa85d2d14d4289920e4671dead96431add2ee78a" + integrity sha512-8rhl0xs2cxfVsqzreYCvs8EwBfn/DhVdqtoLmw19uI3SC5avYX9teCurlErfpPXGmYtMHReGaP2RsLnFvz/lnw== + dependencies: + picomatch "^2.0.4" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -8645,6 +8725,13 @@ to-regex-range@^2.1.0: is-number "^3.0.0" repeat-string "^1.6.1" +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" @@ -8994,11 +9081,6 @@ upath@^1.0.5, upath@^1.1.0: resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw== -upath@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" - integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== - uri-js@^4.2.1, uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -9266,45 +9348,11 @@ vsce@1.48.0: yauzl "^2.3.1" yazl "^2.2.2" -vscode-anymatch@3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/vscode-anymatch/-/vscode-anymatch-3.0.3.tgz#5a79101e6df7e659a1f070367bc42f190eb4ae76" - integrity sha512-qQgfbzJJ5nNShh4jjC3BBekY4d8emcxHFgnqcXwsB/PUKvJPCg7AZYXM7hqS7EDnKrX9tsIFwFMihZ7yut92Qg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -vscode-chokidar@2.1.7: - version "2.1.7" - resolved "https://registry.yarnpkg.com/vscode-chokidar/-/vscode-chokidar-2.1.7.tgz#c5b31eb87402f4779bb4170915245bdcb6f7854b" - integrity sha512-uSNEQetPjAlgIAHmcF9E6M+KCw0f842rsEnJ64aamUAV6TO7gkXNCvLSzb4MuLsPU7ZQyCa++DrLQFjvciK5dg== - dependencies: - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - vscode-anymatch "3.0.3" - optionalDependencies: - vscode-fsevents "1.2.12" - vscode-debugprotocol@1.36.0: version "1.36.0" resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.36.0.tgz#88e6246045480a9cc643e819b597396eaa9d0f4b" integrity sha512-F0MfcUkF88TfNf4iQbcmC+K9rA+zsrQpEz1XpTKidy5sMq8sYsJGUadYDGmmktfjRX+S/ebjHgM+YV/2qm6lVQ== -vscode-fsevents@1.2.12: - version "1.2.12" - resolved "https://registry.yarnpkg.com/vscode-fsevents/-/vscode-fsevents-1.2.12.tgz#01a71a01f90ee95ca822c34427aba437a17c03a7" - integrity sha512-bH/jRdDpSesGpqiVLjp6gHLSKUOh7oNvppzZ17JIrdbRYCcDmV7dIWR5gQc27DFy0RD9JDT+t+ixMid94MkM1A== - dependencies: - nan "^2.14.0" - vscode-minimist@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.1.tgz#e63d3f4a9bf3680dcb8f9304eed612323fd6926a"