diff --git a/.vscode/settings.json b/.vscode/settings.json index 1a760bdda6..551d18bb0c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -61,4 +61,4 @@ "msjsdiag.debugger-for-chrome": "workspace" }, "files.insertFinalNewline": true -} \ No newline at end of file +} diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 0ca58327e7..7a41817ae3 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -41,13 +41,7 @@ var editorEntryPoints = [ ]; var editorResources = [ - 'out-build/vs/{base,editor}/**/*.{svg,png}', - '!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/**' + 'out-editor-build/vs/base/browser/ui/codiconLabel/**/*.ttf' ]; var BUNDLED_FILE_HEADER = [ @@ -92,7 +86,6 @@ 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/lib/standalone.js b/build/lib/standalone.js index 914ade14b3..e374a22eff 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -130,7 +130,7 @@ function createESMSourcesAndResources2(options) { write(getDestAbsoluteFilePath(file), JSON.stringify(tsConfig, null, '\t')); continue; } - if (/\.d\.ts$/.test(file) || /\.css$/.test(file) || /\.js$/.test(file)) { + if (/\.d\.ts$/.test(file) || /\.css$/.test(file) || /\.js$/.test(file) || /\.ttf$/.test(file)) { // Transport the files directly write(getDestAbsoluteFilePath(file), fs.readFileSync(path.join(SRC_FOLDER, file))); continue; @@ -250,35 +250,37 @@ function transportCSS(module, enqueue, write) { const filename = path.join(SRC_DIR, module); const fileContents = fs.readFileSync(filename).toString(); const inlineResources = 'base64'; // see https://github.com/Microsoft/monaco-editor/issues/148 - const inlineResourcesLimit = 300000; //3000; // see https://github.com/Microsoft/monaco-editor/issues/336 - const newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64', inlineResourcesLimit); + const newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64'); write(module, newContents); return true; - function _rewriteOrInlineUrls(contents, forceBase64, inlineByteLimit) { + function _rewriteOrInlineUrls(contents, forceBase64) { return _replaceURL(contents, (url) => { - let imagePath = path.join(path.dirname(module), url); - let fileContents = fs.readFileSync(path.join(SRC_DIR, imagePath)); - if (fileContents.length < inlineByteLimit) { - const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; - let DATA = ';base64,' + fileContents.toString('base64'); - if (!forceBase64 && /\.svg$/.test(url)) { - // .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris - let newText = fileContents.toString() - .replace(/"/g, '\'') - .replace(//g, '%3E') - .replace(/&/g, '%26') - .replace(/#/g, '%23') - .replace(/\s+/g, ' '); - let encodedData = ',' + newText; - if (encodedData.length < DATA.length) { - DATA = encodedData; - } - } - return '"data:' + MIME + DATA + '"'; + const fontMatch = url.match(/^(.*).ttf\?(.*)$/); + if (fontMatch) { + const relativeFontPath = `${fontMatch[1]}.ttf`; // trim the query parameter + const fontPath = path.join(path.dirname(module), relativeFontPath); + enqueue(fontPath); + return relativeFontPath; } - enqueue(imagePath); - return url; + const imagePath = path.join(path.dirname(module), url); + const fileContents = fs.readFileSync(path.join(SRC_DIR, imagePath)); + const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; + let DATA = ';base64,' + fileContents.toString('base64'); + if (!forceBase64 && /\.svg$/.test(url)) { + // .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris + let newText = fileContents.toString() + .replace(/"/g, '\'') + .replace(//g, '%3E') + .replace(/&/g, '%26') + .replace(/#/g, '%23') + .replace(/\s+/g, ' '); + let encodedData = ',' + newText; + if (encodedData.length < DATA.length) { + DATA = encodedData; + } + } + return '"data:' + MIME + DATA + '"'; }); } function _replaceURL(contents, replacer) { diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index 49a0cf8d6d..ec579ddad6 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -154,7 +154,7 @@ export function createESMSourcesAndResources2(options: IOptions2): void { continue; } - if (/\.d\.ts$/.test(file) || /\.css$/.test(file) || /\.js$/.test(file)) { + if (/\.d\.ts$/.test(file) || /\.css$/.test(file) || /\.js$/.test(file) || /\.ttf$/.test(file)) { // Transport the files directly write(getDestAbsoluteFilePath(file), fs.readFileSync(path.join(SRC_FOLDER, file))); continue; @@ -290,40 +290,41 @@ function transportCSS(module: string, enqueue: (module: string) => void, write: const filename = path.join(SRC_DIR, module); const fileContents = fs.readFileSync(filename).toString(); const inlineResources = 'base64'; // see https://github.com/Microsoft/monaco-editor/issues/148 - const inlineResourcesLimit = 300000;//3000; // see https://github.com/Microsoft/monaco-editor/issues/336 - const newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64', inlineResourcesLimit); + const newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64'); write(module, newContents); return true; - function _rewriteOrInlineUrls(contents: string, forceBase64: boolean, inlineByteLimit: number): string { + function _rewriteOrInlineUrls(contents: string, forceBase64: boolean): string { return _replaceURL(contents, (url) => { - let imagePath = path.join(path.dirname(module), url); - let fileContents = fs.readFileSync(path.join(SRC_DIR, imagePath)); - - if (fileContents.length < inlineByteLimit) { - const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; - let DATA = ';base64,' + fileContents.toString('base64'); - - if (!forceBase64 && /\.svg$/.test(url)) { - // .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris - let newText = fileContents.toString() - .replace(/"/g, '\'') - .replace(//g, '%3E') - .replace(/&/g, '%26') - .replace(/#/g, '%23') - .replace(/\s+/g, ' '); - let encodedData = ',' + newText; - if (encodedData.length < DATA.length) { - DATA = encodedData; - } - } - return '"data:' + MIME + DATA + '"'; + const fontMatch = url.match(/^(.*).ttf\?(.*)$/); + if (fontMatch) { + const relativeFontPath = `${fontMatch[1]}.ttf`; // trim the query parameter + const fontPath = path.join(path.dirname(module), relativeFontPath); + enqueue(fontPath); + return relativeFontPath; } - enqueue(imagePath); - return url; + const imagePath = path.join(path.dirname(module), url); + const fileContents = fs.readFileSync(path.join(SRC_DIR, imagePath)); + const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; + let DATA = ';base64,' + fileContents.toString('base64'); + + if (!forceBase64 && /\.svg$/.test(url)) { + // .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris + let newText = fileContents.toString() + .replace(/"/g, '\'') + .replace(//g, '%3E') + .replace(/&/g, '%26') + .replace(/#/g, '%23') + .replace(/\s+/g, ' '); + let encodedData = ',' + newText; + if (encodedData.length < DATA.length) { + DATA = encodedData; + } + } + return '"data:' + MIME + DATA + '"'; }); } diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index 3d1049af6b..e6658a6d42 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -51,7 +51,7 @@ "url": "vscode://schemas/keybindings" }, { - "fileMatch": "vscode://defaultsettings/settings.json", + "fileMatch": "vscode://defaultsettings/*/*.json", "url": "vscode://schemas/settings/default" }, { diff --git a/extensions/git/package.json b/extensions/git/package.json index 66b8731c6d..b64dfb4707 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -469,11 +469,11 @@ }, { "command": "git.clean", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && !gitFreshRepository" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.cleanAll", - "when": "config.git.enabled && gitOpenRepositoryCount != 0 && !gitFreshRepository" + "when": "config.git.enabled && gitOpenRepositoryCount != 0" }, { "command": "git.commit", @@ -763,7 +763,7 @@ { "command": "git.cleanAll", "group": "5_stage", - "when": "scmProvider == git && !gitFreshRepository" + "when": "scmProvider == git" }, { "command": "git.stashIncludeUntracked", @@ -831,7 +831,7 @@ }, { "command": "git.cleanAll", - "when": "scmProvider == git && scmResourceGroup == workingTree && !gitFreshRepository", + "when": "scmProvider == git && scmResourceGroup == workingTree", "group": "1_modification" }, { @@ -841,7 +841,7 @@ }, { "command": "git.cleanAll", - "when": "scmProvider == git && scmResourceGroup == workingTree && !gitFreshRepository", + "when": "scmProvider == git && scmResourceGroup == workingTree", "group": "inline" }, { @@ -850,6 +850,53 @@ "group": "inline" } ], + "scm/resourceFolder/context": [ + { + "command": "git.stage", + "when": "scmProvider == git && scmResourceGroup == merge", + "group": "1_modification" + }, + { + "command": "git.stage", + "when": "scmProvider == git && scmResourceGroup == merge", + "group": "inline" + }, + { + "command": "git.unstage", + "when": "scmProvider == git && scmResourceGroup == index", + "group": "1_modification" + }, + { + "command": "git.unstage", + "when": "scmProvider == git && scmResourceGroup == index", + "group": "inline" + }, + { + "command": "git.stage", + "when": "scmProvider == git && scmResourceGroup == workingTree", + "group": "1_modification" + }, + { + "command": "git.clean", + "when": "scmProvider == git && scmResourceGroup == workingTree", + "group": "1_modification" + }, + { + "command": "git.clean", + "when": "scmProvider == git && scmResourceGroup == workingTree", + "group": "inline" + }, + { + "command": "git.stage", + "when": "scmProvider == git && scmResourceGroup == workingTree", + "group": "inline" + }, + { + "command": "git.ignore", + "when": "scmProvider == git && scmResourceGroup == workingTree", + "group": "1_modification@3" + } + ], "scm/resourceState/context": [ { "command": "git.stage", @@ -933,12 +980,12 @@ }, { "command": "git.clean", - "when": "scmProvider == git && scmResourceGroup == workingTree && !gitFreshRepository", + "when": "scmProvider == git && scmResourceGroup == workingTree", "group": "1_modification" }, { "command": "git.clean", - "when": "scmProvider == git && scmResourceGroup == workingTree && !gitFreshRepository", + "when": "scmProvider == git && scmResourceGroup == workingTree", "group": "inline" }, { diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index b4cbdad098..6587b65421 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -55,7 +55,7 @@ "command.syncRebase": "Sync (Rebase)", "command.publish": "Publish Branch", "command.showOutput": "Show Git Output", - "command.ignore": "Add File to .gitignore", + "command.ignore": "Add to .gitignore", "command.stashIncludeUntracked": "Stash (Include Untracked)", "command.stash": "Stash", "command.stashPop": "Pop Stash...", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index d7b0c671b0..c48d8e9209 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { commands, Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, Decoration, Memento, SourceControlInputBoxValidationType, OutputChannel, LogLevel, env, ProgressOptions, CancellationToken } from 'vscode'; +import { Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, SourceControlInputBoxValidation, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, Decoration, Memento, SourceControlInputBoxValidationType, OutputChannel, LogLevel, env, ProgressOptions, CancellationToken } from 'vscode'; import { Repository as BaseRepository, Commit, Stash, GitError, Submodule, CommitOptions, ForcePushMode } from './git'; import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent, combinedDisposable } from './util'; import { memoize, throttle, debounce } from './decorators'; @@ -633,7 +633,6 @@ export class Repository implements Disposable { private isRepositoryHuge = false; private didWarnAboutLimit = false; - private isFreshRepository: boolean | undefined = undefined; private disposables: Disposable[] = []; @@ -1507,15 +1506,6 @@ export class Repository implements Disposable { // set count badge this.setCountBadge(); - // Disable `Discard All Changes` for "fresh" repositories - // https://github.com/Microsoft/vscode/issues/43066 - const isFreshRepository = !this._HEAD || !this._HEAD.commit; - - if (this.isFreshRepository !== isFreshRepository) { - commands.executeCommand('setContext', 'gitFreshRepository', isFreshRepository); - this.isFreshRepository = isFreshRepository; - } - this._onDidChangeStatus.fire(); } diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 6d99b5f84a..28c4648325 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -14,7 +14,7 @@ "dependencies": { "jsonc-parser": "^2.1.1", "request-light": "^0.2.4", - "vscode-json-languageservice": "^3.3.3", + "vscode-json-languageservice": "^3.3.4", "vscode-languageserver": "^5.3.0-next.8", "vscode-nls": "^4.1.1", "vscode-uri": "^2.0.3" diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 12e39de96a..8230b9546b 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -73,10 +73,10 @@ request-light@^0.2.4: https-proxy-agent "^2.2.1" vscode-nls "^4.0.0" -vscode-json-languageservice@^3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.3.3.tgz#f7e512a2cd5e82fecbebf507d6fceaea47661297" - integrity sha512-5vL3OXTUuQpn6+tGd47dopio+7WwbtIZ07zfYMzAUX8eVWPZjfEsLeSWmQk5Xw+vwgu+j5zC4koz5UofLDGGRA== +vscode-json-languageservice@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.3.4.tgz#4ff67580491d3a5dc469f4a78643f20adff0278d" + integrity sha512-/nuI4uDBfxyVyeGtBdYwP/tIaXYKOoymUOSozYKLzsmrDmu555gZpzc11LrARa96z92wSaa5hfjTtNMAoM2mxw== dependencies: jsonc-parser "^2.1.1" vscode-languageserver-types "^3.15.0-next.2" diff --git a/extensions/markdown-language-features/media/markdown.css b/extensions/markdown-language-features/media/markdown.css index cfe9a45cdc..ebf3b5c35f 100644 --- a/extensions/markdown-language-features/media/markdown.css +++ b/extensions/markdown-language-features/media/markdown.css @@ -182,13 +182,12 @@ pre.hljs code > div { overflow: auto; } -/** Theming */ - pre code { color: var(--vscode-editor-foreground); tab-size: 4; } +/** Theming */ .vscode-light pre { background-color: rgba(220, 220, 220, 0.4); diff --git a/extensions/merge-conflict/src/codelensProvider.ts b/extensions/merge-conflict/src/codelensProvider.ts index 3e170ae78f..d55725f542 100644 --- a/extensions/merge-conflict/src/codelensProvider.ts +++ b/extensions/merge-conflict/src/codelensProvider.ts @@ -100,6 +100,7 @@ export default class MergeConflictCodeLensProvider implements vscode.CodeLensPro this.codeLensRegistrationHandle = vscode.languages.registerCodeLensProvider([ { scheme: 'file' }, { scheme: 'untitled' }, + { scheme: 'vscode-userdata' }, ], this); } } diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index 3efcc124ff..f8a0ff5b7c 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -23,6 +23,7 @@ "support.type", "entity.name.type", "entity.name.namespace", + "entity.other.attribute", "entity.name.scope-resolution", "entity.name.class", "storage.type.numeric.go", @@ -76,7 +77,8 @@ "source.cpp keyword.operator.new", "keyword.operator.delete", "keyword.other.using", - "keyword.other.operator" + "keyword.other.operator", + "entity.name.operator" ], "settings": { "foreground": "#C586C0" @@ -171,72 +173,11 @@ "foreground": "#d7ba7d" } }, - // Scopes that are potentially C++ only follow { - "scope": "source.cpp entity.name", + "scope": "entity.name.label", "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/dark_vs.json b/extensions/theme-defaults/themes/dark_vs.json index 5ad18277a0..34228835df 100644 --- a/extensions/theme-defaults/themes/dark_vs.json +++ b/extensions/theme-defaults/themes/dark_vs.json @@ -44,7 +44,11 @@ }, { "scope": [ - "constant.numeric" + "constant.numeric", + "entity.name.operator.custom-literal.number", + "variable.other.enummember", + "keyword.operator.plus.exponent", + "keyword.operator.minus.exponent" ], "settings": { "foreground": "#b5cea8" @@ -166,7 +170,10 @@ } }, { - "scope": "meta.preprocessor", + "scope": [ + "meta.preprocessor", + "keyword.control.directive" + ], "settings": { "foreground": "#569cd6" } @@ -208,13 +215,19 @@ } }, { - "scope": "storage.modifier", + "scope": [ + "storage.modifier", + "keyword.operator.noexcept" + ], "settings": { "foreground": "#569cd6" } }, { - "scope": "string", + "scope": [ + "string", + "entity.name.operator.custom-literal.string", + ], "settings": { "foreground": "#ce9178" } @@ -294,8 +307,11 @@ "keyword.operator.expression", "keyword.operator.cast", "keyword.operator.sizeof", + "keyword.operator.typeid", + "keyword.operator.alignas", "keyword.operator.instanceof", - "keyword.operator.logical.python" + "keyword.operator.logical.python", + "keyword.operator.wordlike" ], "settings": { "foreground": "#569cd6" @@ -347,4 +363,4 @@ } } ] -} \ No newline at end of file +} diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index e6a4c92235..c7599d60d5 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -23,6 +23,7 @@ "support.type", "entity.name.type", "entity.name.namespace", + "entity.other.attribute", "entity.name.scope-resolution", "entity.name.class", "storage.type.numeric.go", @@ -76,7 +77,8 @@ "source.cpp keyword.operator.new", "source.cpp keyword.operator.delete", "keyword.other.using", - "keyword.other.operator" + "keyword.other.operator", + "entity.name.operator" ], "settings": { "foreground": "#AF00DB" @@ -171,54 +173,11 @@ "foreground": "#ff0000" } }, - // Scopes that are potentially C++ only follow { - "scope": "source.cpp entity.name", + "scope": "entity.name.label", "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/theme-defaults/themes/light_vs.json b/extensions/theme-defaults/themes/light_vs.json index 97787c38bd..96cb7a76fa 100644 --- a/extensions/theme-defaults/themes/light_vs.json +++ b/extensions/theme-defaults/themes/light_vs.json @@ -4,7 +4,10 @@ "include": "./light_defaults.json", "tokenColors": [ { - "scope": ["meta.embedded", "source.groovy.embedded"], + "scope": [ + "meta.embedded", + "source.groovy.embedded" + ], "settings": { "foreground": "#000000ff" } @@ -41,7 +44,11 @@ }, { "scope": [ - "constant.numeric" + "constant.numeric", + "entity.name.operator.custom-literal.number", + "variable.other.enummember", + "keyword.operator.plus.exponent", + "keyword.operator.minus.exponent" ], "settings": { "foreground": "#09885a" @@ -161,7 +168,10 @@ } }, { - "scope": "meta.preprocessor", + "scope": [ + "meta.preprocessor", + "keyword.control.directive" + ], "settings": { "foreground": "#0000ff" } @@ -197,13 +207,19 @@ } }, { - "scope": "storage.modifier", + "scope": [ + "storage.modifier", + "keyword.operator.noexcept" + ], "settings": { "foreground": "#0000ff" } }, { - "scope": "string", + "scope": [ + "string", + "entity.name.operator.custom-literal.string", + ], "settings": { "foreground": "#a31515" } @@ -315,8 +331,11 @@ "keyword.operator.expression", "keyword.operator.cast", "keyword.operator.sizeof", + "keyword.operator.typeid", + "keyword.operator.alignas", "keyword.operator.instanceof", - "keyword.operator.logical.python" + "keyword.operator.logical.python", + "keyword.operator.wordlike" ], "settings": { "foreground": "#0000ff" @@ -368,4 +387,4 @@ } } ] -} \ No newline at end of file +} diff --git a/package.json b/package.json index 452a31e3ee..5299302861 100644 --- a/package.json +++ b/package.json @@ -167,7 +167,7 @@ "vinyl": "^2.0.0", "vinyl-fs": "^3.0.0", "vsce": "1.48.0", - "vscode-debugprotocol": "1.36.0", + "vscode-debugprotocol": "1.37.0", "vscode-nls-dev": "^3.3.1", "webpack": "^4.16.5", "webpack-cli": "^3.3.8", diff --git a/resources/win32/bin/code.sh b/resources/win32/bin/code.sh index a915930e70..74ca1e3d83 100644 --- a/resources/win32/bin/code.sh +++ b/resources/win32/bin/code.sh @@ -34,7 +34,7 @@ if grep -qi Microsoft /proc/version; then WSL_EXT_WLOC=$(cmd.exe /C type %TEMP%\\remote-wsl-loc.txt) cd "$CWD" else - ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --locate-extension $WSL_EXT_ID >/tmp/remote-wsl-loc.txt + ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --locate-extension $WSL_EXT_ID >/tmp/remote-wsl-loc.txt 2>/dev/null WSL_EXT_WLOC=$(cat /tmp/remote-wsl-loc.txt) fi if [ -n "$WSL_EXT_WLOC" ]; then diff --git a/src/sql/platform/accounts/browser/firewallRuleDialog.ts b/src/sql/platform/accounts/browser/firewallRuleDialog.ts index c34e5aa487..fc47a26101 100644 --- a/src/sql/platform/accounts/browser/firewallRuleDialog.ts +++ b/src/sql/platform/accounts/browser/firewallRuleDialog.ts @@ -15,9 +15,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { URI } from 'vs/base/common/uri'; import * as azdata from 'azdata'; import { Button } from 'sql/base/browser/ui/button/button'; @@ -30,6 +30,7 @@ import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; // TODO: Make the help link 1) extensible (01/08/2018, https://github.com/Microsoft/azuredatastudio/issues/450) // in case that other non-Azure sign in is to be used @@ -70,10 +71,10 @@ export class FirewallRuleDialog extends Modal { @IContextViewService private _contextViewService: IContextViewService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, - @IWindowsService private _windowsService: IWindowsService, @IClipboardService clipboardService: IClipboardService, @ILogService logService: ILogService, - @ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService + @ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService, + @IOpenerService private readonly openerService: IOpenerService ) { super( localize('createNewFirewallRule', "Create new firewall rule"), @@ -125,7 +126,7 @@ export class FirewallRuleDialog extends Modal { this._helpLink.setAttribute('href', firewallHelpUri); this._helpLink.innerHTML += localize('firewallRuleHelpDescription', "Learn more about firewall settings"); this._helpLink.onclick = () => { - this._windowsService.openExternal(firewallHelpUri); + this.openerService.open(URI.parse(firewallHelpUri)); }; // Create account picker with event handling diff --git a/src/sql/workbench/api/browser/mainThreadExtensionManagement.ts b/src/sql/workbench/api/browser/mainThreadExtensionManagement.ts index bd45191f53..42dac813d6 100644 --- a/src/sql/workbench/api/browser/mainThreadExtensionManagement.ts +++ b/src/sql/workbench/api/browser/mainThreadExtensionManagement.ts @@ -12,7 +12,6 @@ import { URI } from 'vs/base/common/uri'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; -import { IWindowService } from 'vs/platform/windows/common/windows'; @extHostNamedCustomer(SqlMainContext.MainThreadExtensionManagement) export class MainThreadExtensionManagement extends Disposable implements MainThreadExtensionManagementShape { @@ -23,8 +22,7 @@ export class MainThreadExtensionManagement extends Disposable implements MainThr extHostContext: IExtHostContext, @IExtensionManagementService private _extensionService: IExtensionManagementService, @IConfigurationService private _configurationService: IConfigurationService, - @INotificationService private _notificationService: INotificationService, - @IWindowService protected readonly _windowService: IWindowService + @INotificationService private _notificationService: INotificationService ) { super(); } @@ -53,9 +51,6 @@ export class MainThreadExtensionManagement extends Disposable implements MainThr this._configurationService.updateValue('workbench.enableObsoleteApiUsageNotification', false, ConfigurationTarget.USER); }, isSecondary: true - }, { - label: localize('devTools', "Open Developer Tools"), - run: () => this._windowService.openDevTools() }]); this._obsoleteExtensionApiUsageNotificationShown = true; } diff --git a/src/sql/workbench/browser/actions.ts b/src/sql/workbench/browser/actions.ts index bcc8df2fcc..2369784835 100644 --- a/src/sql/workbench/browser/actions.ts +++ b/src/sql/workbench/browser/actions.ts @@ -12,10 +12,11 @@ import { Task } from 'sql/platform/tasks/browser/tasksRegistry'; import { ObjectMetadata } from 'azdata'; import { Action } from 'vs/base/common/actions'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; import * as nls from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IInsightsConfig } from 'sql/platform/dashboard/browser/insightRegistry'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; export interface BaseActionContext { object?: ObjectMetadata; @@ -85,6 +86,6 @@ export class ConfigureDashboardAction extends Task { } runTask(accessor: ServicesAccessor): Promise { - return accessor.get(IWindowsService).openExternal(ConfigureDashboardAction.configHelpUri).then(); + return accessor.get(IOpenerService).open(URI.parse(ConfigureDashboardAction.configHelpUri)).then(); } } diff --git a/src/sql/workbench/common/workspaceActions.ts b/src/sql/workbench/common/workspaceActions.ts index d8f1deae3c..148306372f 100644 --- a/src/sql/workbench/common/workspaceActions.ts +++ b/src/sql/workbench/common/workspaceActions.ts @@ -4,28 +4,31 @@ *--------------------------------------------------------------------------------------------*/ import { Action } from 'vs/base/common/actions'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; //tslint:disable-next-line:layering import { ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; +//tslint:disable-next-line:layering +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { URI } from 'vs/base/common/uri'; export class ShowFileInFolderAction extends Action { - constructor(private path: string, label: string, private windowsService: ElectronMainService) { + constructor(private path: string, label: string, @IElectronService private windowsService: ElectronMainService) { super('showItemInFolder.action.id', label); } run(): Promise { - return this.windowsService.showItemInFolder(this.path); + return this.windowsService.showItemInFolder(undefined, this.path); } } export class OpenFileInFolderAction extends Action { - constructor(private path: string, label: string, private windowsService: IWindowsService) { + constructor(private path: string, label: string, @IOpenerService private openerService: IOpenerService) { super('showItemInFolder.action.id', label); } run() { - return this.windowsService.openExternal(this.path); + return this.openerService.open(URI.file(this.path)); } } diff --git a/src/sql/workbench/parts/charts/browser/actions.ts b/src/sql/workbench/parts/charts/browser/actions.ts index 5b44c26682..6b549235d5 100644 --- a/src/sql/workbench/parts/charts/browser/actions.ts +++ b/src/sql/workbench/parts/charts/browser/actions.ts @@ -20,6 +20,7 @@ import { IInsightOptions } from 'sql/workbench/parts/charts/common/interfaces'; import { IFileService } from 'vs/platform/files/common/files'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { VSBuffer } from 'vs/base/common/buffer'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; export interface IChartActionContext { options: IInsightOptions; @@ -142,10 +143,10 @@ export class SaveImageAction extends Action { public static ICON = 'saveAsImage'; constructor( - @IWindowsService private readonly windowsService: IWindowsService, @INotificationService private readonly notificationService: INotificationService, @IFileService private readonly fileService: IFileService, - @IFileDialogService private readonly fileDialogService: IFileDialogService + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IOpenerService private readonly openerService: IOpenerService ) { super(SaveImageAction.ID, SaveImageAction.LABEL, SaveImageAction.ICON); } @@ -168,7 +169,7 @@ export class SaveImageAction extends Action { if (err) { this.notificationService.error(err.message); } else { - this.windowsService.openExternal(filePath.toString()); + this.openerService.open(filePath, { openExternal: true }); this.notificationService.notify({ severity: Severity.Error, message: localize('chartSaved', "Saved Chart to path: {0}", filePath.toString()) diff --git a/src/sql/workbench/parts/notebook/browser/notebook.contribution.ts b/src/sql/workbench/parts/notebook/browser/notebook.contribution.ts index b9afa5dc40..d81d1c87e8 100644 --- a/src/sql/workbench/parts/notebook/browser/notebook.contribution.ts +++ b/src/sql/workbench/parts/notebook/browser/notebook.contribution.ts @@ -35,6 +35,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ObjectExplorerActionsContext } from 'sql/workbench/parts/objectExplorer/browser/objectExplorerActions'; import { ItemContextKey } from 'sql/workbench/parts/dashboard/browser/widgets/explorer/explorerTreeContext'; import { ManageActionContext } from 'sql/workbench/browser/actions'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { MarkdownOutputComponent } from 'sql/workbench/parts/notebook/browser/outputs/markdownOutput.component'; import { registerCellComponent } from 'sql/platform/notebooks/common/outputRegistry'; import { TextCellComponent } from 'sql/workbench/parts/notebook/browser/cellViews/textCell.component'; @@ -130,6 +131,7 @@ registerAction({ const viewletService = accessor.get(IViewletService); const workspaceEditingService = accessor.get(IWorkspaceEditingService); const windowService = accessor.get(IWindowService); + const hostService = accessor.get(IHostService); let folders = []; if (!options.folderPath) { return; @@ -141,7 +143,7 @@ registerAction({ return windowService.openWindow([{ folderUri: folders[0] }], { forceNewWindow: options.forceNewWindow }); } else { - return windowService.reloadWindow(); + return hostService.reload(); } } }); diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index a2c2be8f4a..2972c42d70 100644 --- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -5,7 +5,7 @@ @font-face { font-family: "codicon"; - src: url("./codicon.ttf?e042d2dda15ef7b36b910e3edf539f26") format("truetype"); + src: url("./codicon.ttf?3b584136fb1f0186a1ee578cdcb5240b") format("truetype"); } .codicon[class*='codicon-'] { @@ -203,152 +203,153 @@ .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" } +.codicon-gear:before { content: "\f165" } +.codicon-gift:before { content: "\f166" } +.codicon-gist-secret:before { content: "\f167" } +.codicon-gist:before { content: "\f168" } +.codicon-git-commit:before { content: "\f169" } +.codicon-git-compare:before { content: "\f16a" } +.codicon-git-merge:before { content: "\f16b" } +.codicon-github-action:before { content: "\f16c" } +.codicon-github-alt:before { content: "\f16d" } +.codicon-github:before { content: "\f16e" } +.codicon-globe:before { content: "\f16f" } +.codicon-go-to-file:before { content: "\f170" } +.codicon-grabber:before { content: "\f171" } +.codicon-graph:before { content: "\f172" } +.codicon-gripper:before { content: "\f173" } +.codicon-heart:before { content: "\f174" } +.codicon-history:before { content: "\f175" } +.codicon-home:before { content: "\f176" } +.codicon-horizontal-rule:before { content: "\f177" } +.codicon-hubot:before { content: "\f178" } +.codicon-inbox:before { content: "\f179" } +.codicon-interface:before { content: "\f17a" } +.codicon-issue-closed:before { content: "\f17b" } +.codicon-issue-reopened:before { content: "\f17c" } +.codicon-issues:before { content: "\f17d" } +.codicon-italic:before { content: "\f17e" } +.codicon-jersey:before { content: "\f17f" } +.codicon-json:before { content: "\f180" } +.codicon-kebab-vertical:before { content: "\f181" } +.codicon-key:before { content: "\f182" } +.codicon-keyword:before { content: "\f183" } +.codicon-law:before { content: "\f184" } +.codicon-lightbulb-autofix:before { content: "\f185" } +.codicon-link-external:before { content: "\f186" } +.codicon-link:before { content: "\f187" } +.codicon-list-ordered:before { content: "\f188" } +.codicon-list-unordered:before { content: "\f189" } +.codicon-live-share:before { content: "\f18a" } +.codicon-loading:before { content: "\f18b" } +.codicon-location:before { content: "\f18c" } +.codicon-mail-read:before { content: "\f18d" } +.codicon-mail:before { content: "\f18e" } +.codicon-markdown:before { content: "\f18f" } +.codicon-megaphone:before { content: "\f190" } +.codicon-mention:before { content: "\f191" } +.codicon-method:before { content: "\f192" } +.codicon-milestone:before { content: "\f193" } +.codicon-misc:before { content: "\f194" } +.codicon-mortar-board:before { content: "\f195" } +.codicon-move:before { content: "\f196" } +.codicon-multiple-windows:before { content: "\f197" } +.codicon-mute:before { content: "\f198" } +.codicon-namespace:before { content: "\f199" } +.codicon-no-newline:before { content: "\f19a" } +.codicon-note:before { content: "\f19b" } +.codicon-numeric:before { content: "\f19c" } +.codicon-octoface:before { content: "\f19d" } +.codicon-open-preview:before { content: "\f19e" } +.codicon-operator:before { content: "\f19f" } +.codicon-package:before { content: "\f1a0" } +.codicon-paintcan:before { content: "\f1a1" } +.codicon-parameter:before { content: "\f1a2" } +.codicon-pause:before { content: "\f1a3" } +.codicon-pin:before { content: "\f1a4" } +.codicon-play:before { content: "\f1a5" } +.codicon-plug:before { content: "\f1a6" } +.codicon-preserve-case:before { content: "\f1a7" } +.codicon-preview:before { content: "\f1a8" } +.codicon-project:before { content: "\f1a9" } +.codicon-property:before { content: "\f1aa" } +.codicon-pulse:before { content: "\f1ab" } +.codicon-question:before { content: "\f1ac" } +.codicon-quote:before { content: "\f1ad" } +.codicon-radio-tower:before { content: "\f1ae" } +.codicon-reactions:before { content: "\f1af" } +.codicon-references:before { content: "\f1b0" } +.codicon-refresh:before { content: "\f1b1" } +.codicon-regex:before { content: "\f1b2" } +.codicon-remote:before { content: "\f1b3" } +.codicon-remove:before { content: "\f1b4" } +.codicon-replace-all:before { content: "\f1b5" } +.codicon-replace:before { content: "\f1b6" } +.codicon-repo-clone:before { content: "\f1b7" } +.codicon-repo-force-push:before { content: "\f1b8" } +.codicon-repo-pull:before { content: "\f1b9" } +.codicon-repo-push:before { content: "\f1ba" } +.codicon-report:before { content: "\f1bb" } +.codicon-request-changes:before { content: "\f1bc" } +.codicon-restart:before { content: "\f1bd" } +.codicon-rocket:before { content: "\f1be" } +.codicon-root-folder-opened:before { content: "\f1bf" } +.codicon-root-folder:before { content: "\f1c0" } +.codicon-rss:before { content: "\f1c1" } +.codicon-ruby:before { content: "\f1c2" } +.codicon-ruler:before { content: "\f1c3" } +.codicon-save-all:before { content: "\f1c4" } +.codicon-save-as:before { content: "\f1c5" } +.codicon-save:before { content: "\f1c6" } +.codicon-screen-full:before { content: "\f1c7" } +.codicon-screen-normal:before { content: "\f1c8" } +.codicon-search-stop:before { content: "\f1c9" } +.codicon-selection:before { content: "\f1ca" } +.codicon-server:before { content: "\f1cb" } +.codicon-settings:before { content: "\f1cc" } +.codicon-shield:before { content: "\f1cd" } +.codicon-smiley:before { content: "\f1ce" } +.codicon-snippet:before { content: "\f1cf" } +.codicon-sort-precedence:before { content: "\f1d0" } +.codicon-split-horizontal:before { content: "\f1d1" } +.codicon-split-vertical:before { content: "\f1d2" } +.codicon-squirrel:before { content: "\f1d3" } +.codicon-star-empty:before { content: "\f1d4" } +.codicon-star-full:before { content: "\f1d5" } +.codicon-star-half:before { content: "\f1d6" } +.codicon-start:before { content: "\f1d7" } +.codicon-step-into:before { content: "\f1d8" } +.codicon-step-out:before { content: "\f1d9" } +.codicon-step-over:before { content: "\f1da" } +.codicon-string:before { content: "\f1db" } +.codicon-structure:before { content: "\f1dc" } +.codicon-tasklist:before { content: "\f1dd" } +.codicon-telescope:before { content: "\f1de" } +.codicon-text-size:before { content: "\f1df" } +.codicon-three-bars:before { content: "\f1e0" } +.codicon-thumbsdown:before { content: "\f1e1" } +.codicon-thumbsup:before { content: "\f1e2" } +.codicon-tools:before { content: "\f1e3" } +.codicon-trash:before { content: "\f1e4" } +.codicon-triangle-down:before { content: "\f1e5" } +.codicon-triangle-left:before { content: "\f1e6" } +.codicon-triangle-right:before { content: "\f1e7" } +.codicon-triangle-up:before { content: "\f1e8" } +.codicon-twitter:before { content: "\f1e9" } +.codicon-unfold:before { content: "\f1ea" } +.codicon-unlock:before { content: "\f1eb" } +.codicon-unmute:before { content: "\f1ec" } +.codicon-unverified:before { content: "\f1ed" } +.codicon-variable:before { content: "\f1ee" } +.codicon-verified:before { content: "\f1ef" } +.codicon-versions:before { content: "\f1f0" } +.codicon-vm-active:before { content: "\f1f1" } +.codicon-vm-outline:before { content: "\f1f2" } +.codicon-vm-running:before { content: "\f1f3" } +.codicon-watch:before { content: "\f1f4" } +.codicon-whitespace:before { content: "\f1f5" } +.codicon-whole-word:before { content: "\f1f6" } +.codicon-window:before { content: "\f1f7" } +.codicon-word-wrap:before { content: "\f1f8" } +.codicon-zoom-in:before { content: "\f1f9" } +.codicon-zoom-out:before { content: "\f1fa" } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf index 688b9ffd0b..fff7ab5197 100644 Binary files a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf 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 deleted file mode 100644 index ef04a20569..0000000000 --- a/src/vs/base/browser/ui/codiconLabel/codiconLabel.mock.ts +++ /dev/null @@ -1,24 +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 { 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/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index 21788bc0ca..6a1aa43e71 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -295,7 +295,7 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { render(container: HTMLElement): void { const labelRenderer: ILabelRenderer = (el: HTMLElement): IDisposable | null => { - this.element = append(el, $('a.action-label.icon')); + this.element = append(el, $('a.action-label.codicon')); if (this.clazz) { addClasses(this.element, this.clazz); } diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index 9dda103ae8..1fccfc96a7 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -32,6 +32,7 @@ export interface IReplaceInputOptions extends IReplaceInputStyles { export interface IReplaceInputStyles extends IInputBoxStyles { inputActiveOptionBorder?: Color; + inputActiveOptionBackground?: Color; } const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input"); @@ -44,7 +45,8 @@ export class PreserveCaseCheckbox extends Checkbox { actionClassName: 'codicon-preserve-case', title: NLS_PRESERVE_CASE_LABEL + opts.appendTitle, isChecked: opts.isChecked, - inputActiveOptionBorder: opts.inputActiveOptionBorder + inputActiveOptionBorder: opts.inputActiveOptionBorder, + inputActiveOptionBackground: opts.inputActiveOptionBackground }); } } @@ -60,6 +62,7 @@ export class ReplaceInput extends Widget { private fixFocusOnOptionClickEnabled = true; private inputActiveOptionBorder?: Color; + private inputActiveOptionBackground?: Color; private inputBackground?: Color; private inputForeground?: Color; private inputBorder?: Color; @@ -105,6 +108,7 @@ export class ReplaceInput extends Widget { this.label = options.label || NLS_DEFAULT_LABEL; this.inputActiveOptionBorder = options.inputActiveOptionBorder; + this.inputActiveOptionBackground = options.inputActiveOptionBackground; this.inputBackground = options.inputBackground; this.inputForeground = options.inputForeground; this.inputBorder = options.inputBorder; @@ -181,6 +185,7 @@ export class ReplaceInput extends Widget { public style(styles: IReplaceInputStyles): void { this.inputActiveOptionBorder = styles.inputActiveOptionBorder; + this.inputActiveOptionBackground = styles.inputActiveOptionBackground; this.inputBackground = styles.inputBackground; this.inputForeground = styles.inputForeground; this.inputBorder = styles.inputBorder; @@ -202,6 +207,7 @@ export class ReplaceInput extends Widget { if (this.domNode) { const checkBoxStyles: ICheckboxStyles = { inputActiveOptionBorder: this.inputActiveOptionBorder, + inputActiveOptionBackground: this.inputActiveOptionBackground, }; this.preserveCase.style(checkBoxStyles); @@ -281,7 +287,8 @@ export class ReplaceInput extends Widget { this.preserveCase = this._register(new PreserveCaseCheckbox({ appendTitle: '', isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder + inputActiveOptionBorder: this.inputActiveOptionBorder, + inputActiveOptionBackground: this.inputActiveOptionBackground, })); this._register(this.preserveCase.onChange(viaKeyboard => { this._onDidOptionChange.fire(viaKeyboard); diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 8b38bcd655..078f6ad0eb 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -188,12 +188,12 @@ class BranchNode implements ISplitView, IDisposable { return this.orientation === Orientation.HORIZONTAL ? this.maximumSize : this.maximumOrthogonalSize; } - private _onDidChange = new Emitter(); + private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; private childrenChangeDisposable: IDisposable = Disposable.None; - private _onDidSashReset = new Emitter(); + private readonly _onDidSashReset = new Emitter(); readonly onDidSashReset: Event = this._onDidSashReset.event; private splitviewSashResetDisposable: IDisposable = Disposable.None; private childrenSashResetDisposable: IDisposable = Disposable.None; @@ -539,7 +539,7 @@ class LeafNode implements ISplitView, IDisposable { this._onDidSetLinkedNode.fire(undefined); } - private _onDidSetLinkedNode = new Emitter(); + private readonly _onDidSetLinkedNode = new Emitter(); private _onDidViewChange: Event; readonly onDidChange: Event; diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index 3c6ac72122..7d8c83de33 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -79,7 +79,10 @@ export interface IKeyboardNavigationLabelProvider { * element always match. */ getKeyboardNavigationLabel(element: T): { toString(): string | undefined; } | undefined; - mightProducePrintableCharacter?(event: IKeyboardEvent): boolean; +} + +export interface IKeyboardNavigationDelegate { + mightProducePrintableCharacter(event: IKeyboardEvent): boolean; } export const enum ListDragOverEffect { diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index f6dd11b08c..f52e10f098 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./list'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { range } from 'vs/base/common/arrays'; import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent } from './list'; import { List, IListStyles, IListOptions } from './listWidget'; @@ -32,7 +32,7 @@ class PagedRenderer implements IListRenderer { const data = this.renderer.renderTemplate(container); - return { data, disposable: { dispose: () => { } } }; + return { data, disposable: Disposable.None }; } renderElement(index: number, _: number, data: ITemplateData, height: number | undefined): void { @@ -127,7 +127,7 @@ export class PagedList implements IDisposable { } get onPin(): Event> { - return Event.map(this.list.onPin, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); + return Event.map(this.list.onDidPin, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } get onContextMenu(): Event> { diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 9063a2466f..f408e9cf5d 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -190,7 +190,7 @@ export class ListView implements ISpliceable, IDisposable { private readonly disposables: DisposableStore = new DisposableStore(); - private _onDidChangeContentHeight = new Emitter(); + private readonly _onDidChangeContentHeight = new Emitter(); readonly onDidChangeContentHeight: Event = Event.latch(this._onDidChangeContentHeight.event); get contentHeight(): number { return this.rangeMap.size; } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 7816dbe0c2..4972a5d18e 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -16,7 +16,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Event, Emitter, EventBufferer } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; -import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListAriaRootRole, ListError } from './list'; +import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListAriaRootRole, ListError, IKeyboardNavigationDelegate } from './list'; import { ListView, IListViewOptions, IListViewDragAndDrop, IAriaProvider } from './listView'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; @@ -110,7 +110,7 @@ class Trait implements ISpliceable, IDisposable { private indexes: number[] = []; private sortedIndexes: number[] = []; - private _onChange = new Emitter(); + private readonly _onChange = new Emitter(); readonly onChange: Event = this._onChange.event; get trait(): string { return this._trait; } @@ -176,7 +176,7 @@ class Trait implements ISpliceable, IDisposable { } dispose() { - this._onChange = dispose(this._onChange); + dispose(this._onChange); } } @@ -322,16 +322,18 @@ enum TypeLabelControllerState { Typing } -export function mightProducePrintableCharacter(event: IKeyboardEvent): boolean { - if (event.ctrlKey || event.metaKey || event.altKey) { - return false; - } +export const DefaultKeyboardNavigationDelegate = new class implements IKeyboardNavigationDelegate { + mightProducePrintableCharacter(event: IKeyboardEvent): boolean { + if (event.ctrlKey || event.metaKey || event.altKey) { + return false; + } - return (event.keyCode >= KeyCode.KEY_A && event.keyCode <= KeyCode.KEY_Z) - || (event.keyCode >= KeyCode.KEY_0 && event.keyCode <= KeyCode.KEY_9) - || (event.keyCode >= KeyCode.NUMPAD_0 && event.keyCode <= KeyCode.NUMPAD_9) - || (event.keyCode >= KeyCode.US_SEMICOLON && event.keyCode <= KeyCode.US_QUOTE); -} + return (event.keyCode >= KeyCode.KEY_A && event.keyCode <= KeyCode.KEY_Z) + || (event.keyCode >= KeyCode.KEY_0 && event.keyCode <= KeyCode.KEY_9) + || (event.keyCode >= KeyCode.NUMPAD_0 && event.keyCode <= KeyCode.NUMPAD_9) + || (event.keyCode >= KeyCode.US_SEMICOLON && event.keyCode <= KeyCode.US_QUOTE); + } +}; class TypeLabelController implements IDisposable { @@ -347,7 +349,8 @@ class TypeLabelController implements IDisposable { constructor( private list: List, private view: ListView, - private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider + private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider, + private delegate: IKeyboardNavigationDelegate ) { this.updateOptions(list.options); } @@ -379,7 +382,7 @@ class TypeLabelController implements IDisposable { .filter(e => !isInputElement(e.target as HTMLElement)) .filter(() => this.automaticKeyboardNavigation || this.triggered) .map(event => new StandardKeyboardEvent(event)) - .filter(this.keyboardNavigationLabelProvider.mightProducePrintableCharacter ? e => this.keyboardNavigationLabelProvider.mightProducePrintableCharacter!(e) : e => mightProducePrintableCharacter(e)) + .filter(e => this.delegate.mightProducePrintableCharacter(e)) .forEach(e => { e.stopPropagation(); e.preventDefault(); }) .map(event => event.browserEvent.key) .event; @@ -818,6 +821,7 @@ export interface IListOptions extends IListStyles { readonly enableKeyboardNavigation?: boolean; readonly automaticKeyboardNavigation?: boolean; readonly keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider; + readonly keyboardNavigationDelegate?: IKeyboardNavigationDelegate; readonly ariaRole?: ListAriaRootRole; readonly ariaLabel?: string; readonly keyboardSupport?: boolean; @@ -1107,13 +1111,11 @@ export class List implements ISpliceable, IDisposable { return Event.map(this.eventBufferer.wrapEvent(this.selection.onChange), e => this.toListEvent(e)); } - private _onDidOpen = new Emitter>(); + private readonly _onDidOpen = new Emitter>(); readonly onDidOpen: Event> = this._onDidOpen.event; - private _onPin = new Emitter(); - @memoize get onPin(): Event> { - return Event.map(this._onPin.event, indexes => this.toListEvent({ indexes })); - } + private readonly _onDidPin = new Emitter>(); + readonly onDidPin: Event> = this._onDidPin.event; get domId(): string { return this.view.domId; } get onDidScroll(): Event { return this.view.onDidScroll; } @@ -1166,7 +1168,7 @@ export class List implements ISpliceable, IDisposable { readonly onDidFocus: Event; readonly onDidBlur: Event; - private _onDidDispose = new Emitter(); + private readonly _onDidDispose = new Emitter(); readonly onDidDispose: Event = this._onDidDispose.event; constructor( @@ -1228,7 +1230,8 @@ export class List implements ISpliceable, IDisposable { } if (_options.keyboardNavigationLabelProvider) { - this.typeLabelController = new TypeLabelController(this, this.view, _options.keyboardNavigationLabelProvider); + const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate; + this.typeLabelController = new TypeLabelController(this, this.view, _options.keyboardNavigationLabelProvider, delegate); this.disposables.add(this.typeLabelController); } @@ -1582,14 +1585,14 @@ export class List implements ISpliceable, IDisposable { this._onDidOpen.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent }); } - pin(indexes: number[]): void { + pin(indexes: number[], browserEvent?: UIEvent): void { for (const index of indexes) { if (index < 0 || index >= this.length) { throw new ListError(this.user, `Invalid index ${index}`); } } - this._onPin.fire(indexes); + this._onDidPin.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent }); } style(styles: IListStyles): void { @@ -1626,7 +1629,7 @@ export class List implements ISpliceable, IDisposable { this.disposables.dispose(); this._onDidOpen.dispose(); - this._onPin.dispose(); + this._onDidPin.dispose(); this._onDidDispose.dispose(); } } diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 7077adb5bc..d1d28c74b0 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -39,8 +39,6 @@ class SelectListRenderer implements IListRenderer - - - - diff --git a/src/vs/base/browser/ui/toolbar/ellipsis-hc.svg b/src/vs/base/browser/ui/toolbar/ellipsis-hc.svg deleted file mode 100644 index 3d7068f6b4..0000000000 --- a/src/vs/base/browser/ui/toolbar/ellipsis-hc.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/base/browser/ui/toolbar/ellipsis-light.svg b/src/vs/base/browser/ui/toolbar/ellipsis-light.svg deleted file mode 100644 index 883d2722ce..0000000000 --- a/src/vs/base/browser/ui/toolbar/ellipsis-light.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/base/browser/ui/toolbar/toolbar.css b/src/vs/base/browser/ui/toolbar/toolbar.css index d23a05bcb3..863eaf8d16 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.css +++ b/src/vs/base/browser/ui/toolbar/toolbar.css @@ -7,15 +7,3 @@ display: inline-block; padding: 0; } - -.vs .monaco-toolbar .action-label.toolbar-toggle-more { - background-image: url('ellipsis-light.svg'); -} - -.vs-dark .monaco-toolbar .action-label.toolbar-toggle-more { - background-image: url('ellipsis-dark.svg'); -} - -.hc-black .monaco-toolbar .action-label.toolbar-toggle-more { - background-image: url('ellipsis-hc.svg'); -} \ No newline at end of file diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 9ab6c3a3ad..c958f75f32 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -65,7 +65,7 @@ export class ToolBar extends Disposable { this.options.actionViewItemProvider, this.actionRunner, this.options.getKeyBinding, - 'toolbar-toggle-more', + 'codicon-more', this.options.anchorAlignmentProvider ); this.toggleMenuActionViewItem.value.setActionContext(this.actionBar.context); diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index a0705e5062..0cf2cd2bd9 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -5,16 +5,16 @@ import 'vs/css!./media/tree'; import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IListOptions, List, IListStyles, mightProducePrintableCharacter, MouseController } from 'vs/base/browser/ui/list/listWidget'; -import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list'; +import { IListOptions, List, IListStyles, MouseController, DefaultKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/listWidget'; +import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider, IKeyboardNavigationDelegate } from 'vs/base/browser/ui/list/list'; import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass, hasParentWithClass, createStyleSheet, clearNode } from 'vs/base/browser/dom'; import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event'; -import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent, ITreeDragAndDrop, TreeDragOverBubble, TreeVisibility, TreeFilterResult, ITreeModelSpliceEvent, TreeMouseEventTarget } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; import { IDragAndDropData, StaticDND, DragAndDropData } from 'vs/base/browser/dnd'; -import { range, equals, distinctES6 } from 'vs/base/common/arrays'; +import { range, equals, distinctES6, fromSet } from 'vs/base/common/arrays'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { domEvent } from 'vs/base/browser/event'; import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters'; @@ -567,7 +567,7 @@ class TypeFilterController implements IDisposable { private _empty: boolean = false; get empty(): boolean { return this._empty; } - private _onDidChangeEmptyState = new Emitter(); + private readonly _onDidChangeEmptyState = new Emitter(); readonly onDidChangeEmptyState: Event = Event.latch(this._onDidChangeEmptyState.event); private positionClassName = 'ne'; @@ -581,7 +581,7 @@ class TypeFilterController implements IDisposable { private automaticKeyboardNavigation = true; private triggered = false; - private _onDidChangePattern = new Emitter(); + private readonly _onDidChangePattern = new Emitter(); readonly onDidChangePattern = this._onDidChangePattern.event; private enabledDisposables: IDisposable[] = []; @@ -592,7 +592,7 @@ class TypeFilterController implements IDisposable { model: ITreeModel, private view: List>, private filter: TypeFilter, - private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider + private keyboardNavigationDelegate: IKeyboardNavigationDelegate ) { this.domNode = $(`.monaco-list-type-filter.${this.positionClassName}`); this.domNode.draggable = true; @@ -658,13 +658,12 @@ class TypeFilterController implements IDisposable { return; } - const isPrintableCharEvent = this.keyboardNavigationLabelProvider.mightProducePrintableCharacter ? (e: IKeyboardEvent) => this.keyboardNavigationLabelProvider.mightProducePrintableCharacter!(e) : (e: IKeyboardEvent) => mightProducePrintableCharacter(e); const onKeyDown = Event.chain(domEvent(this.view.getHTMLElement(), 'keydown')) .filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode) .map(e => new StandardKeyboardEvent(e)) .filter(this.keyboardNavigationEventFilter || (() => true)) .filter(() => this.automaticKeyboardNavigation || this.triggered) - .filter(e => isPrintableCharEvent(e) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey))) + .filter(e => this.keyboardNavigationDelegate.mightProducePrintableCharacter(e) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey))) .forEach(e => { e.stopPropagation(); e.preventDefault(); }) .event; @@ -935,7 +934,7 @@ class Trait { private nodes: ITreeNode[] = []; private elements: T[] | undefined; - private _onDidChange = new Emitter>(); + private readonly _onDidChange = new Emitter>(); readonly onDidChange = this._onDidChange.event; private _nodeSet: Set> | undefined; @@ -1189,6 +1188,7 @@ export abstract class AbstractTree implements IDisposable get onDidChangeFocus(): Event> { return this.eventBufferer.wrapEvent(this.focus.onDidChange); } get onDidChangeSelection(): Event> { return this.eventBufferer.wrapEvent(this.selection.onDidChange); } get onDidOpen(): Event> { return Event.map(this.view.onDidOpen, asTreeEvent); } + get onDidPin(): Event> { return Event.map(this.view.onDidPin, asTreeEvent); } get onMouseClick(): Event> { return Event.map(this.view.onMouseClick, asTreeMouseEvent); } get onMouseDblClick(): Event> { return Event.map(this.view.onMouseDblClick, asTreeMouseEvent); } @@ -1204,7 +1204,7 @@ export abstract class AbstractTree implements IDisposable get onDidChangeCollapseState(): Event> { return this.model.onDidChangeCollapseState; } get onDidChangeRenderNodeCount(): Event> { return this.model.onDidChangeRenderNodeCount; } - private _onWillRefilter = new Emitter(); + private readonly _onWillRefilter = new Emitter(); readonly onWillRefilter: Event = this._onWillRefilter.event; get filterOnType(): boolean { return !!this._options.filterOnType; } @@ -1213,7 +1213,7 @@ export abstract class AbstractTree implements IDisposable get openOnSingleClick(): boolean { return typeof this._options.openOnSingleClick === 'undefined' ? true : this._options.openOnSingleClick; } get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? false : this._options.expandOnlyOnTwistieClick; } - private _onDidUpdateOptions = new Emitter>(); + private readonly _onDidUpdateOptions = new Emitter>(); readonly onDidUpdateOptions: Event> = this._onDidUpdateOptions.event; get onDidDispose(): Event { return this.view.onDidDispose; } @@ -1228,7 +1228,7 @@ export abstract class AbstractTree implements IDisposable const treeDelegate = new ComposedTreeDelegate>(delegate); const onDidChangeCollapseStateRelay = new Relay>(); - const onDidChangeActiveNodes = new Relay[]>(); + const onDidChangeActiveNodes = new Emitter[]>(); const activeNodes = new EventCollection(onDidChangeActiveNodes.event); this.disposables.push(activeNodes); @@ -1251,11 +1251,23 @@ export abstract class AbstractTree implements IDisposable onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState; this.model.onDidSplice(e => { - this.focus.onDidModelSplice(e); - this.selection.onDidModelSplice(e); - }, null, this.disposables); + this.eventBufferer.bufferEvents(() => { + this.focus.onDidModelSplice(e); + this.selection.onDidModelSplice(e); + }); - onDidChangeActiveNodes.input = Event.map(Event.any(this.focus.onDidChange, this.selection.onDidChange, this.model.onDidSplice), () => [...this.focus.getNodes(), ...this.selection.getNodes()]); + const set = new Set>(); + + for (const node of this.focus.getNodes()) { + set.add(node); + } + + for (const node of this.selection.getNodes()) { + set.add(node); + } + + onDidChangeActiveNodes.fire(fromSet(set)); + }, null, this.disposables); if (_options.keyboardSupport !== false) { const onKeyDown = Event.chain(this.view.onKeyDown) @@ -1268,7 +1280,8 @@ export abstract class AbstractTree implements IDisposable } if (_options.keyboardNavigationLabelProvider) { - this.typeFilterController = new TypeFilterController(this, this.model, this.view, filter!, _options.keyboardNavigationLabelProvider); + const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate; + this.typeFilterController = new TypeFilterController(this, this.model, this.view, filter!, delegate); this.focusNavigationFilter = node => this.typeFilterController!.shouldAllowFocus(node); this.disposables.push(this.typeFilterController!); } diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 2b56371210..94a2a16c32 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ComposedTreeDelegate, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from 'vs/base/browser/ui/tree/abstractTree'; -import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; +import { ObjectTree, IObjectTreeOptions, CompressibleObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list'; import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop, TreeError, WeakMapper } from 'vs/base/browser/ui/tree/tree'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -1010,6 +1010,24 @@ export interface ITreeCompressionDelegate { isIncompressible(element: T): boolean; } +function asCompressibleObjectTreeOptions(options?: ICompressibleAsyncDataTreeOptions): ICompressibleObjectTreeOptions, TFilterData> | undefined { + const objectTreeOptions = options && asObjectTreeOptions(options); + + return objectTreeOptions && { + ...objectTreeOptions, + keyboardNavigationLabelProvider: objectTreeOptions.keyboardNavigationLabelProvider && { + ...objectTreeOptions.keyboardNavigationLabelProvider, + getCompressedNodeKeyboardNavigationLabel(els) { + return options!.keyboardNavigationLabelProvider!.getCompressedNodeKeyboardNavigationLabel(els.map(e => e.element as T)); + } + } + }; +} + +export interface ICompressibleAsyncDataTreeOptions extends IAsyncDataTreeOptions { + readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider; +} + export class CompressibleAsyncDataTree extends AsyncDataTree { protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node)); @@ -1031,11 +1049,11 @@ export class CompressibleAsyncDataTree extends As container: HTMLElement, delegate: IListVirtualDelegate, renderers: ICompressibleTreeRenderer[], - options: IAsyncDataTreeOptions + options: ICompressibleAsyncDataTreeOptions ): ObjectTree, TFilterData> { const objectTreeDelegate = new ComposedTreeDelegate>(delegate); const objectTreeRenderers = renderers.map(r => new CompressibleAsyncDataTreeRenderer(r, this.nodeMapper, () => this.compressibleNodeMapper, this._onDidChangeNodeSlowState.event)); - const objectTreeOptions = asObjectTreeOptions(options) || {}; + const objectTreeOptions = asCompressibleObjectTreeOptions(options) || {}; return new CompressibleObjectTree(user, container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions); } diff --git a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts index 6bd87aa84b..8640e8ce77 100644 --- a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts @@ -336,7 +336,7 @@ function mapOptions(compressedNodeUnwrapper: CompressedNodeUnwra ...options, sorter: options.sorter && { compare(node: ICompressedTreeNode, otherNode: ICompressedTreeNode): number { - return options.sorter!.compare(compressedNodeUnwrapper(node), compressedNodeUnwrapper(otherNode)); + return options.sorter!.compare(node.elements[0], otherNode.elements[0]); } }, identityProvider: options.identityProvider && { diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index 742607faea..5b4aa9b168 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -162,9 +162,10 @@ export class DataTree extends AbstractTree>(Iterator.fromArray(children), element => { const { elements: children, size } = this.iterate(element, isCollapsed); + const collapsible = this.dataSource.hasChildren ? this.dataSource.hasChildren(element) : undefined; const collapsed = size === 0 ? undefined : (isCollapsed && isCollapsed(element)); - return { element, children, collapsed }; + return { element, children, collapsible, collapsed }; }); return { elements, size: children.length }; diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index 6b1966761c..2c4407fa26 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -62,17 +62,17 @@ export class IndexTreeModel, TFilterData = voi private root: IIndexTreeNode; private eventBufferer = new EventBufferer(); - private _onDidChangeCollapseState = new Emitter>(); + private readonly _onDidChangeCollapseState = new Emitter>(); readonly onDidChangeCollapseState: Event> = this.eventBufferer.wrapEvent(this._onDidChangeCollapseState.event); - private _onDidChangeRenderNodeCount = new Emitter>(); + private readonly _onDidChangeRenderNodeCount = new Emitter>(); readonly onDidChangeRenderNodeCount: Event> = this.eventBufferer.wrapEvent(this._onDidChangeRenderNodeCount.event); private collapseByDefault: boolean; private filter?: ITreeFilter; private autoExpandSingleChildren: boolean; - private _onDidSplice = new Emitter>(); + private readonly _onDidSplice = new Emitter>(); readonly onDidSplice = this._onDidSplice.event; constructor( @@ -169,7 +169,7 @@ export class IndexTreeModel, TFilterData = voi parentNode.visibleChildrenCount += insertedVisibleChildrenCount - deletedVisibleChildrenCount; if (revealed && visible) { - const visibleDeleteCount = deletedNodes.reduce((r, node) => r + node.renderNodeCount, 0); + const visibleDeleteCount = deletedNodes.reduce((r, node) => r + (node.visible ? node.renderNodeCount : 0), 0); this._updateAncestorsRenderNodeCount(parentNode, renderNodeCount - visibleDeleteCount); this.list.splice(listIndex, visibleDeleteCount, treeListElementsToInsert); diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index d8c2195e81..d184822924 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -8,9 +8,10 @@ import { AbstractTree, IAbstractTreeOptions } from 'vs/base/browser/ui/tree/abst import { ISpliceable } from 'vs/base/common/sequence'; import { ITreeNode, ITreeModel, ITreeElement, ITreeRenderer, ITreeSorter, ICollapseStateChangeEvent } from 'vs/base/browser/ui/tree/tree'; import { ObjectTreeModel, IObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; -import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; import { Event } from 'vs/base/common/event'; import { CompressibleObjectTreeModel, ElementMapper, ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; +import { memoize } from 'vs/base/common/decorators'; export interface IObjectTreeOptions extends IAbstractTreeOptions { sorter?: ITreeSorter; @@ -77,9 +78,12 @@ class CompressibleRenderer implements ITreeRender readonly templateId: string; readonly onDidChangeTwistieState: Event | undefined; - compressedTreeNodeProvider: ICompressedTreeNodeProvider; + @memoize + private get compressedTreeNodeProvider(): ICompressedTreeNodeProvider { + return this._compressedTreeNodeProvider(); + } - constructor(private renderer: ICompressibleTreeRenderer) { + constructor(private _compressedTreeNodeProvider: () => ICompressedTreeNodeProvider, private renderer: ICompressibleTreeRenderer) { this.templateId = renderer.templateId; if (renderer.onDidChangeTwistieState) { @@ -127,7 +131,38 @@ class CompressibleRenderer implements ITreeRender } } -export class CompressibleObjectTree, TFilterData = void> extends ObjectTree { +export interface ICompressibleKeyboardNavigationLabelProvider extends IKeyboardNavigationLabelProvider { + getCompressedNodeKeyboardNavigationLabel(elements: T[]): { toString(): string | undefined; } | undefined; +} + +export interface ICompressibleObjectTreeOptions extends IObjectTreeOptions { + readonly keyboardNavigationLabelProvider?: ICompressibleKeyboardNavigationLabelProvider; +} + +function asObjectTreeOptions(compressedTreeNodeProvider: () => ICompressedTreeNodeProvider, options?: ICompressibleObjectTreeOptions): IObjectTreeOptions | undefined { + return options && { + ...options, + keyboardNavigationLabelProvider: options.keyboardNavigationLabelProvider && { + getKeyboardNavigationLabel(e: T) { + let compressedTreeNode: ITreeNode, TFilterData>; + + try { + compressedTreeNode = compressedTreeNodeProvider().getCompressedTreeNode(e); + } catch { + return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e); + } + + if (compressedTreeNode.element.elements.length === 1) { + return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e); + } else { + return options.keyboardNavigationLabelProvider!.getCompressedNodeKeyboardNavigationLabel(compressedTreeNode.element.elements); + } + } + } + }; +} + +export class CompressibleObjectTree, TFilterData = void> extends ObjectTree implements ICompressedTreeNodeProvider { protected model: CompressibleObjectTreeModel; @@ -136,11 +171,11 @@ export class CompressibleObjectTree, TFilterData = vo container: HTMLElement, delegate: IListVirtualDelegate, renderers: ICompressibleTreeRenderer[], - options: IObjectTreeOptions = {} + options: ICompressibleObjectTreeOptions = {} ) { - const compressibleRenderers = renderers.map(r => new CompressibleRenderer(r)); - super(user, container, delegate, compressibleRenderers, options); - compressibleRenderers.forEach(r => r.compressedTreeNodeProvider = this); + const compressedTreeNodeProvider = () => this; + const compressibleRenderers = renderers.map(r => new CompressibleRenderer(compressedTreeNodeProvider, r)); + super(user, container, delegate, compressibleRenderers, asObjectTreeOptions(compressedTreeNodeProvider, options)); } setChildren(element: T | null, children?: ISequence>): void { diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts index e21362349c..2906f9d7bd 100644 --- a/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -9,6 +9,7 @@ import { IndexTreeModel, IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/ import { Event } from 'vs/base/common/event'; import { ITreeModel, ITreeNode, ITreeElement, ITreeSorter, ICollapseStateChangeEvent, ITreeModelSpliceEvent, TreeError } from 'vs/base/browser/ui/tree/tree'; import { IIdentityProvider } from 'vs/base/browser/ui/list/list'; +import { mergeSort } from 'vs/base/common/arrays'; export type ITreeNodeCallback = (node: ITreeNode) => void; @@ -123,7 +124,7 @@ export class ObjectTreeModel, TFilterData extends Non let iterator = elements ? getSequenceIterator(elements) : Iterator.empty>(); if (this.sorter) { - iterator = Iterator.fromArray(Iterator.collect(iterator).sort(this.sorter.compare.bind(this.sorter))); + iterator = Iterator.fromArray(mergeSort(Iterator.collect(iterator), this.sorter.compare.bind(this.sorter))); } return Iterator.map(iterator, treeElement => { diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 832a4ab2b7..c4e257f084 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -164,6 +164,7 @@ export interface ITreeNavigator { } export interface IDataSource { + hasChildren?(element: TInput | T): boolean; getChildren(element: TInput | T): T[]; } diff --git a/src/vs/base/browser/ui/tree/treeDefaults.ts b/src/vs/base/browser/ui/tree/treeDefaults.ts index cf32280eda..e913dc579a 100644 --- a/src/vs/base/browser/ui/tree/treeDefaults.ts +++ b/src/vs/base/browser/ui/tree/treeDefaults.ts @@ -22,4 +22,4 @@ export class CollapseAllAction extends Action { return Promise.resolve(); } -} \ No newline at end of file +} diff --git a/src/vs/base/common/amd.ts b/src/vs/base/common/amd.ts index 0db1d7e074..4f04a8c6a0 100644 --- a/src/vs/base/common/amd.ts +++ b/src/vs/base/common/amd.ts @@ -11,6 +11,7 @@ export function getPathFromAmdModule(requirefn: typeof require, relativePath: st /** * Reference a resource that might be inlined. + * Do not inline icons that will be used by the native mac touchbar. * Do not rename this method unless you adopt the build scripts. */ export function registerAndGetAmdImageURL(absolutePath: string): string { diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 829da18773..af9e0bf08c 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -372,6 +372,12 @@ export function distinctES6(array: ReadonlyArray): T[] { }); } +export function fromSet(set: Set): T[] { + const result: T[] = []; + set.forEach(o => result.push(o)); + return result; +} + export function uniqueFilter(keyFn: (t: T) => string): (t: T) => boolean { const seen: { [key: string]: boolean; } = Object.create(null); @@ -418,6 +424,12 @@ export function first(array: ReadonlyArray, fn: (item: T) => boolean, notF return index < 0 ? notFoundValue : array[index]; } +export function firstOrDefault(array: ReadonlyArray, notFoundValue: NotFound): T | NotFound; +export function firstOrDefault(array: ReadonlyArray): T | undefined; +export function firstOrDefault(array: ReadonlyArray, notFoundValue?: NotFound): T | NotFound | undefined { + return array.length > 0 ? array[0] : notFoundValue; +} + export function commonPrefixLength(one: ReadonlyArray, other: ReadonlyArray, equals: (a: T, b: T) => boolean = (a, b) => a === b): number { let result = 0; diff --git a/src/vs/base/common/collections.ts b/src/vs/base/common/collections.ts index 5a29b8b300..9393302f3b 100644 --- a/src/vs/base/common/collections.ts +++ b/src/vs/base/common/collections.ts @@ -97,6 +97,12 @@ export function fromMap(original: Map): IStringDictionary { return result; } +export function mapValues(map: Map): V[] { + const result: V[] = []; + map.forEach(v => result.push(v)); + return result; +} + export class SetMap { private map = new Map>(); diff --git a/src/vs/base/common/decorators.ts b/src/vs/base/common/decorators.ts index 8a9cc8d70c..62ffd87486 100644 --- a/src/vs/base/common/decorators.ts +++ b/src/vs/base/common/decorators.ts @@ -24,40 +24,64 @@ export function createDecorator(mapFn: (fn: Function, key: string) => Function): }; } -export function memoize(target: any, key: string, descriptor: any) { - let fnKey: string | null = null; - let fn: Function | null = null; +let memoizeId = 0; +export function createMemoizer() { + const memoizeKeyPrefix = `$memoize${memoizeId++}`; + let self: any = undefined; - if (typeof descriptor.value === 'function') { - fnKey = 'value'; - fn = descriptor.value; + const result = function memoize(target: any, key: string, descriptor: any) { + let fnKey: string | null = null; + let fn: Function | null = null; - if (fn!.length !== 0) { - console.warn('Memoize should only be used in functions with zero parameters'); - } - } else if (typeof descriptor.get === 'function') { - fnKey = 'get'; - fn = descriptor.get; - } + if (typeof descriptor.value === 'function') { + fnKey = 'value'; + fn = descriptor.value; - if (!fn) { - throw new Error('not supported'); - } - - const memoizeKey = `$memoize$${key}`; - - descriptor[fnKey!] = function (...args: any[]) { - if (!this.hasOwnProperty(memoizeKey)) { - Object.defineProperty(this, memoizeKey, { - configurable: false, - enumerable: false, - writable: false, - value: fn!.apply(this, args) - }); + if (fn!.length !== 0) { + console.warn('Memoize should only be used in functions with zero parameters'); + } + } else if (typeof descriptor.get === 'function') { + fnKey = 'get'; + fn = descriptor.get; } - return this[memoizeKey]; + if (!fn) { + throw new Error('not supported'); + } + + const memoizeKey = `${memoizeKeyPrefix}:${key}`; + descriptor[fnKey!] = function (...args: any[]) { + self = this; + + if (!this.hasOwnProperty(memoizeKey)) { + Object.defineProperty(this, memoizeKey, { + configurable: true, + enumerable: false, + writable: true, + value: fn!.apply(this, args) + }); + } + + return this[memoizeKey]; + }; }; + + result.clear = () => { + if (typeof self === 'undefined') { + return; + } + Object.getOwnPropertyNames(self).forEach(property => { + if (property.indexOf(memoizeKeyPrefix) === 0) { + delete self[property]; + } + }); + }; + + return result; +} + +export function memoize(target: any, key: string, descriptor: any) { + return createMemoizer()(target, key, descriptor); } export interface IDebouceReducer { @@ -87,4 +111,4 @@ export function debounce(delay: number, reducer?: IDebouceReducer, initial }, delay); }; }); -} \ No newline at end of file +} diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 4d81a04a24..13c0178cde 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -451,7 +451,7 @@ class LeakageMonitor { * Sample: class Document { - private _onDidChange = new Emitter<(value:string)=>any>(); + private readonly _onDidChange = new Emitter<(value:string)=>any>(); public onDidChange = this._onDidChange.event; @@ -808,7 +808,7 @@ export class Relay implements IDisposable { private inputEvent: Event = Event.None; private inputEventListener: IDisposable = Disposable.None; - private emitter = new Emitter({ + private readonly emitter = new Emitter({ onFirstListenerDidAdd: () => { this.listening = true; this.inputEventListener = this.inputEvent(this.emitter.fire, this.emitter); diff --git a/src/vs/base/common/history.ts b/src/vs/base/common/history.ts index 8172fd8cd1..fbf2fd8987 100644 --- a/src/vs/base/common/history.ts +++ b/src/vs/base/common/history.ts @@ -11,7 +11,7 @@ export class HistoryNavigator implements INavigator { private _limit: number; private _navigator!: ArrayNavigator; - constructor(history: T[] = [], limit: number = 10) { + constructor(history: readonly T[] = [], limit: number = 10) { this._initialize(history); this._limit = limit; this._onChange(); @@ -62,7 +62,8 @@ export class HistoryNavigator implements INavigator { private _onChange() { this._reduceToLimit(); - this._navigator = new ArrayNavigator(this._elements, 0, this._elements.length, this._elements.length); + const elements = this._elements; + this._navigator = new ArrayNavigator(elements, 0, elements.length, elements.length); } private _reduceToLimit() { @@ -72,7 +73,7 @@ export class HistoryNavigator implements INavigator { } } - private _initialize(history: T[]): void { + private _initialize(history: readonly T[]): void { this._history = new Set(); for (const entry of history) { this._history.add(entry); diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index 09cfdaa47e..2e3687f583 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -18,6 +18,22 @@ export interface Iterator { next(): IteratorResult; } +interface NativeIteratorYieldResult { + done?: false; + value: TYield; +} + +interface NativeIteratorReturnResult { + done: true; + value: TReturn; +} + +type NativeIteratorResult = NativeIteratorYieldResult | NativeIteratorReturnResult; + +export interface NativeIterator { + next(): NativeIteratorResult; +} + export module Iterator { const _empty: Iterator = { next() { @@ -56,6 +72,20 @@ export module Iterator { }; } + export function fromNativeIterator(it: NativeIterator): Iterator { + return { + next(): IteratorResult { + const result = it.next(); + + if (result.done) { + return FIN; + } + + return { done: false, value: result.value }; + } + }; + } + export function from(elements: Iterator | T[] | undefined): Iterator { if (!elements) { return Iterator.empty(); @@ -160,12 +190,12 @@ export interface INextIterator { export class ArrayIterator implements INextIterator { - private items: T[]; + private readonly items: readonly T[]; protected start: number; protected end: number; protected index: number; - constructor(items: T[], start: number = 0, end: number = items.length, index = start - 1) { + constructor(items: readonly T[], start: number = 0, end: number = items.length, index = start - 1) { this.items = items; this.start = start; this.end = end; @@ -193,7 +223,7 @@ export class ArrayIterator implements INextIterator { export class ArrayNavigator extends ArrayIterator implements INavigator { - constructor(items: T[], start: number = 0, end: number = items.length, index = start - 1) { + constructor(items: readonly T[], start: number = 0, end: number = items.length, index = start - 1) { super(items, start, end, index); } diff --git a/src/vs/base/common/jsonSchema.ts b/src/vs/base/common/jsonSchema.ts index 2796d1cb7b..005215ce15 100644 --- a/src/vs/base/common/jsonSchema.ts +++ b/src/vs/base/common/jsonSchema.ts @@ -61,7 +61,7 @@ export interface IJSONSchema { markdownDescription?: string; // VSCode extension doNotSuggest?: boolean; // VSCode extension allowComments?: boolean; // VSCode extension - allowsTrailingCommas?: boolean; // VSCode extension + allowTrailingCommas?: boolean; // VSCode extension } export interface IJSONSchemaMap { diff --git a/src/vs/base/common/resourceTree.ts b/src/vs/base/common/resourceTree.ts new file mode 100644 index 0000000000..cb297324c1 --- /dev/null +++ b/src/vs/base/common/resourceTree.ts @@ -0,0 +1,191 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { memoize } from 'vs/base/common/decorators'; +import * as paths from 'vs/base/common/path'; +import { Iterator } from 'vs/base/common/iterator'; +import { relativePath, joinPath } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { mapValues } from 'vs/base/common/collections'; + +export interface ILeafNode { + readonly uri: URI; + readonly relativePath: string; + readonly name: string; + readonly element: T; + readonly context: C; +} + +export interface IBranchNode { + readonly uri: URI; + readonly relativePath: string; + readonly name: string; + readonly size: number; + readonly children: Iterator>; + readonly parent: IBranchNode | undefined; + readonly context: C; + get(childName: string): INode | undefined; +} + +export type INode = IBranchNode | ILeafNode; + +// Internals + +class Node { + + @memoize + get name(): string { return paths.posix.basename(this.relativePath); } + + constructor(readonly uri: URI, readonly relativePath: string, readonly context: C) { } +} + +class BranchNode extends Node implements IBranchNode { + + private _children = new Map | LeafNode>(); + + get size(): number { + return this._children.size; + } + + get children(): Iterator | LeafNode> { + return Iterator.fromArray(mapValues(this._children)); + } + + constructor(uri: URI, relativePath: string, context: C, readonly parent: IBranchNode | undefined = undefined) { + super(uri, relativePath, context); + } + + get(path: string): BranchNode | LeafNode | undefined { + return this._children.get(path); + } + + set(path: string, child: BranchNode | LeafNode): void { + this._children.set(path, child); + } + + delete(path: string): void { + this._children.delete(path); + } +} + +class LeafNode extends Node implements ILeafNode { + + constructor(uri: URI, path: string, context: C, readonly element: T) { + super(uri, path, context); + } +} + +function collect(node: INode, result: T[]): T[] { + if (ResourceTree.isBranchNode(node)) { + Iterator.forEach(node.children, child => collect(child, result)); + } else { + result.push(node.element); + } + + return result; +} + +export class ResourceTree, C> { + + readonly root: BranchNode; + + static isBranchNode(obj: any): obj is IBranchNode { + return obj instanceof BranchNode; + } + + static getRoot(node: IBranchNode): IBranchNode { + while (node.parent) { + node = node.parent; + } + + return node; + } + + static collect(node: INode): T[] { + return collect(node, []); + } + + constructor(context: C, rootURI: URI = URI.file('/')) { + this.root = new BranchNode(rootURI, '', context); + } + + add(uri: URI, element: T): void { + const key = relativePath(this.root.uri, uri) || uri.fsPath; + const parts = key.split(/[\\\/]/).filter(p => !!p); + let node = this.root; + let path = ''; + + for (let i = 0; i < parts.length; i++) { + const name = parts[i]; + path = path + '/' + name; + + let child = node.get(name); + + if (!child) { + if (i < parts.length - 1) { + child = new BranchNode(joinPath(this.root.uri, path), path, this.root.context, node); + node.set(name, child); + } else { + child = new LeafNode(uri, path, this.root.context, element); + node.set(name, child); + return; + } + } + + if (!(child instanceof BranchNode)) { + if (i < parts.length - 1) { + throw new Error('Inconsistent tree: can\'t override leaf with branch.'); + } + + // replace + node.set(name, new LeafNode(uri, path, this.root.context, element)); + return; + } else if (i === parts.length - 1) { + throw new Error('Inconsistent tree: can\'t override branch with leaf.'); + } + + node = child; + } + } + + delete(uri: URI): T | undefined { + const key = relativePath(this.root.uri, uri) || uri.fsPath; + const parts = key.split(/[\\\/]/).filter(p => !!p); + return this._delete(this.root, parts, 0); + } + + private _delete(node: BranchNode, parts: string[], index: number): T | undefined { + const name = parts[index]; + const child = node.get(name); + + if (!child) { + return undefined; + } + + // not at end + if (index < parts.length - 1) { + if (child instanceof BranchNode) { + const result = this._delete(child, parts, index + 1); + + if (typeof result !== 'undefined' && child.size === 0) { + node.delete(name); + } + + return result; + } else { + throw new Error('Inconsistent tree: Expected a branch, found a leaf instead.'); + } + } + + //at end + if (child instanceof BranchNode) { + // TODO: maybe we can allow this + throw new Error('Inconsistent tree: Expected a leaf, found a branch instead.'); + } + + node.delete(name); + return child.element; + } +} diff --git a/src/vs/base/common/sequence.ts b/src/vs/base/common/sequence.ts index 659b352c9e..d0343fa2fd 100644 --- a/src/vs/base/common/sequence.ts +++ b/src/vs/base/common/sequence.ts @@ -24,11 +24,11 @@ export class Sequence implements ISequence, ISpliceable { readonly elements: T[] = []; - private _onDidSplice = new Emitter>(); + private readonly _onDidSplice = new Emitter>(); readonly onDidSplice: Event> = this._onDidSplice.event; splice(start: number, deleteCount: number, toInsert: T[] = []): void { this.elements.splice(start, deleteCount, ...toInsert); this._onDidSplice.fire({ start, deleteCount, toInsert }); } -} \ No newline at end of file +} diff --git a/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts b/src/vs/base/parts/contextmenu/electron-main/contextmenu.ts index 388185df18..6d7fc4c11c 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, ipcMain } from 'electron'; +import { Menu, MenuItem, BrowserWindow, ipcMain, IpcMainEvent } 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: Electron.IpcMainEvent, contextMenuId: number, items: ISerializableContextMenuItem[], onClickChannel: string, options?: IPopupOptions) => { + ipcMain.on(CONTEXT_MENU_CHANNEL, (event: 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: Electron.IpcMainEvent, onClickChannel: string, items: ISerializableContextMenuItem[]): Menu { +function createMenu(event: IpcMainEvent, onClickChannel: string, items: ISerializableContextMenuItem[]): Menu { const menu = new Menu(); items.forEach(item => { @@ -65,4 +65,4 @@ function createMenu(event: Electron.IpcMainEvent, onClickChannel: string, items: }); return menu; -} \ No newline at end of file +} diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index 4b0be27f6d..552815306b 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -150,6 +150,10 @@ export const enum ProtocolConstants { * If there is no reconnection within this time-frame, consider the connection permanently closed... */ ReconnectionGraceTime = 3 * 60 * 60 * 1000, // 3hrs + /** + * Maximal grace time between the first and the last reconnection... + */ + ReconnectionShortGraceTime = 5 * 60 * 1000, // 5min } class ProtocolMessage { @@ -347,10 +351,10 @@ export class Protocol extends Disposable implements IMessagePassingProtocol { private _socketWriter: ProtocolWriter; private _socketReader: ProtocolReader; - private _onMessage = new Emitter(); + private readonly _onMessage = new Emitter(); readonly onMessage: Event = this._onMessage.event; - private _onClose = new Emitter(); + private readonly _onClose = new Emitter(); readonly onClose: Event = this._onClose.event; constructor(socket: ISocket) { diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 79e367f347..43d6e38c7c 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -442,7 +442,7 @@ export class ChannelClient implements IChannelClient, IDisposable { private lastRequestId: number = 0; private protocolListener: IDisposable | null; - private _onDidInitialize = new Emitter(); + private readonly _onDidInitialize = new Emitter(); readonly onDidInitialize = this._onDidInitialize.event; constructor(private protocol: IMessagePassingProtocol) { @@ -660,7 +660,7 @@ export class IPCServer implements IChannelServer, I private channels = new Map>(); private _connections = new Set>(); - private _onDidChangeConnections = new Emitter>(); + private readonly _onDidChangeConnections = new Emitter>(); readonly onDidChangeConnections: Event> = this._onDidChangeConnections.event; get connections(): Connection[] { diff --git a/src/vs/base/parts/ipc/electron-main/ipc.electron-main.ts b/src/vs/base/parts/ipc/electron-main/ipc.electron-main.ts index 53c1850313..7f669be329 100644 --- a/src/vs/base/parts/ipc/electron-main/ipc.electron-main.ts +++ b/src/vs/base/parts/ipc/electron-main/ipc.electron-main.ts @@ -6,12 +6,12 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IPCServer, ClientConnectionEvent } from 'vs/base/parts/ipc/common/ipc'; import { Protocol } from 'vs/base/parts/ipc/node/ipc.electron'; -import { ipcMain } from 'electron'; +import { ipcMain, WebContents } from 'electron'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { VSBuffer } from 'vs/base/common/buffer'; interface IIPCEvent { - event: { sender: Electron.WebContents; }; + event: { sender: WebContents; }; message: Buffer | null; } @@ -27,7 +27,7 @@ export class Server extends IPCServer { private static Clients = new Map(); private static getOnDidClientConnect(): Event { - const onHello = Event.fromNodeEventEmitter(ipcMain, 'ipc:hello', ({ sender }) => sender); + const onHello = Event.fromNodeEventEmitter(ipcMain, 'ipc:hello', ({ sender }) => sender); return Event.map(onHello, webContents => { const id = webContents.id; diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index 6edc1fc60d..1a031debb6 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -92,7 +92,7 @@ export class Client implements IChannelClient, IDisposable { private _client: IPCClient | null; private channels = new Map(); - private _onDidProcessExit = new Emitter<{ code: number, signal: string }>(); + private readonly _onDidProcessExit = new Emitter<{ code: number, signal: string }>(); readonly onDidProcessExit = this._onDidProcessExit.event; constructor(private modulePath: string, private options: IIPCOptions) { diff --git a/src/vs/base/parts/ipc/test/node/ipc.test.ts b/src/vs/base/parts/ipc/test/node/ipc.test.ts index 375fb217e6..415c59f6bd 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.test.ts @@ -16,7 +16,7 @@ class QueueProtocol implements IMessagePassingProtocol { private buffering = true; private buffers: VSBuffer[] = []; - private _onMessage = new Emitter({ + private readonly _onMessage = new Emitter({ onFirstListenerDidAdd: () => { for (const buffer of this.buffers) { this._onMessage.fire(buffer); @@ -57,7 +57,7 @@ function createProtocolPair(): [IMessagePassingProtocol, IMessagePassingProtocol class TestIPCClient extends IPCClient { - private _onDidDisconnect = new Emitter(); + private readonly _onDidDisconnect = new Emitter(); readonly onDidDisconnect = this._onDidDisconnect.event; constructor(protocol: IMessagePassingProtocol, id: string) { @@ -107,7 +107,7 @@ interface ITestService { class TestService implements ITestService { - private _pong = new Emitter(); + private readonly _pong = new Emitter(); readonly pong = this._pong.event; marco(): Promise { diff --git a/src/vs/base/parts/ipc/test/node/testService.ts b/src/vs/base/parts/ipc/test/node/testService.ts index c6963e93be..f95fe4d855 100644 --- a/src/vs/base/parts/ipc/test/node/testService.ts +++ b/src/vs/base/parts/ipc/test/node/testService.ts @@ -20,7 +20,7 @@ export interface ITestService { export class TestService implements ITestService { - private _onMarco = new Emitter(); + private readonly _onMarco = new Emitter(); onMarco: Event = this._onMarco.event; marco(): Promise { @@ -76,4 +76,4 @@ export class TestServiceClient implements ITestService { cancelMe(): Promise { return this.channel.call('cancelMe'); } -} \ No newline at end of file +} diff --git a/src/vs/base/parts/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.test.ts index ebf287f0b5..a34a2fd3d0 100644 --- a/src/vs/base/parts/storage/test/node/storage.test.ts +++ b/src/vs/base/parts/storage/test/node/storage.test.ts @@ -101,7 +101,7 @@ suite('Storage Library', () => { await mkdirp(storageDir); class TestSQLiteStorageDatabase extends SQLiteStorageDatabase { - private _onDidChangeItemsExternal = new Emitter(); + private readonly _onDidChangeItemsExternal = new Emitter(); get onDidChangeItemsExternal(): Event { return this._onDidChangeItemsExternal.event; } fireDidChangeItemsExternal(event: IStorageItemsChangeEvent): void { diff --git a/src/vs/base/parts/tree/browser/treeImpl.ts b/src/vs/base/parts/tree/browser/treeImpl.ts index 4ea61da4b2..e0fbe7e749 100644 --- a/src/vs/base/parts/tree/browser/treeImpl.ts +++ b/src/vs/base/parts/tree/browser/treeImpl.ts @@ -77,7 +77,7 @@ export class Tree implements _.ITree { readonly onDidExpandItem: Event = this._onDidExpandItem.event; private _onDidCollapseItem = new Relay(); readonly onDidCollapseItem: Event = this._onDidCollapseItem.event; - private _onDispose = new Emitter(); + private readonly _onDispose = new Emitter(); readonly onDidDispose: Event = this._onDispose.event; constructor(container: HTMLElement, configuration: _.ITreeConfiguration, options: _.ITreeOptions = {}) { diff --git a/src/vs/base/parts/tree/browser/treeModel.ts b/src/vs/base/parts/tree/browser/treeModel.ts index 33c6d39e88..d29542bf51 100644 --- a/src/vs/base/parts/tree/browser/treeModel.ts +++ b/src/vs/base/parts/tree/browser/treeModel.ts @@ -259,29 +259,29 @@ export class Item { private traits: { [trait: string]: boolean; }; - private _onDidCreate = new Emitter(); + private readonly _onDidCreate = new Emitter(); readonly onDidCreate: Event = this._onDidCreate.event; - private _onDidReveal = new Emitter(); + private readonly _onDidReveal = new Emitter(); readonly onDidReveal: Event = this._onDidReveal.event; - private _onExpand = new Emitter(); + private readonly _onExpand = new Emitter(); readonly onExpand: Event = this._onExpand.event; - private _onDidExpand = new Emitter(); + private readonly _onDidExpand = new Emitter(); readonly onDidExpand: Event = this._onDidExpand.event; - private _onCollapse = new Emitter(); + private readonly _onCollapse = new Emitter(); readonly onCollapse: Event = this._onCollapse.event; - private _onDidCollapse = new Emitter(); + private readonly _onDidCollapse = new Emitter(); readonly onDidCollapse: Event = this._onDidCollapse.event; - private _onDidAddTrait = new Emitter(); + private readonly _onDidAddTrait = new Emitter(); readonly onDidAddTrait: Event = this._onDidAddTrait.event; - private _onDidRemoveTrait = new Emitter(); + private readonly _onDidRemoveTrait = new Emitter(); readonly onDidRemoveTrait: Event = this._onDidRemoveTrait.event; - private _onDidRefresh = new Emitter(); + private readonly _onDidRefresh = new Emitter(); readonly onDidRefresh: Event = this._onDidRefresh.event; - private _onRefreshChildren = new Emitter(); + private readonly _onRefreshChildren = new Emitter(); readonly onRefreshChildren: Event = this._onRefreshChildren.event; - private _onDidRefreshChildren = new Emitter(); + private readonly _onDidRefreshChildren = new Emitter(); readonly onDidRefreshChildren: Event = this._onDidRefreshChildren.event; - private _onDidDispose = new Emitter(); + private readonly _onDidDispose = new Emitter(); readonly onDidDispose: Event = this._onDidDispose.event; private _isDisposed: boolean; @@ -868,19 +868,19 @@ export class TreeModel { private registryDisposable: IDisposable = Disposable.None; private traitsToItems: ITraitMap; - private _onSetInput = new Emitter(); + private readonly _onSetInput = new Emitter(); readonly onSetInput: Event = this._onSetInput.event; - private _onDidSetInput = new Emitter(); + private readonly _onDidSetInput = new Emitter(); readonly onDidSetInput: Event = this._onDidSetInput.event; - private _onRefresh = new Emitter(); + private readonly _onRefresh = new Emitter(); readonly onRefresh: Event = this._onRefresh.event; - private _onDidRefresh = new Emitter(); + private readonly _onDidRefresh = new Emitter(); readonly onDidRefresh: Event = this._onDidRefresh.event; - private _onDidHighlight = new Emitter<_.IHighlightEvent>(); + private readonly _onDidHighlight = new Emitter<_.IHighlightEvent>(); readonly onDidHighlight: Event<_.IHighlightEvent> = this._onDidHighlight.event; - private _onDidSelect = new Emitter<_.ISelectionEvent>(); + private readonly _onDidSelect = new Emitter<_.ISelectionEvent>(); readonly onDidSelect: Event<_.ISelectionEvent> = this._onDidSelect.event; - private _onDidFocus = new Emitter<_.IFocusEvent>(); + private readonly _onDidFocus = new Emitter<_.IFocusEvent>(); readonly onDidFocus: Event<_.IFocusEvent> = this._onDidFocus.event; private _onDidRevealItem = new Relay(); diff --git a/src/vs/base/parts/tree/test/browser/treeModel.test.ts b/src/vs/base/parts/tree/test/browser/treeModel.test.ts index 49de21e6a6..0d1a9e69ed 100644 --- a/src/vs/base/parts/tree/test/browser/treeModel.test.ts +++ b/src/vs/base/parts/tree/test/browser/treeModel.test.ts @@ -1081,10 +1081,10 @@ class DynamicModel implements _.IDataSource { private data: any; public promiseFactory: { (): Promise; } | null; - private _onGetChildren = new Emitter(); + private readonly _onGetChildren = new Emitter(); readonly onGetChildren: Event = this._onGetChildren.event; - private _onDidGetChildren = new Emitter(); + private readonly _onDidGetChildren = new Emitter(); readonly onDidGetChildren: Event = this._onDidGetChildren.event; constructor() { diff --git a/src/vs/base/test/browser/ui/grid/util.ts b/src/vs/base/test/browser/ui/grid/util.ts index 7e167c8eeb..a0f50cc8f8 100644 --- a/src/vs/base/test/browser/ui/grid/util.ts +++ b/src/vs/base/test/browser/ui/grid/util.ts @@ -10,7 +10,7 @@ import { IView } from 'vs/base/browser/ui/grid/grid'; export class TestView implements IView { - private _onDidChange = new Emitter<{ width: number; height: number; } | undefined>(); + private readonly _onDidChange = new Emitter<{ width: number; height: number; } | undefined>(); readonly onDidChange = this._onDidChange.event; get minimumWidth(): number { return this._minimumWidth; } @@ -28,7 +28,7 @@ export class TestView implements IView { private _element: HTMLElement = document.createElement('div'); get element(): HTMLElement { this._onDidGetElement.fire(); return this._element; } - private _onDidGetElement = new Emitter(); + private readonly _onDidGetElement = new Emitter(); readonly onDidGetElement = this._onDidGetElement.event; private _width = 0; @@ -39,10 +39,10 @@ export class TestView implements IView { get size(): [number, number] { return [this.width, this.height]; } - private _onDidLayout = new Emitter<{ width: number; height: number; }>(); + private readonly _onDidLayout = new Emitter<{ width: number; height: number; }>(); readonly onDidLayout: Event<{ width: number; height: number; }> = this._onDidLayout.event; - private _onDidFocus = new Emitter(); + private readonly _onDidFocus = new Emitter(); readonly onDidFocus: Event = this._onDidFocus.event; constructor( diff --git a/src/vs/base/test/browser/ui/splitview/splitview.test.ts b/src/vs/base/test/browser/ui/splitview/splitview.test.ts index f657a0bb58..bb68a9d97b 100644 --- a/src/vs/base/test/browser/ui/splitview/splitview.test.ts +++ b/src/vs/base/test/browser/ui/splitview/splitview.test.ts @@ -10,7 +10,7 @@ import { Sash, SashState } from 'vs/base/browser/ui/sash/sash'; class TestView implements IView { - private _onDidChange = new Emitter(); + private readonly _onDidChange = new Emitter(); readonly onDidChange = this._onDidChange.event; get minimumSize(): number { return this._minimumSize; } @@ -22,17 +22,17 @@ class TestView implements IView { private _element: HTMLElement = document.createElement('div'); get element(): HTMLElement { this._onDidGetElement.fire(); return this._element; } - private _onDidGetElement = new Emitter(); + private readonly _onDidGetElement = new Emitter(); readonly onDidGetElement = this._onDidGetElement.event; private _size = 0; get size(): number { return this._size; } private _orthogonalSize: number | undefined = 0; get orthogonalSize(): number | undefined { return this._orthogonalSize; } - private _onDidLayout = new Emitter<{ size: number; orthogonalSize: number | undefined }>(); + private readonly _onDidLayout = new Emitter<{ size: number; orthogonalSize: number | undefined }>(); readonly onDidLayout = this._onDidLayout.event; - private _onDidFocus = new Emitter(); + private readonly _onDidFocus = new Emitter(); readonly onDidFocus = this._onDidFocus.event; constructor( diff --git a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts index 347c780ae4..009f9ca9d6 100644 --- a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts @@ -690,4 +690,38 @@ suite('IndexTreeModel', function () { assert.deepEqual(model.getNodeLocation(list[3]), [0, 5]); }); }); + + test('refilter with filtered out nodes', function () { + const list: ITreeNode[] = []; + let query = new RegExp(''); + const filter = new class implements ITreeFilter { + filter(element: string): boolean { + return query.test(element); + } + }; + + const model = new IndexTreeModel('test', toSpliceable(list), 'root', { filter }); + + model.splice([0], 0, Iterator.fromArray([ + { element: 'silver' }, + { element: 'gold' }, + { element: 'platinum' } + ])); + + assert.deepEqual(toArray(list), ['silver', 'gold', 'platinum']); + + query = /platinum/; + model.refilter(); + assert.deepEqual(toArray(list), ['platinum']); + + model.splice([0], Number.POSITIVE_INFINITY, Iterator.fromArray([ + { element: 'silver' }, + { element: 'gold' }, + { element: 'platinum' } + ])); + assert.deepEqual(toArray(list), ['platinum']); + + model.refilter(); + assert.deepEqual(toArray(list), ['platinum']); + }); }); diff --git a/src/vs/base/test/common/decorators.test.ts b/src/vs/base/test/common/decorators.test.ts index 1f93c2f7dd..0c73d72b58 100644 --- a/src/vs/base/test/common/decorators.test.ts +++ b/src/vs/base/test/common/decorators.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { memoize } from 'vs/base/common/decorators'; +import { memoize, createMemoizer } from 'vs/base/common/decorators'; suite('Decorators', () => { test('memoize should memoize methods', () => { @@ -125,4 +125,24 @@ suite('Decorators', () => { assert.equal(foo.answer, 42); } }); + + test('memoize clear', () => { + const memoizer = createMemoizer(); + let counter = 0; + class Foo { + @memoizer + get answer() { return ++counter; } + } + + const foo = new Foo(); + assert.equal(foo.answer, 1); + assert.equal(foo.answer, 1); + memoizer.clear(); + assert.equal(foo.answer, 2); + assert.equal(foo.answer, 2); + memoizer.clear(); + assert.equal(foo.answer, 3); + assert.equal(foo.answer, 3); + assert.equal(foo.answer, 3); + }); }); diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index 91e0ca7c60..7ae0a979d1 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -25,7 +25,7 @@ namespace Samples { export class Document3 { - private _onDidChange = new Emitter(); + private readonly _onDidChange = new Emitter(); onDidChange: Event = this._onDidChange.event; diff --git a/src/vs/base/test/common/resourceTree.test.ts b/src/vs/base/test/common/resourceTree.test.ts new file mode 100644 index 0000000000..50293e4217 --- /dev/null +++ b/src/vs/base/test/common/resourceTree.test.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 * as assert from 'assert'; +import { ResourceTree, IBranchNode, ILeafNode } from 'vs/base/common/resourceTree'; +import { URI } from 'vs/base/common/uri'; + +suite('ResourceTree', function () { + test('ctor', function () { + const tree = new ResourceTree(null); + assert(ResourceTree.isBranchNode(tree.root)); + assert.equal(tree.root.size, 0); + }); + + test('simple', function () { + const tree = new ResourceTree(null); + + tree.add(URI.file('/foo/bar.txt'), 'bar contents'); + assert(ResourceTree.isBranchNode(tree.root)); + assert.equal(tree.root.size, 1); + + let foo = tree.root.get('foo') as IBranchNode; + assert(foo); + assert(ResourceTree.isBranchNode(foo)); + assert.equal(foo.size, 1); + + let bar = foo.get('bar.txt') as ILeafNode; + assert(bar); + assert(!ResourceTree.isBranchNode(bar)); + assert.equal(bar.element, 'bar contents'); + + tree.add(URI.file('/hello.txt'), 'hello contents'); + assert.equal(tree.root.size, 2); + + let hello = tree.root.get('hello.txt') as ILeafNode; + assert(hello); + assert(!ResourceTree.isBranchNode(hello)); + assert.equal(hello.element, 'hello contents'); + + tree.delete(URI.file('/foo/bar.txt')); + assert.equal(tree.root.size, 1); + hello = tree.root.get('hello.txt') as ILeafNode; + assert(hello); + assert(!ResourceTree.isBranchNode(hello)); + assert.equal(hello.element, 'hello contents'); + }); +}); diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 3593c528ae..c5780cd125 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -209,4 +209,10 @@ if (options.workspaceUri) { options.workspaceUri = URI.revive(options.workspaceUri); } +if (Array.isArray(options.staticExtensions)) { + options.staticExtensions.forEach(extension => { + extension.extensionLocation = URI.revive(extension.extensionLocation); + }); +} + 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 4637310668..9101761577 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -303,7 +303,7 @@ export class IssueReporter extends Disposable { const loggerClient = new LoggerChannelClient(mainProcessService.getChannel('logger')); this.logService = new FollowerLogService(loggerClient, logService); - const sharedProcess = (serviceCollection.get(IWindowsService)).whenSharedProcessReady() + const sharedProcess = mainProcessService.getChannel('sharedProcess').call('whenSharedProcessReady') .then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${configuration.windowId}`)); const instantiationService = new InstantiationService(serviceCollection, true); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index bc32d6a98c..e7d3dd1421 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -51,7 +51,7 @@ 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/productService'; -import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration } 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'; @@ -122,11 +122,6 @@ 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); @@ -173,8 +168,15 @@ 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)); + + // User Data Sync Contributions + 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)); services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); + registerConfiguration(); const instantiationService2 = instantiationService.createChild(services); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 514d837a06..0d301eab04 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -3,12 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor } from 'electron'; +import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor, IpcMainEvent } from 'electron'; import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform'; 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 { LegacyWindowsMainService } from 'vs/platform/windows/electron-main/legacyWindowsMainService'; 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'; @@ -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 { LaunchMainService, LaunchChannel, ILaunchMainService } from 'vs/platform/launch/electron-main/launchService'; +import { LaunchMainService, LaunchChannel, ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; 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'; @@ -53,7 +53,6 @@ import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlLi import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver'; import { IMenubarService } from 'vs/platform/menubar/node/menubar'; 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'; import { homedir } from 'os'; @@ -78,6 +77,7 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro 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'; +import { ISharedProcessMainService, SharedProcessMainService } from 'vs/platform/ipc/electron-main/sharedProcessMainService'; export class CodeApplication extends Disposable { @@ -125,7 +125,7 @@ export class CodeApplication extends Disposable { // Mac only event: open new window when we get activated if (!hasVisibleWindows && this.windowsMainService) { - this.windowsMainService.openNewWindow(OpenContext.DOCK); + this.windowsMainService.openEmptyWindow(OpenContext.DOCK); } }); @@ -165,8 +165,8 @@ export class CodeApplication extends Disposable { event.preventDefault(); }); - app.on('web-contents-created', (_event: Electron.Event, contents) => { - contents.on('will-attach-webview', (event: Electron.Event, webPreferences, params) => { + app.on('web-contents-created', (_event: Event, contents) => { + contents.on('will-attach-webview', (event: Event, webPreferences, params) => { const isValidWebviewSource = (source: string): boolean => { if (!source) { @@ -247,7 +247,7 @@ export class CodeApplication extends Disposable { app.on('new-window-for-tab', () => { if (this.windowsMainService) { - this.windowsMainService.openNewWindow(OpenContext.DESKTOP); //macOS native tab "+" button + this.windowsMainService.openEmptyWindow(OpenContext.DESKTOP); //macOS native tab "+" button } }); @@ -258,7 +258,7 @@ export class CodeApplication extends Disposable { this.lifecycleMainService.kill(code); }); - ipc.on('vscode:fetchShellEnv', async (event: Electron.IpcMainEvent) => { + ipc.on('vscode:fetchShellEnv', async (event: IpcMainEvent) => { const webContents = event.sender; try { @@ -275,10 +275,10 @@ export class CodeApplication extends Disposable { } }); - ipc.on('vscode:toggleDevTools', (event: Electron.IpcMainEvent) => event.sender.toggleDevTools()); - ipc.on('vscode:openDevTools', (event: Electron.IpcMainEvent) => event.sender.openDevTools()); + ipc.on('vscode:toggleDevTools', (event: IpcMainEvent) => event.sender.toggleDevTools()); + ipc.on('vscode:openDevTools', (event: IpcMainEvent) => event.sender.openDevTools()); - ipc.on('vscode:reloadWindow', (event: Electron.IpcMainEvent) => event.sender.reload()); + ipc.on('vscode:reloadWindow', (event: IpcMainEvent) => event.sender.reload()); // Some listeners after window opened (async () => { @@ -449,7 +449,8 @@ export class CodeApplication extends Disposable { } services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId, this.userEnv])); - services.set(IWindowsService, new SyncDescriptor(WindowsService, [sharedProcess])); + services.set(ISharedProcessMainService, new SyncDescriptor(SharedProcessMainService, [sharedProcess])); + services.set(IWindowsService, new SyncDescriptor(LegacyWindowsMainService)); services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService)); const diagnosticsChannel = getDelayedChannel(sharedProcessClient.then(client => client.getChannel('diagnostics'))); @@ -546,8 +547,12 @@ export class CodeApplication extends Disposable { const electronChannel = new SimpleServiceProxyChannel(electronService); electronIpcServer.registerChannel('electron', electronChannel); + const sharedProcessMainService = accessor.get(ISharedProcessMainService); + const sharedProcessChannel = new SimpleServiceProxyChannel(sharedProcessMainService); + electronIpcServer.registerChannel('sharedProcess', sharedProcessChannel); + const workspacesMainService = accessor.get(IWorkspacesMainService); - const workspacesChannel = new WorkspacesChannel(workspacesMainService); + const workspacesChannel = new WorkspacesChannel(workspacesMainService, accessor.get(IWindowsMainService)); electronIpcServer.registerChannel('workspaces', workspacesChannel); const windowsService = accessor.get(IWindowsService); @@ -556,7 +561,7 @@ export class CodeApplication extends Disposable { sharedProcessClient.then(client => client.registerChannel('windows', windowsChannel)); const menubarService = accessor.get(IMenubarService); - const menubarChannel = new MenubarChannel(menubarService); + const menubarChannel = new SimpleServiceProxyChannel(menubarService); electronIpcServer.registerChannel('menubar', menubarChannel); const urlService = accessor.get(IURLService); diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index 57bb78578e..1e2909a0e4 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -7,13 +7,13 @@ import { localize } from 'vs/nls'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { Event } from 'vs/base/common/event'; -import { BrowserWindow, app } from 'electron'; +import { BrowserWindow, app, AuthInfo, WebContents, Event as ElectronEvent } from 'electron'; type LoginEvent = { - event: Electron.Event; - webContents: Electron.WebContents; - req: Electron.Request; - authInfo: Electron.AuthInfo; + event: ElectronEvent; + webContents: WebContents; + req: Request; + authInfo: AuthInfo; cb: (username: string, password: string) => void; }; @@ -93,4 +93,4 @@ export class ProxyAuthHandler { dispose(): void { this.disposables = dispose(this.disposables); } -} \ No newline at end of file +} diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index d8f8a2b985..ceab5d0409 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -14,7 +14,7 @@ import { mkdirp } from 'vs/base/node/pfs'; import { validatePaths } from 'vs/code/node/paths'; 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 { LaunchChannelClient } from 'vs/platform/launch/electron-main/launchMainService'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index a3d0a42705..aeff3c626f 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -18,7 +18,7 @@ export class SharedProcess implements ISharedProcess { private barrier = new Barrier(); - private window: Electron.BrowserWindow | null = null; + private window: BrowserWindow | null = null; constructor( private readonly machineId: string, diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 9a8993a6dc..4aa7856093 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -7,7 +7,7 @@ import * as path from 'vs/base/common/path'; import * as objects from 'vs/base/common/objects'; import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display } from 'electron'; +import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment } from 'electron'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -44,7 +44,7 @@ export const defaultWindowState = function (mode = WindowMode.Normal): IWindowSt }; }; -interface ITouchBarSegment extends Electron.SegmentedControlSegment { +interface ITouchBarSegment extends SegmentedControlSegment { id: string; } @@ -58,7 +58,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { private hiddenTitleBarStyle: boolean; private showTimeoutHandle: NodeJS.Timeout; private _id: number; - private _win: Electron.BrowserWindow; + private _win: BrowserWindow; private _lastFocusTime: number; private _readyState: ReadyState; private windowState: IWindowState; @@ -72,7 +72,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { private marketplaceHeadersPromise: Promise; - private readonly touchBarGroups: Electron.TouchBarSegmentedControl[]; + private readonly touchBarGroups: TouchBarSegmentedControl[]; constructor( config: IWindowCreationOptions, @@ -116,7 +116,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // in case we are maximized or fullscreen, only show later after the call to maximize/fullscreen (see below) const isFullscreenOrMaximized = (this.windowState.mode === WindowMode.Maximized || this.windowState.mode === WindowMode.Fullscreen); - const options: Electron.BrowserWindowConstructorOptions = { + const options: BrowserWindowConstructorOptions = { width: this.windowState.width, height: this.windowState.height, x: this.windowState.x, @@ -231,7 +231,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { return this._id; } - get win(): Electron.BrowserWindow { + get win(): BrowserWindow { return this._win; } @@ -421,7 +421,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { }); // Window Failed to load - this._win.webContents.on('did-fail-load', (event: Electron.Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean) => { + this._win.webContents.on('did-fail-load', (event: Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean) => { this.logService.warn('[electron event]: fail to load, ', errorDescription); }); @@ -653,7 +653,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // only consider non-minimized window states if (mode === WindowMode.Normal || mode === WindowMode.Maximized) { - let bounds: Electron.Rectangle; + let bounds: Rectangle; if (mode === WindowMode.Normal) { bounds = this.getBounds(); } else { @@ -778,7 +778,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { return undefined; } - getBounds(): Electron.Rectangle { + getBounds(): Rectangle { const pos = this._win.getPosition(); const dimension = this._win.getSize(); @@ -908,7 +908,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } - onWindowTitleDoubleClick(): void { + handleTitleDoubleClick(): void { // Respect system settings on mac with regards to title click on windows title if (isMacintosh) { @@ -988,7 +988,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._win.setTouchBar(new TouchBar({ items: this.touchBarGroups })); } - private createTouchBarGroup(items: ISerializableCommandAction[] = []): Electron.TouchBarSegmentedControl { + private createTouchBarGroup(items: ISerializableCommandAction[] = []): TouchBarSegmentedControl { // Group Segments const segments = this.createTouchBarGroupSegments(items); @@ -1008,7 +1008,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { private createTouchBarGroupSegments(items: ISerializableCommandAction[] = []): ITouchBarSegment[] { const segments: ITouchBarSegment[] = items.map(item => { - let icon: Electron.NativeImage | undefined; + let icon: NativeImage | undefined; if (item.iconLocation && item.iconLocation.dark.scheme === 'file') { icon = nativeImage.createFromPath(URI.revive(item.iconLocation.dark).fsPath); if (icon.isEmpty()) { diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index e720e9c70e..f94abe0ea1 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -13,12 +13,12 @@ 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 { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences, FileFilter, shell, MessageBoxReturnValue, MessageBoxOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, Display } from 'electron'; import { parseLineAndColumnAware } from 'vs/code/node/paths'; 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 { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, 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/common/product'; @@ -197,9 +197,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService { @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); - const windowsStateStoreData = this.stateService.getItem(WindowsManager.windowsStateStorageKey); - this.windowsState = restoreWindowsState(windowsStateStoreData); + this.windowsState = restoreWindowsState(this.stateService.getItem(WindowsManager.windowsStateStorageKey)); if (!Array.isArray(this.windowsState.openedWindows)) { this.windowsState.openedWindows = []; } @@ -227,7 +226,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { private registerListeners(): void { // React to workbench ready events from windows - ipc.on('vscode:workbenchReady', (event: Electron.Event, windowId: number) => { + ipc.on('vscode:workbenchReady', (event: Event, windowId: number) => { this.logService.trace('IPC#vscode-workbenchReady'); const win = this.getWindowById(windowId); @@ -381,6 +380,12 @@ export class WindowsManager extends Disposable implements IWindowsMainService { }; } + async openExternal(url: string): Promise { + shell.openExternal(url); + + return true; + } + open(openConfig: IOpenConfiguration): ICodeWindow[] { this.logService.trace('windowsManager#open'); openConfig = this.validateOpenConfig(openConfig); @@ -867,7 +872,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { message = localize('uriInvalidTitle', "URI can not be opened"); detail = localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", uri.toString()); } - const options: Electron.MessageBoxOptions = { + const options: MessageBoxOptions = { title: product.nameLong, type: 'info', buttons: [localize('ok', "OK")], @@ -1468,7 +1473,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // // We want the new window to open on the same display that the last active one is in - let displayToUse: Electron.Display | undefined; + let displayToUse: Display | undefined; const displays = screen.getAllDisplays(); // Single Display @@ -1605,13 +1610,13 @@ export class WindowsManager extends Disposable implements IWindowsMainService { return getLastActiveWindow(WindowsManager.WINDOWS.filter(window => window.remoteAuthority === remoteAuthority)); } - openNewWindow(context: OpenContext, options?: INewWindowOptions): ICodeWindow[] { + openEmptyWindow(context: OpenContext, options?: { reuse?: boolean, remoteAuthority?: string }): ICodeWindow[] { let cli = this.environmentService.args; - const remote = options && options.remoteAuthority || undefined; + const remote = options && options.remoteAuthority; if (cli && (cli.remote !== remote)) { cli = { ...cli, remote }; } - const forceReuseWindow = options && options.reuseWindow; + const forceReuseWindow = options && options.reuse; const forceNewWindow = !forceReuseWindow; return this.open({ context, cli, forceEmpty: true, forceNewWindow, forceReuseWindow }); } @@ -1665,11 +1670,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { getWindowById(windowId: number): ICodeWindow | undefined { const res = WindowsManager.WINDOWS.filter(window => window.id === windowId); - if (res && res.length === 1) { - return res[0]; - } - - return undefined; + return arrays.firstOrDefault(res); } getWindows(): ICodeWindow[] { @@ -1714,9 +1715,9 @@ export class WindowsManager extends Disposable implements IWindowsMainService { return; // Return early if the window has been going down already } - if (result.button === 0) { + if (result.response === 0) { window.reload(); - } else if (result.button === 2) { + } else if (result.response === 2) { this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually window.win.destroy(); // make sure to destroy the window as it is unresponsive } @@ -1737,9 +1738,9 @@ export class WindowsManager extends Disposable implements IWindowsMainService { return; // Return early if the window has been going down already } - if (result.button === 0) { + if (result.response === 0) { window.reload(); - } else if (result.button === 1) { + } else if (result.response === 1) { this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually window.win.destroy(); // make sure to destroy the window as it has crashed } @@ -1761,7 +1762,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { this._onWindowClose.fire(win.id); } - async pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise { + async pickFileFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise { const title = localize('open', "Open"); const paths = await this.dialogs.pick({ ...options, pickFolders: true, pickFiles: true, title }); if (paths) { @@ -1773,7 +1774,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { })); this.open({ context: OpenContext.DIALOG, - contextWindowId: options.windowId, + contextWindowId: win ? win.id : undefined, cli: this.environmentService.args, urisToOpen, forceNewWindow: options.forceNewWindow @@ -1781,14 +1782,14 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } } - async pickFolderAndOpen(options: INativeOpenDialogOptions): Promise { + async pickFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise { const title = localize('openFolder', "Open Folder"); const paths = await this.dialogs.pick({ ...options, pickFolders: true, title }); if (paths) { this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFolder', options.telemetryExtraData); this.open({ context: OpenContext.DIALOG, - contextWindowId: options.windowId, + contextWindowId: win ? win.id : undefined, cli: this.environmentService.args, urisToOpen: paths.map(path => ({ folderUri: URI.file(path) })), forceNewWindow: options.forceNewWindow @@ -1796,14 +1797,14 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } } - async pickFileAndOpen(options: INativeOpenDialogOptions): Promise { + async pickFileAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise { const title = localize('openFile', "Open File"); const paths = await this.dialogs.pick({ ...options, pickFiles: true, title }); if (paths) { this.sendPickerTelemetry(paths, options.telemetryEventName || 'openFile', options.telemetryExtraData); this.open({ context: OpenContext.DIALOG, - contextWindowId: options.windowId, + contextWindowId: win ? win.id : undefined, cli: this.environmentService.args, urisToOpen: paths.map(path => ({ fileUri: URI.file(path) })), forceNewWindow: options.forceNewWindow @@ -1811,7 +1812,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } } - async pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise { + async pickWorkspaceAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise { const title = localize('openWorkspaceTitle', "Open Workspace"); const buttonLabel = mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")); const filters = WORKSPACE_FILTER; @@ -1820,7 +1821,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { this.sendPickerTelemetry(paths, options.telemetryEventName || 'openWorkspace', options.telemetryExtraData); this.open({ context: OpenContext.DIALOG, - contextWindowId: options.windowId, + contextWindowId: win ? win.id : undefined, cli: this.environmentService.args, urisToOpen: paths.map(path => ({ workspaceUri: URI.file(path) })), forceNewWindow: options.forceNewWindow @@ -1842,15 +1843,15 @@ export class WindowsManager extends Disposable implements IWindowsMainService { }); } - showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): Promise { + showMessageBox(options: MessageBoxOptions, win?: ICodeWindow): Promise { return this.dialogs.showMessageBox(options, win); } - showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): Promise { + showSaveDialog(options: SaveDialogOptions, win?: ICodeWindow): Promise { return this.dialogs.showSaveDialog(options, win); } - showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): Promise { + showOpenDialog(options: OpenDialogOptions, win?: ICodeWindow): Promise { return this.dialogs.showOpenDialog(options, win); } @@ -1897,10 +1898,10 @@ class Dialogs { this.noWindowDialogQueue = new Queue(); } - async pick(options: IInternalNativeOpenDialogOptions): Promise { + async pick(options: IInternalNativeOpenDialogOptions, win?: ICodeWindow): Promise { // Ensure dialog options - const dialogOptions: Electron.OpenDialogOptions = { + const dialogOptions: OpenDialogOptions = { title: options.title, buttonLabel: options.buttonLabel, filters: options.filters @@ -1928,15 +1929,15 @@ class Dialogs { } // Show Dialog - const focusedWindow = (typeof options.windowId === 'number' ? this.windowsMainService.getWindowById(options.windowId) : undefined) || this.windowsMainService.getFocusedWindow(); + const windowToUse = win || this.windowsMainService.getFocusedWindow(); - const paths = await this.showOpenDialog(dialogOptions, focusedWindow); - if (paths && paths.length > 0) { + const result = await this.showOpenDialog(dialogOptions, windowToUse); + if (result && result.filePaths && result.filePaths.length > 0) { // Remember path in storage for next time - this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0])); + this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(result.filePaths[0])); - return paths; + return result.filePaths; } return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check @@ -1956,20 +1957,17 @@ class Dialogs { return windowDialogQueue; } - showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): Promise { + showMessageBox(options: MessageBoxOptions, window?: ICodeWindow): Promise { 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); + return dialog.showMessageBox(window.win, options); } - return { button: result.response, checkboxChecked: result.checkboxChecked }; + return dialog.showMessageBox(options); }); } - showSaveDialog(options: Electron.SaveDialogOptions, window?: ICodeWindow): Promise { + showSaveDialog(options: SaveDialogOptions, window?: ICodeWindow): Promise { function normalizePath(path: string | undefined): string | undefined { if (path && isMacintosh) { @@ -1980,18 +1978,20 @@ class Dialogs { } return this.getDialogQueue(window).queue(async () => { - let result: Electron.SaveDialogReturnValue; + let result: SaveDialogReturnValue; if (window) { result = await dialog.showSaveDialog(window.win, options); } else { result = await dialog.showSaveDialog(options); } - return normalizePath(result.filePath); + result.filePath = normalizePath(result.filePath); + + return result; }); } - showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): Promise { + showOpenDialog(options: OpenDialogOptions, window?: ICodeWindow): Promise { function normalizePaths(paths: string[] | undefined): string[] | undefined { if (paths && paths.length > 0 && isMacintosh) { @@ -2012,14 +2012,16 @@ class Dialogs { } // Show dialog - let result: Electron.OpenDialogReturnValue; + let result: OpenDialogReturnValue; if (window) { result = await dialog.showOpenDialog(window.win, options); } else { result = await dialog.showOpenDialog(options); } - return normalizePaths(result.filePaths); + result.filePaths = normalizePaths(result.filePaths); + + return result; }); } } @@ -2056,7 +2058,7 @@ class WorkspacesManager { // Prevent overwriting a workspace that is currently opened in another window if (findWindowOnWorkspace(this.windowsMainService.getWindows(), getWorkspaceIdentifier(path))) { - const options: Electron.MessageBoxOptions = { + const options: MessageBoxOptions = { title: product.nameLong, type: 'info', buttons: [localize('ok', "OK")], diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index 945cd8b2d7..dca4615d6c 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -70,8 +70,8 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC return Object.keys(this._diffEditors).map(id => this._diffEditors[id]); } - getFocusedCodeEditor(): ICodeEditor | null { - let editorWithWidgetFocus: ICodeEditor | null = null; + getFocusedCodeEditor(): ICodeEditor | undefined { + let editorWithWidgetFocus: ICodeEditor | undefined; const editors = this.listCodeEditors(); for (const editor of editors) { @@ -124,8 +124,8 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC delete this._transientWatchers[w.uri]; } - abstract getActiveCodeEditor(): ICodeEditor | null; - abstract openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; + abstract getActiveCodeEditor(): ICodeEditor | undefined; + abstract openCodeEditor(input: IResourceInput, source: ICodeEditor | undefined, sideBySide?: boolean): Promise; } export class ModelTransientSettingWatcher { diff --git a/src/vs/editor/browser/services/codeEditorService.ts b/src/vs/editor/browser/services/codeEditorService.ts index c8f84e3119..fc861b28b5 100644 --- a/src/vs/editor/browser/services/codeEditorService.ts +++ b/src/vs/editor/browser/services/codeEditorService.ts @@ -26,16 +26,17 @@ export interface ICodeEditorService { addCodeEditor(editor: ICodeEditor): void; removeCodeEditor(editor: ICodeEditor): void; - listCodeEditors(): ICodeEditor[]; + listCodeEditors(): readonly ICodeEditor[]; addDiffEditor(editor: IDiffEditor): void; removeDiffEditor(editor: IDiffEditor): void; - listDiffEditors(): IDiffEditor[]; + listDiffEditors(): readonly IDiffEditor[]; /** - * Returns the current focused code editor (if the focus is in the editor or in an editor widget) or null. + * Returns the current focused code editor (if the focus is in the editor or in an editor widget) or + * `undefined` if none. */ - getFocusedCodeEditor(): ICodeEditor | null; + getFocusedCodeEditor(): ICodeEditor | undefined; registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string): void; removeDecorationType(key: string): void; @@ -44,6 +45,6 @@ export interface ICodeEditorService { setTransientModelProperty(model: ITextModel, key: string, value: any): void; getTransientModelProperty(model: ITextModel, key: string): any; - getActiveCodeEditor(): ICodeEditor | null; - openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; + getActiveCodeEditor(): ICodeEditor | undefined; + openCodeEditor(input: IResourceInput, source: ICodeEditor | undefined, sideBySide?: boolean): Promise; } diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index 1840d47596..a4993570fc 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -65,8 +65,8 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { return provider.getOptions(this, writable); } - abstract getActiveCodeEditor(): ICodeEditor | null; - abstract openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise; + abstract getActiveCodeEditor(): ICodeEditor | undefined; + abstract openCodeEditor(input: IResourceInput, source: ICodeEditor | undefined, sideBySide?: boolean): Promise; } interface IModelDecorationOptionsProvider extends IDisposable { diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index f71d0a604a..d5eb21cd8c 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, IExternalUriResolver } from 'vs/platform/opener/common/opener'; +import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions } from 'vs/platform/opener/common/opener'; export class OpenerService extends Disposable implements IOpenerService { @@ -45,7 +45,7 @@ export class OpenerService extends Disposable implements IOpenerService { return { dispose: remove }; } - async open(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { + async open(resource: URI, options?: OpenOptions): Promise { // no scheme ?!? if (!resource.scheme) { return Promise.resolve(false); @@ -65,22 +65,33 @@ export class OpenerService extends Disposable implements IOpenerService { return true; } } + // use default openers return this._doOpen(resource, options); } - private _doOpen(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { + public async resolveExternalUri(resource: URI, options?: { readonly allowTunneling?: boolean }): Promise<{ resolved: URI, dispose(): void }> { + for (const resolver of this._resolvers.toArray()) { + const result = await resolver.resolveExternalUri(resource, options); + if (result) { + return result; + } + } + return { resolved: resource, dispose: () => { } }; + } + + private _doOpen(resource: URI, options: OpenOptions | undefined): Promise { const { scheme, path, query, fragment } = resource; if (equalsIgnoreCase(scheme, Schemas.mailto) || (options && options.openExternal)) { // open default mail application - return this._doOpenExternal(resource); + return this._doOpenExternal(resource, options); } if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https)) { // open link in default browser - return this._doOpenExternal(resource); + return this._doOpenExternal(resource, options); } else if (equalsIgnoreCase(scheme, Schemas.command)) { // run command or bail out if command isn't known if (!CommandsRegistry.getCommand(path)) { @@ -124,13 +135,9 @@ export class OpenerService extends Disposable implements IOpenerService { } } - private async _doOpenExternal(resource: URI): Promise { - for (const resolver of this._resolvers.toArray()) { - resource = await resolver.resolveExternalUri(resource); - } - - dom.windowOpenNoOpener(encodeURI(resource.toString(true))); - + private async _doOpenExternal(resource: URI, options: OpenOptions | undefined): Promise { + const { resolved } = await this.resolveExternalUri(resource, options); + dom.windowOpenNoOpener(encodeURI(resolved.toString(true))); return Promise.resolve(true); } diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 876b2cb5c9..9875ed2f6b 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -737,7 +737,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE ); } - public setSelections(ranges: ISelection[], source: string = 'api'): void { + public setSelections(ranges: readonly ISelection[], source: string = 'api'): void { if (!this._modelData) { return; } @@ -1172,7 +1172,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } public hasWidgetFocus(): boolean { - return (this._editorWidgetFocus.getValue() === BooleanEventValue.True); + return this._focusTracker && this._focusTracker.hasFocus(); } public addContentWidget(widget: editorBrowser.IContentWidget): void { @@ -1452,13 +1452,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } const viewOutgoingEvents = new ViewOutgoingEvents(viewModel); - viewOutgoingEvents.onDidGainFocus = () => { - this._editorTextFocus.setValue(true); - // In IE, the focus is not synchronous, so we give it a little help - this._editorWidgetFocus.setValue(true); - }; - viewOutgoingEvents.onDidScroll = (e) => this._onDidScrollChange.fire(e); + viewOutgoingEvents.onDidGainFocus = () => this._editorTextFocus.setValue(true); viewOutgoingEvents.onDidLoseFocus = () => this._editorTextFocus.setValue(false); viewOutgoingEvents.onContextMenu = (e) => this._onContextMenu.fire(e); viewOutgoingEvents.onMouseDown = (e) => this._onMouseDown.fire(e); @@ -1548,10 +1543,6 @@ export class BooleanEventEmitter extends Disposable { this._value = BooleanEventValue.NotSet; } - public getValue(): BooleanEventValue { - return this._value; - } - public setValue(_value: boolean) { const value = (_value ? BooleanEventValue.True : BooleanEventValue.False); if (this._value === value) { diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index b7f6e68d5a..3ac66cad5b 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -728,7 +728,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this.modifiedEditor.setSelection(something); } - public setSelections(ranges: ISelection[]): void { + public setSelections(ranges: readonly ISelection[]): void { this.modifiedEditor.setSelections(ranges); } diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index 0c09c0de29..537a00310b 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -424,7 +424,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { return this._cursors.getPrimaryCursor().modelState.position; } - public setSelections(source: string, selections: ISelection[]): void { + public setSelections(source: string, selections: readonly ISelection[]): void { this.setStates(source, CursorChangeReason.NotSet, CursorState.fromModelSelections(selections)); } diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index 1bd9f55ac4..1acd5d9d71 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -460,7 +460,7 @@ export class CursorState { return CursorState.fromModelState(modelState); } - public static fromModelSelections(modelSelections: ISelection[]): PartialModelCursorState[] { + public static fromModelSelections(modelSelections: readonly ISelection[]): PartialModelCursorState[] { let states: PartialModelCursorState[] = []; for (let i = 0, len = modelSelections.length; i < len; i++) { states[i] = this.fromModelSelection(modelSelections[i]); diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 63fc4720df..3305765401 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -389,7 +389,7 @@ export interface IEditor { * Set the selections for all the cursors of the editor. * Cursors will be removed or added, as necessary. */ - setSelections(selections: ISelection[]): void; + setSelections(selections: readonly ISelection[]): void; /** * Scroll vertically as necessary and reveal lines. diff --git a/src/vs/editor/common/modes/languageFeatureRegistry.ts b/src/vs/editor/common/modes/languageFeatureRegistry.ts index 75791a68cd..d71af7e5f8 100644 --- a/src/vs/editor/common/modes/languageFeatureRegistry.ts +++ b/src/vs/editor/common/modes/languageFeatureRegistry.ts @@ -32,9 +32,6 @@ export class LanguageFeatureRegistry { private readonly _entries: Entry[] = []; private readonly _onDidChange = new Emitter(); - constructor() { - } - get onDidChange(): Event { return this._onDidChange.event; } diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index 12aa78b072..59d91b3daf 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -11,6 +11,7 @@ import { FrankensteinMode } from 'vs/editor/common/modes/abstractMode'; import { NULL_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/nullMode'; import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry'; import { ILanguageSelection, IModeService } from 'vs/editor/common/services/modeService'; +import { firstOrDefault } from 'vs/base/common/arrays'; class LanguageSelection extends Disposable implements ILanguageSelection { @@ -96,22 +97,12 @@ export class ModeServiceImpl implements IModeService { public getModeIdByFilepathOrFirstLine(resource: URI | null, firstLine?: string): string | null { const modeIds = this._registry.getModeIdsFromFilepathOrFirstLine(resource, firstLine); - - if (modeIds.length > 0) { - return modeIds[0]; - } - - return null; + return firstOrDefault(modeIds, null); } public getModeId(commaSeparatedMimetypesOrCommaSeparatedIds: string | undefined): string | null { const modeIds = this._registry.extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds); - - if (modeIds.length > 0) { - return modeIds[0]; - } - - return null; + return firstOrDefault(modeIds, null); } public getLanguageIdentifier(modeId: string | LanguageId): LanguageIdentifier | null { @@ -164,12 +155,7 @@ export class ModeServiceImpl implements IModeService { private _getModeIdByLanguageName(languageName: string): string | null { const modeIds = this._registry.getModeIdsFromLanguageName(languageName); - - if (modeIds.length > 0) { - return modeIds[0]; - } - - return null; + return firstOrDefault(modeIds, null); } private _getOrCreateMode(modeId: string): IMode { diff --git a/src/vs/editor/common/view/minimapCharRenderer.ts b/src/vs/editor/common/view/minimapCharRenderer.ts index fc87a3473d..71faf09d31 100644 --- a/src/vs/editor/common/view/minimapCharRenderer.ts +++ b/src/vs/editor/common/view/minimapCharRenderer.ts @@ -19,7 +19,7 @@ export class MinimapTokensColorTracker { private _colors!: RGBA8[]; private _backgroundIsLight!: boolean; - private _onDidChange = new Emitter(); + private readonly _onDidChange = new Emitter(); public readonly onDidChange: Event = this._onDidChange.event; private constructor() { diff --git a/src/vs/editor/common/view/viewEvents.ts b/src/vs/editor/common/view/viewEvents.ts index 4b8773b80b..08562381af 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -244,9 +244,6 @@ export class ViewTokensChangedEvent { export class ViewThemeChangedEvent { public readonly type = ViewEventType.ViewThemeChanged; - - constructor() { - } } export class ViewTokensColorsChangedEvent { @@ -270,9 +267,6 @@ export class ViewZonesChangedEvent { export class ViewLanguageConfigurationEvent { public readonly type = ViewEventType.ViewLanguageConfigurationChanged; - - constructor() { - } } export type ViewEvent = ( diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index e96895197d..93e435b15b 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -1401,9 +1401,6 @@ class OverviewRulerDecorations { readonly result: IOverviewRulerDecorations = Object.create(null); - constructor() { - } - public accept(color: string, startLineNumber: number, endLineNumber: number, lane: number): void { let prev = this.result[color]; diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.css b/src/vs/editor/contrib/codeAction/lightBulbWidget.css index 8aba43a852..08ab8d9715 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.css +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.css @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-editor .lightbulb-glyph { +.monaco-editor .codicon-lightbulb { display: flex; align-items: center; justify-content: center; @@ -12,25 +12,7 @@ padding-left: 2px; } -.monaco-editor .lightbulb-glyph:hover { +.monaco-editor .codicon-lightbulb:hover { cursor: pointer; /* transform: scale(1.3, 1.3); */ } - -.monaco-editor.vs .lightbulb-glyph { - background: url('lightbulb-light.svg') center center no-repeat; -} - -.monaco-editor.vs .lightbulb-glyph.autofixable { - background: url('lightbulb-autofix-light.svg') center center no-repeat; -} - -.monaco-editor.vs-dark .lightbulb-glyph, -.monaco-editor.hc-black .lightbulb-glyph { - background: url('lightbulb-dark.svg') center center no-repeat; -} - -.monaco-editor.vs-dark .lightbulb-glyph.autofixable, -.monaco-editor.hc-black .lightbulb-glyph.autofixable { - background: url('lightbulb-autofix-dark.svg') center center no-repeat; -} diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index ea6979d7c7..e6e3387f7e 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts @@ -15,6 +15,8 @@ import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import * as nls from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; namespace LightBulbState { @@ -57,7 +59,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { ) { super(); this._domNode = document.createElement('div'); - this._domNode.className = 'lightbulb-glyph'; + this._domNode.className = 'codicon codicon-lightbulb'; this._editor.addContentWidget(this); @@ -175,7 +177,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { position: { lineNumber: effectiveLineNumber, column: 1 }, preference: LightBulbWidget._posPref }); - dom.toggleClass(this._domNode, 'autofixable', actions.hasAutoFix); + dom.toggleClass(this._domNode, 'codicon-lightbulb-autofix', actions.hasAutoFix); this._editor.layoutContentWidget(this); } @@ -199,3 +201,27 @@ export class LightBulbWidget extends Disposable implements IContentWidget { this.title = title; } } + +registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + + // Lightbulb Icon + const editorLightBulbForegroundColor = theme.getColor(editorLightBulbForeground); + if (editorLightBulbForegroundColor) { + collector.addRule(` + .monaco-workbench .contentWidgets .codicon-lightbulb, + .monaco-workbench .markers-panel-container .codicon-lightbulb { + color: ${editorLightBulbForegroundColor}; + }`); + } + + // Lightbulb Auto Fix Icon + const editorLightBulbAutoFixForegroundColor = theme.getColor(editorLightBulbAutoFixForeground); + if (editorLightBulbAutoFixForegroundColor) { + collector.addRule(` + .monaco-workbench .contentWidgets .codicon-lightbulb-autofix, + .monaco-workbench .markers-panel-container .codicon-lightbulb-autofix { + color: ${editorLightBulbAutoFixForegroundColor}; + }`); + } + +}); diff --git a/src/vs/editor/contrib/codeAction/lightbulb-autofix-dark.svg b/src/vs/editor/contrib/codeAction/lightbulb-autofix-dark.svg deleted file mode 100644 index 34d4f3aedf..0000000000 --- a/src/vs/editor/contrib/codeAction/lightbulb-autofix-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/editor/contrib/codeAction/lightbulb-autofix-light.svg b/src/vs/editor/contrib/codeAction/lightbulb-autofix-light.svg deleted file mode 100644 index c34a0c2805..0000000000 --- a/src/vs/editor/contrib/codeAction/lightbulb-autofix-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/editor/contrib/codeAction/lightbulb-dark.svg b/src/vs/editor/contrib/codeAction/lightbulb-dark.svg deleted file mode 100644 index d2b6e1287a..0000000000 --- a/src/vs/editor/contrib/codeAction/lightbulb-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/codeAction/lightbulb-light.svg b/src/vs/editor/contrib/codeAction/lightbulb-light.svg deleted file mode 100644 index 8572effd08..0000000000 --- a/src/vs/editor/contrib/codeAction/lightbulb-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/editor/contrib/colorPicker/colorPickerModel.ts b/src/vs/editor/contrib/colorPicker/colorPickerModel.ts index 4c77a4d055..781be66050 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerModel.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerModel.ts @@ -41,13 +41,13 @@ export class ColorPickerModel { this._onDidChangePresentation.fire(this.presentation); } - private _onColorFlushed = new Emitter(); + private readonly _onColorFlushed = new Emitter(); readonly onColorFlushed: Event = this._onColorFlushed.event; - private _onDidChangeColor = new Emitter(); + private readonly _onDidChangeColor = new Emitter(); readonly onDidChangeColor: Event = this._onDidChangeColor.event; - private _onDidChangePresentation = new Emitter(); + private readonly _onDidChangePresentation = new Emitter(); readonly onDidChangePresentation: Event = this._onDidChangePresentation.event; constructor(color: Color, availableColorPresentations: IColorPresentation[], private presentationIndex: number) { diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts index bd9e5f7a32..3368b5ace7 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts @@ -127,10 +127,10 @@ class SaturationBox extends Disposable { private height!: number; private monitor: GlobalMouseMoveMonitor | null; - private _onDidChange = new Emitter<{ s: number, v: number }>(); + private readonly _onDidChange = new Emitter<{ s: number, v: number }>(); readonly onDidChange: Event<{ s: number, v: number }> = this._onDidChange.event; - private _onColorFlushed = new Emitter(); + private readonly _onColorFlushed = new Emitter(); readonly onColorFlushed: Event = this._onColorFlushed.event; constructor(container: HTMLElement, private readonly model: ColorPickerModel, private pixelRatio: number) { @@ -237,10 +237,10 @@ abstract class Strip extends Disposable { protected slider: HTMLElement; private height!: number; - private _onDidChange = new Emitter(); + private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; - private _onColorFlushed = new Emitter(); + private readonly _onColorFlushed = new Emitter(); readonly onColorFlushed: Event = this._onColorFlushed.event; constructor(container: HTMLElement, protected model: ColorPickerModel) { diff --git a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts index 29b81d9ee8..c49e6d223b 100644 --- a/src/vs/editor/contrib/cursorUndo/cursorUndo.ts +++ b/src/vs/editor/contrib/cursorUndo/cursorUndo.ts @@ -14,9 +14,9 @@ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; class CursorState { - readonly selections: Selection[]; + readonly selections: readonly Selection[]; - constructor(selections: Selection[]) { + constructor(selections: readonly Selection[]) { this.selections = selections; } diff --git a/src/vs/editor/contrib/folding/folding.css b/src/vs/editor/contrib/folding/folding.css index c6ac58bf05..f04ce20e65 100644 --- a/src/vs/editor/contrib/folding/folding.css +++ b/src/vs/editor/contrib/folding/folding.css @@ -3,47 +3,23 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-editor .margin-view-overlays .folding { +.monaco-editor .margin-view-overlays .codicon { cursor: pointer; - background-repeat: no-repeat; - background-origin: border-box; - background-position: calc(50% + 2px) center; - background-size: auto calc(100% - 3px); opacity: 0; transition: opacity 0.5s; + display: flex; + align-items: center; + justify-content: center; + font-size: 140%; + margin-left: 2px; } -.monaco-editor .margin-view-overlays .folding { - background-image: url('tree-expanded-light.svg'); -} - -.monaco-editor.hc-black .margin-view-overlays .folding, -.monaco-editor.vs-dark .margin-view-overlays .folding { - background-image: url('tree-expanded-dark.svg'); -} - -.monaco-editor.hc-black .margin-view-overlays .folding { - background-image: url('tree-expanded-hc.svg'); -} - -.monaco-editor .margin-view-overlays:hover .folding, -.monaco-editor .margin-view-overlays .folding.alwaysShowFoldIcons { +.monaco-editor .margin-view-overlays:hover .codicon, +.monaco-editor .margin-view-overlays .codicon.codicon-chevron-right, +.monaco-editor .margin-view-overlays .codicon.alwaysShowFoldIcons { opacity: 1; } -.monaco-editor .margin-view-overlays .folding.collapsed { - background-image: url('tree-collapsed-light.svg'); - opacity: 1; -} - -.monaco-editor.vs-dark .margin-view-overlays .folding.collapsed { - background-image: url('tree-collapsed-dark.svg'); -} - -.monaco-editor.hc-black .margin-view-overlays .folding.collapsed { - background-image: url('tree-collapsed-hc.svg'); -} - .monaco-editor .inline-folded:after { color: grey; margin: 0.1em 0.2em 0 0.2em; @@ -51,4 +27,4 @@ display: inline; line-height: 1em; cursor: pointer; -} \ No newline at end of file +} diff --git a/src/vs/editor/contrib/folding/foldingDecorations.ts b/src/vs/editor/contrib/folding/foldingDecorations.ts index 93a720c09b..8257c1c888 100644 --- a/src/vs/editor/contrib/folding/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/foldingDecorations.ts @@ -13,17 +13,17 @@ export class FoldingDecorationProvider implements IDecorationProvider { private static COLLAPSED_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, afterContentClassName: 'inline-folded', - linesDecorationsClassName: 'folding collapsed' + linesDecorationsClassName: 'codicon codicon-chevron-right' }); private static EXPANDED_AUTO_HIDE_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - linesDecorationsClassName: 'folding' + linesDecorationsClassName: 'codicon codicon-chevron-down' }); private static EXPANDED_VISUAL_DECORATION = ModelDecorationOptions.register({ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - linesDecorationsClassName: 'folding alwaysShowFoldIcons' + linesDecorationsClassName: 'codicon codicon-chevron-down alwaysShowFoldIcons' }); public autoHideFoldingControls: boolean = true; diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts index 77cbcfd0ec..1a41e37564 100644 --- a/src/vs/editor/contrib/folding/foldingModel.ts +++ b/src/vs/editor/contrib/folding/foldingModel.ts @@ -28,7 +28,7 @@ export class FoldingModel { private _editorDecorationIds: string[]; private _isInitialized: boolean; - private _updateEventEmitter = new Emitter(); + private readonly _updateEventEmitter = new Emitter(); public readonly onDidChange: Event = this._updateEventEmitter.event; public get regions(): FoldingRegions { return this._regions; } diff --git a/src/vs/editor/contrib/folding/hiddenRangeModel.ts b/src/vs/editor/contrib/folding/hiddenRangeModel.ts index 98b73811e9..b73d8313ae 100644 --- a/src/vs/editor/contrib/folding/hiddenRangeModel.ts +++ b/src/vs/editor/contrib/folding/hiddenRangeModel.ts @@ -14,7 +14,7 @@ export class HiddenRangeModel { private readonly _foldingModel: FoldingModel; private _hiddenRanges: IRange[]; private _foldingModelListener: IDisposable | null; - private _updateEventEmitter = new Emitter(); + private readonly _updateEventEmitter = new Emitter(); public get onDidChange(): Event { return this._updateEventEmitter.event; } public get hiddenRanges() { return this._hiddenRanges; } @@ -154,4 +154,4 @@ function findRange(ranges: IRange[], line: number): IRange | null { return ranges[i]; } return null; -} \ No newline at end of file +} diff --git a/src/vs/editor/contrib/folding/tree-collapsed-dark.svg b/src/vs/editor/contrib/folding/tree-collapsed-dark.svg deleted file mode 100644 index 243be1451c..0000000000 --- a/src/vs/editor/contrib/folding/tree-collapsed-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/folding/tree-collapsed-hc.svg b/src/vs/editor/contrib/folding/tree-collapsed-hc.svg deleted file mode 100644 index 40ba72b708..0000000000 --- a/src/vs/editor/contrib/folding/tree-collapsed-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/folding/tree-collapsed-light.svg b/src/vs/editor/contrib/folding/tree-collapsed-light.svg deleted file mode 100644 index 0d746558a4..0000000000 --- a/src/vs/editor/contrib/folding/tree-collapsed-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/folding/tree-expanded-dark.svg b/src/vs/editor/contrib/folding/tree-expanded-dark.svg deleted file mode 100644 index 5570923e17..0000000000 --- a/src/vs/editor/contrib/folding/tree-expanded-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/folding/tree-expanded-hc.svg b/src/vs/editor/contrib/folding/tree-expanded-hc.svg deleted file mode 100644 index b370009330..0000000000 --- a/src/vs/editor/contrib/folding/tree-expanded-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/folding/tree-expanded-light.svg b/src/vs/editor/contrib/folding/tree-expanded-light.svg deleted file mode 100644 index 939ebc8b96..0000000000 --- a/src/vs/editor/contrib/folding/tree-expanded-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts index 8b52db0fc3..4e10a88f80 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts @@ -162,7 +162,7 @@ export class DefinitionAction extends EditorAction { } } - private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean): Promise { + private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean): Promise { // range is the target-selection-range when we have one // and the the fallback is the 'full' range let range: IRange | undefined = undefined; diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index f07e0c52ce..1d95386839 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -173,7 +173,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { private readonly _callOnDispose = new DisposableStore(); private _severity: MarkerSeverity; private _backgroundColor?: Color; - private _onDidSelectRelatedInformation = new Emitter(); + private readonly _onDidSelectRelatedInformation = new Emitter(); private _heightInPixel!: number; readonly onDidSelectRelatedInformation: Event = this._onDidSelectRelatedInformation.event; diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index 64c9928bee..8d37c33c1c 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, IActionOptions, ServicesAccessor, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; @@ -425,23 +425,21 @@ export class AutoIndentOnPaste implements IEditorContribution { private static readonly ID = 'editor.contrib.autoIndentOnPaste'; private readonly editor: ICodeEditor; - private callOnDispose: IDisposable[]; - private callOnModel: IDisposable[]; + private readonly callOnDispose = new DisposableStore(); + private readonly callOnModel = new DisposableStore(); constructor(editor: ICodeEditor) { this.editor = editor; - this.callOnDispose = []; - this.callOnModel = []; - this.callOnDispose.push(editor.onDidChangeConfiguration(() => this.update())); - this.callOnDispose.push(editor.onDidChangeModel(() => this.update())); - this.callOnDispose.push(editor.onDidChangeModelLanguage(() => this.update())); + this.callOnDispose.add(editor.onDidChangeConfiguration(() => this.update())); + this.callOnDispose.add(editor.onDidChangeModel(() => this.update())); + this.callOnDispose.add(editor.onDidChangeModelLanguage(() => this.update())); } private update(): void { // clean up - this.callOnModel = dispose(this.callOnModel); + this.callOnModel.clear(); // we are disabled if (!this.editor.getOption(EditorOption.autoIndent) || this.editor.getOption(EditorOption.formatOnPaste)) { @@ -453,7 +451,7 @@ export class AutoIndentOnPaste implements IEditorContribution { return; } - this.callOnModel.push(this.editor.onDidPaste((range: Range) => { + this.callOnModel.add(this.editor.onDidPaste((range: Range) => { this.trigger(range); })); } @@ -611,8 +609,8 @@ export class AutoIndentOnPaste implements IEditorContribution { } public dispose(): void { - this.callOnDispose = dispose(this.callOnDispose); - this.callOnModel = dispose(this.callOnModel); + this.callOnDispose.dispose(); + this.callOnModel.dispose(); } } diff --git a/src/vs/editor/contrib/referenceSearch/media/chevron-next-dark.svg b/src/vs/editor/contrib/referenceSearch/media/chevron-next-dark.svg deleted file mode 100644 index 455532ddb7..0000000000 --- a/src/vs/editor/contrib/referenceSearch/media/chevron-next-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/referenceSearch/media/chevron-next-light.svg b/src/vs/editor/contrib/referenceSearch/media/chevron-next-light.svg deleted file mode 100644 index a443086f35..0000000000 --- a/src/vs/editor/contrib/referenceSearch/media/chevron-next-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/referenceSearch/media/chevron-previous-dark.svg b/src/vs/editor/contrib/referenceSearch/media/chevron-previous-dark.svg deleted file mode 100644 index 5ca3526019..0000000000 --- a/src/vs/editor/contrib/referenceSearch/media/chevron-previous-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/editor/contrib/referenceSearch/media/chevron-previous-light.svg b/src/vs/editor/contrib/referenceSearch/media/chevron-previous-light.svg deleted file mode 100644 index 87e179a7f3..0000000000 --- a/src/vs/editor/contrib/referenceSearch/media/chevron-previous-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 ab8bce2bf9..a39987f8aa 100644 --- a/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css +++ b/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css @@ -60,24 +60,3 @@ border-top: 1px solid; position: relative; } - -/* Dark Theme */ -/* High Contrast Theme */ - -.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 .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 .codicon.chevron-down { - background: url('chevron-next-light.svg') center center no-repeat; -} - -.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; -} diff --git a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts index 56790e31b9..c4c5e52f84 100644 --- a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts @@ -57,8 +57,8 @@ export namespace PeekContext { export const notInPeekEditor: ContextKeyExpr = inPeekEditor.toNegated(); } -export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | null { - let editor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); +export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | undefined { + const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); if (editor instanceof EmbeddedCodeEditorWidget) { return editor.getParentEditor(); } @@ -83,7 +83,7 @@ export abstract class PeekViewWidget extends ZoneWidget { public _serviceBrand: undefined; - private _onDidClose = new Emitter(); + private readonly _onDidClose = new Emitter(); protected _headElement?: HTMLDivElement; protected _primaryHeading?: HTMLElement; diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts index a54ff274ab..d79f70aba1 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -196,7 +196,7 @@ export class ReferenceWidget extends PeekViewWidget { private readonly _disposeOnNewModel = new DisposableStore(); private readonly _callOnDispose = new DisposableStore(); - private _onDidSelectReference = new Emitter(); + private readonly _onDidSelectReference = new Emitter(); private _tree!: WorkbenchAsyncDataTree; private _treeContainer!: HTMLElement; diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 6f1b2685ef..9351753613 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -6,7 +6,7 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { dispose, IDisposable, DisposableStore, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; @@ -36,8 +36,13 @@ import { CommitCharacterController } from './suggestCommitCharacters'; import { IPosition } from 'vs/editor/common/core/position'; import { TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import * as platform from 'vs/base/common/platform'; -const _sticky = false; // for development purposes only +/** + * Stop suggest widget from disappearing when clicking into other areas + * For development purpose only + */ +const _sticky = false; class LineSuffix { @@ -180,6 +185,21 @@ export class SuggestController implements IEditorContribution { } })); + this._toDispose.add(this._widget.getValue().onDetailsKeyDown(e => { + // cmd + c on macOS, ctrl + c on Win / Linux + if ( + e.toKeybinding().equals(new SimpleKeybinding(true, false, false, false, KeyCode.KEY_C)) || + (platform.isMacintosh && e.toKeybinding().equals(new SimpleKeybinding(false, false, false, true, KeyCode.KEY_C))) + ) { + e.stopPropagation(); + return; + } + + if (!e.toKeybinding().isModifierKey()) { + this._editor.focus(); + } + })); + // Manage the acceptSuggestionsOnEnter context key let acceptSuggestionsOnEnter = SuggestContext.AcceptSuggestionsOnEnter.bindTo(_contextKeyService); let updateFromConfig = () => { diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 32b93cee18..35fec59c6b 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -10,14 +10,14 @@ import * as strings from 'vs/base/common/strings'; import { Event, Emitter } from 'vs/base/common/event'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, dispose, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; -import { addClass, append, $, hide, removeClass, show, toggleClass, getDomNodePagePosition, hasClass, addDisposableListener } from 'vs/base/browser/dom'; +import { addClass, append, $, hide, removeClass, show, toggleClass, getDomNodePagePosition, hasClass, addDisposableListener, addStandardDisposableListener } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IListEvent, IListRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; -import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import { Context as SuggestContext, CompletionItem } from './suggest'; import { CompletionModel } from './completionModel'; import { alert } from 'vs/base/browser/ui/aria/aria'; @@ -39,6 +39,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { FileKind } from 'vs/platform/files/common/files'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { flatten } from 'vs/base/common/arrays'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; const expandSuggestionDocsByDefault = false; @@ -159,7 +160,6 @@ class Renderer implements IListRenderer data.icon.className = 'icon ' + completionKindToCssClass(suggestion.kind); data.colorspan.style.backgroundColor = ''; - const labelOptions: IIconLabelValueOptions = { labelEscapeNewLines: true, matches: createMatches(element.score) @@ -337,6 +337,8 @@ class SuggestionDetails { } this.el.style.height = this.header.offsetHeight + this.docs.offsetHeight + (this.borderWidth * 2) + 'px'; + this.el.style.userSelect = 'text'; + this.el.tabIndex = -1; this.close.onmousedown = e => { e.preventDefault(); @@ -427,7 +429,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate(); - private onDidFocusEmitter = new Emitter(); - private onDidHideEmitter = new Emitter(); - private onDidShowEmitter = new Emitter(); + private readonly onDidSelectEmitter = new Emitter(); + private readonly onDidFocusEmitter = new Emitter(); + private readonly onDidHideEmitter = new Emitter(); + private readonly onDidShowEmitter = new Emitter(); readonly onDidSelect: Event = this.onDidSelectEmitter.event; readonly onDidFocus: Event = this.onDidFocusEmitter.event; @@ -472,6 +474,9 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate(); + public readonly onDetailsKeyDown: Event = this._onDetailsKeydown.event; + constructor( private readonly editor: ICodeEditor, @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -525,7 +530,6 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate this.onCursorSelectionChanged())); this.toDispose.add(this.editor.onDidChangeConfiguration(e => e.hasChanged(EditorOption.suggest) && applyIconStyle())); - this.suggestWidgetVisible = SuggestContext.Visible.bindTo(contextKeyService); this.suggestWidgetMultipleSuggestions = SuggestContext.MultipleSuggestions.bindTo(contextKeyService); @@ -533,6 +537,25 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate { + this._onDetailsKeydown.fire(e); + })); + + this.toDispose.add(this.editor.onMouseDown((e: IEditorMouseEvent) => this.onEditorMouseDown(e))); + } + + private onEditorMouseDown(mouseEvent: IEditorMouseEvent): void { + // Clicking inside details + if (this.details.element.contains(mouseEvent.target.element)) { + this.details.element.focus(); + } + // Clicking outside details and inside suggest + else { + if (this.element.contains(mouseEvent.target.element)) { + this.editor.focus(); + } + } } private onCursorSelectionChanged(): void { diff --git a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts index 75c89550be..eac7f0821f 100644 --- a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts @@ -30,6 +30,8 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { ISuggestMemoryService } from 'vs/editor/contrib/suggest/suggestMemory'; import { ITextModel } from 'vs/editor/common/model'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; export interface Ctor { new(): T; @@ -46,6 +48,7 @@ function createMockEditor(model: TextModel): TestCodeEditor { serviceCollection: new ServiceCollection( [ITelemetryService, NullTelemetryService], [IStorageService, new InMemoryStorageService()], + [IKeybindingService, new MockKeybindingService()], [ISuggestMemoryService, new class implements ISuggestMemoryService { _serviceBrand: undefined; memorize(): void { diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 340215e251..e0367f0d60 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -420,7 +420,7 @@ export class SimpleConfigurationService implements IConfigurationService { _serviceBrand: undefined; - private _onDidChangeConfiguration = new Emitter(); + private readonly _onDidChangeConfiguration = new Emitter(); public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; private readonly _configuration: Configuration; diff --git a/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts index b1ad7b1e2f..f0605f829a 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts @@ -15,19 +15,19 @@ import { IResourceInput } from 'vs/platform/editor/common/editor'; export class StandaloneCodeEditorServiceImpl extends CodeEditorServiceImpl { - public getActiveCodeEditor(): ICodeEditor | null { - return null; // not supported in the standalone case + public getActiveCodeEditor(): ICodeEditor | undefined { + return undefined; // not supported in the standalone case } - public openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + public openCodeEditor(input: IResourceInput, source: ICodeEditor | undefined, sideBySide?: boolean): Promise { if (!source) { - return Promise.resolve(null); + return Promise.resolve(undefined); } return Promise.resolve(this.doOpenEditor(source, input)); } - private doOpenEditor(editor: ICodeEditor, input: IResourceInput): ICodeEditor | null { + private doOpenEditor(editor: ICodeEditor, input: IResourceInput): ICodeEditor | undefined { const model = this.findModel(editor, input.resource); if (!model) { if (input.resource) { @@ -39,7 +39,7 @@ export class StandaloneCodeEditorServiceImpl extends CodeEditorServiceImpl { return editor; } } - return null; + return undefined; } const selection = (input.options ? input.options.selection : null); diff --git a/src/vs/editor/test/browser/editorTestServices.ts b/src/vs/editor/test/browser/editorTestServices.ts index a522e63b7e..0d6ede3a87 100644 --- a/src/vs/editor/test/browser/editorTestServices.ts +++ b/src/vs/editor/test/browser/editorTestServices.ts @@ -14,10 +14,10 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti export class TestCodeEditorService extends AbstractCodeEditorService { public lastInput?: IResourceInput; - public getActiveCodeEditor(): ICodeEditor | null { return null; } - public openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + public getActiveCodeEditor(): ICodeEditor | undefined { return undefined; } + public openCodeEditor(input: IResourceInput, _source: ICodeEditor | undefined, _sideBySide?: boolean): Promise { this.lastInput = input; - return Promise.resolve(null); + return Promise.resolve(undefined); } public registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string): void { } public removeDecorationType(key: string): void { } diff --git a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts index 40a1ca784f..c2147f5bec 100644 --- a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts +++ b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts @@ -15,12 +15,12 @@ import { TestTheme, TestThemeService } from 'vs/platform/theme/test/common/testT const themeServiceMock = new TestThemeService(); export class TestCodeEditorServiceImpl extends CodeEditorServiceImpl { - getActiveCodeEditor(): ICodeEditor | null { - return null; + getActiveCodeEditor(): ICodeEditor | undefined { + return undefined; } - openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { - return Promise.resolve(null); + openCodeEditor(input: IResourceInput, source: ICodeEditor | undefined, sideBySide?: boolean): Promise { + return Promise.resolve(undefined); } } diff --git a/src/vs/editor/test/browser/services/openerService.test.ts b/src/vs/editor/test/browser/services/openerService.test.ts index b1a2c5751a..c16eb4898b 100644 --- a/src/vs/editor/test/browser/services/openerService.test.ts +++ b/src/vs/editor/test/browser/services/openerService.test.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { OpenerService } from 'vs/editor/browser/services/openerService'; import { TestCodeEditorService } from 'vs/editor/test/browser/editorTestServices'; @@ -15,8 +16,8 @@ suite('OpenerService', function () { const commandService = new (class implements ICommandService { _serviceBrand: undefined; - onWillExecuteCommand = () => ({ dispose: () => { } }); - onDidExecuteCommand = () => ({ dispose: () => { } }); + onWillExecuteCommand = () => Disposable.None; + onDidExecuteCommand = () => Disposable.None; executeCommand(id: string, ...args: any[]): Promise { lastCommand = { id, args }; return Promise.resolve(undefined); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 8ab1ed624e..32714a6767 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2152,7 +2152,7 @@ declare namespace monaco.editor { * Set the selections for all the cursors of the editor. * Cursors will be removed or added, as necessary. */ - setSelections(selections: ISelection[]): void; + setSelections(selections: readonly ISelection[]): void; /** * Scroll vertically as necessary and reveal lines. */ diff --git a/src/vs/nls.d.ts b/src/vs/nls.d.ts index 9b5dbc8f98..f01b4dd183 100644 --- a/src/vs/nls.d.ts +++ b/src/vs/nls.d.ts @@ -12,7 +12,7 @@ export interface ILocalizeInfo { * Localize a message. * * `message` can contain `{n}` notation where it is replaced by the nth value in `...args` - * For example, `localize('hello {0}', name)` + * For example, `localize({ key: 'sayHello', comment: ['Welcomes user'] }, 'hello {0}', name)` */ export declare function localize(info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): string; @@ -20,6 +20,6 @@ export declare function localize(info: ILocalizeInfo, message: string, ...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)` + * For example, `localize('sayHello', '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 cca94a72a4..4ab0a0a55a 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -87,6 +87,7 @@ export const enum MenuId { ProblemsPanelContext, SCMChangeContext, SCMResourceContext, + SCMResourceFolderContext, SCMResourceGroupContext, SCMSourceControl, SCMTitle, diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index 47c5305687..465dba5e88 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { TypeConstraint, validateConstraints } from 'vs/base/common/types'; import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; @@ -136,8 +136,8 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR export const NullCommandService: ICommandService = { _serviceBrand: undefined, - onWillExecuteCommand: () => ({ dispose: () => { } }), - onDidExecuteCommand: () => ({ dispose: () => { } }), + onWillExecuteCommand: () => Disposable.None, + onDidExecuteCommand: () => Disposable.None, executeCommand() { return Promise.resolve(undefined); } diff --git a/src/vs/platform/configuration/common/configurationRegistry.ts b/src/vs/platform/configuration/common/configurationRegistry.ts index 056f4a7956..2c2db932aa 100644 --- a/src/vs/platform/configuration/common/configurationRegistry.ts +++ b/src/vs/platform/configuration/common/configurationRegistry.ts @@ -170,7 +170,7 @@ class ConfigurationRegistry implements IConfigurationRegistry { properties: {} }; this.configurationContributors = [this.defaultOverridesConfigurationNode]; - this.editorConfigurationSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting', allowsTrailingCommas: true, allowComments: true }; + this.editorConfigurationSchema = { properties: {}, patternProperties: {}, additionalProperties: false, errorMessage: 'Unknown editor configuration setting', allowTrailingCommas: true, allowComments: true }; this.configurationProperties = {}; this.excludedConfigurationProperties = {}; this.computeOverridePropertyPattern(); diff --git a/src/vs/platform/debug/common/extensionHostDebugIpc.ts b/src/vs/platform/debug/common/extensionHostDebugIpc.ts index 11df86a82e..fe7c358e99 100644 --- a/src/vs/platform/debug/common/extensionHostDebugIpc.ts +++ b/src/vs/platform/debug/common/extensionHostDebugIpc.ts @@ -15,11 +15,11 @@ export class ExtensionHostDebugBroadcastChannel implements IServerChan static readonly ChannelName = 'extensionhostdebugservice'; - private _onCloseEmitter = new Emitter(); - private _onReloadEmitter = new Emitter(); - private _onTerminateEmitter = new Emitter(); - private _onLogToEmitter = new Emitter(); - private _onAttachEmitter = new Emitter(); + private readonly _onCloseEmitter = new Emitter(); + private readonly _onReloadEmitter = new Emitter(); + private readonly _onTerminateEmitter = new Emitter(); + private readonly _onLogToEmitter = new Emitter(); + private readonly _onAttachEmitter = new Emitter(); call(ctx: TContext, command: string, arg?: any): Promise { switch (command) { diff --git a/src/vs/platform/driver/browser/baseDriver.ts b/src/vs/platform/driver/browser/baseDriver.ts index 0f4561468d..5f4a3b4634 100644 --- a/src/vs/platform/driver/browser/baseDriver.ts +++ b/src/vs/platform/driver/browser/baseDriver.ts @@ -43,8 +43,6 @@ function serializeElement(element: Element, recursive: boolean): IElement { export abstract class BaseWindowDriver implements IWindowDriver { - constructor() { } - abstract click(selector: string, xoffset?: number, yoffset?: number): Promise; abstract doubleClick(selector: string): Promise; diff --git a/src/vs/platform/driver/browser/driver.ts b/src/vs/platform/driver/browser/driver.ts index 613655393e..6dddd97d92 100644 --- a/src/vs/platform/driver/browser/driver.ts +++ b/src/vs/platform/driver/browser/driver.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver'; class BrowserWindowDriver extends BaseWindowDriver { @@ -21,7 +21,5 @@ class BrowserWindowDriver extends BaseWindowDriver { export async function registerWindowDriver(): Promise { (window).driver = new BrowserWindowDriver(); - return toDisposable(() => { - return { dispose: () => { } }; - }); + return Disposable.None; } diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index 262692fef9..319af731f3 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -11,11 +11,12 @@ import * as electron from 'electron'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { timeout } from 'vs/base/common/async'; import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver'; +import { IElectronService } from 'vs/platform/electron/node/electron'; class WindowDriver extends BaseWindowDriver { constructor( - @IWindowService private readonly windowService: IWindowService + @IElectronService private readonly electronService: IElectronService ) { super(); } @@ -41,7 +42,7 @@ class WindowDriver extends BaseWindowDriver { } async openDevTools(): Promise { - await this.windowService.openDevTools({ mode: 'detach' }); + await this.electronService.openDevTools({ mode: 'detach' }); } } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 5e1a5efa45..86523369c3 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -18,6 +18,7 @@ import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { timeout } from 'vs/base/common/async'; import { IDriver, IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; +import { NativeImage } from 'electron'; function isSilentKeyCode(keyCode: KeyCode) { return keyCode < KeyCode.KEY_0; @@ -29,7 +30,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { private registeredWindowIds = new Set(); private reloadingWindowIds = new Set(); - private onDidReloadingChange = new Emitter(); + private readonly onDidReloadingChange = new Emitter(); constructor( private windowServer: IPCServer, @@ -62,7 +63,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { throw new Error('Invalid window'); } const webContents = window.win.webContents; - const image = await new Promise(c => webContents.capturePage(c)); + const image = await new Promise(c => webContents.capturePage(c)); return image.toPNG().toString('base64'); } diff --git a/src/vs/platform/electron/electron-browser/electronService.ts b/src/vs/platform/electron/electron-browser/electronService.ts index 76c09baf92..cc14ba22aa 100644 --- a/src/vs/platform/electron/electron-browser/electronService.ts +++ b/src/vs/platform/electron/electron-browser/electronService.ts @@ -6,12 +6,16 @@ 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'; +import { IWindowService } from 'vs/platform/windows/common/windows'; export class ElectronService { _serviceBrand: undefined; - constructor(@IMainProcessService mainProcessService: IMainProcessService) { - return createSimpleChannelProxy(mainProcessService.getChannel('electron')); + constructor( + @IMainProcessService mainProcessService: IMainProcessService, + @IWindowService windowService: IWindowService + ) { + return createSimpleChannelProxy(mainProcessService.getChannel('electron'), windowService.windowId); } } diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 349a7de184..0e1305f4c1 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -3,45 +3,246 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; +import { MessageBoxOptions, MessageBoxReturnValue, shell, OpenDevToolsOptions, SaveDialogOptions, SaveDialogReturnValue, OpenDialogOptions, OpenDialogReturnValue, CrashReporterStartOptions, crashReporter, Menu } from 'electron'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { OpenContext, INativeOpenDialogOptions } from 'vs/platform/windows/common/windows'; +import { isMacintosh } from 'vs/base/common/platform'; import { IElectronService } from 'vs/platform/electron/node/electron'; -import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; -import { MessageBoxOptions, MessageBoxReturnValue, shell } from 'electron'; +import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; +import { AddContextToFunctions } from 'vs/platform/ipc/node/simpleIpcProxy'; -export class ElectronMainService implements IElectronService { +export class ElectronMainService implements AddContextToFunctions { _serviceBrand: undefined; constructor( - @IWindowsMainService private readonly windowsMainService: IWindowsMainService + @IWindowsMainService private readonly windowsMainService: IWindowsMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService ) { } //#region Window - private get window(): ICodeWindow | undefined { - return this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); + async windowCount(windowId: number): Promise { + return this.windowsMainService.getWindowCount(); } - async windowCount(): Promise { - return this.windowsMainService.getWindowCount(); + async openEmptyWindow(windowId: number, options?: { reuse?: boolean, remoteAuthority?: string }): Promise { + this.windowsMainService.openEmptyWindow(OpenContext.API, options); + } + + async toggleFullScreen(windowId: number): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + window.toggleFullScreen(); + } + } + + async handleTitleDoubleClick(windowId: number): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + window.handleTitleDoubleClick(); + } + } + + async isMaximized(windowId: number): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + return window.win.isMaximized(); + } + + return false; + } + + async maximizeWindow(windowId: number): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + window.win.maximize(); + } + } + + async unmaximizeWindow(windowId: number): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + window.win.unmaximize(); + } + } + + async minimizeWindow(windowId: number): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + window.win.minimize(); + } } //#endregion - //#region Other + //#region Dialog - async showMessageBox(options: MessageBoxOptions): Promise { - const result = await this.windowsMainService.showMessageBox(options, this.window); - - return { - response: result.button, - checkboxChecked: !!result.checkboxChecked - }; + async showMessageBox(windowId: number, options: MessageBoxOptions): Promise { + return this.windowsMainService.showMessageBox(options, this.windowsMainService.getWindowById(windowId)); } - async showItemInFolder(path: string): Promise { + async showSaveDialog(windowId: number, options: SaveDialogOptions): Promise { + return this.windowsMainService.showSaveDialog(options, this.windowsMainService.getWindowById(windowId)); + } + + async showOpenDialog(windowId: number, options: OpenDialogOptions): Promise { + return this.windowsMainService.showOpenDialog(options, this.windowsMainService.getWindowById(windowId)); + } + + async pickFileFolderAndOpen(windowId: number, options: INativeOpenDialogOptions): Promise { + return this.windowsMainService.pickFileFolderAndOpen(options, this.windowsMainService.getWindowById(windowId)); + } + + async pickFileAndOpen(windowId: number, options: INativeOpenDialogOptions): Promise { + return this.windowsMainService.pickFileAndOpen(options, this.windowsMainService.getWindowById(windowId)); + } + + async pickFolderAndOpen(windowId: number, options: INativeOpenDialogOptions): Promise { + return this.windowsMainService.pickFolderAndOpen(options, this.windowsMainService.getWindowById(windowId)); + } + + async pickWorkspaceAndOpen(windowId: number, options: INativeOpenDialogOptions): Promise { + return this.windowsMainService.pickWorkspaceAndOpen(options, this.windowsMainService.getWindowById(windowId)); + } + + //#endregion + + //#region OS + + async showItemInFolder(windowId: number, path: string): Promise { shell.showItemInFolder(path); } + async setRepresentedFilename(windowId: number, path: string): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + window.setRepresentedFilename(path); + } + } + + async setDocumentEdited(windowId: number, edited: boolean): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + window.win.setDocumentEdited(edited); + } + } + + async openExternal(windowId: number, url: string): Promise { + return this.windowsMainService.openExternal(url); + } + + async updateTouchBar(windowId: number, items: ISerializableCommandAction[][]): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + window.updateTouchBar(items); + } + } + + //#endregion + + //#region macOS Touchbar + + async newWindowTab(): Promise { + this.windowsMainService.openNewTabbedWindow(OpenContext.API); + } + + async showPreviousWindowTab(): Promise { + Menu.sendActionToFirstResponder('selectPreviousTab:'); + } + + async showNextWindowTab(): Promise { + Menu.sendActionToFirstResponder('selectNextTab:'); + } + + async moveWindowTabToNewWindow(): Promise { + Menu.sendActionToFirstResponder('moveTabToNewWindow:'); + } + + async mergeAllWindowTabs(): Promise { + Menu.sendActionToFirstResponder('mergeAllWindows:'); + } + + async toggleWindowTabsBar(): Promise { + Menu.sendActionToFirstResponder('toggleTabBar:'); + } + + //#endregion + + //#region Lifecycle + + async relaunch(windowId: number, options?: { addArgs?: string[], removeArgs?: string[] }): Promise { + return this.lifecycleMainService.relaunch(options); + } + + async reload(windowId: number): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + return this.windowsMainService.reload(window); + } + } + + async closeWorkpsace(windowId: number): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + return this.windowsMainService.closeWorkspace(window); + } + } + + async closeWindow(windowId: number): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + return window.win.close(); + } + } + + async quit(windowId: number): Promise { + return this.windowsMainService.quit(); + } + + //#endregion + + //#region Connectivity + + async resolveProxy(windowId: number, url: string): Promise { + return new Promise(resolve => { + const window = this.windowsMainService.getWindowById(windowId); + if (window && window.win && window.win.webContents && window.win.webContents.session) { + window.win.webContents.session.resolveProxy(url, proxy => resolve(proxy)); + } else { + resolve(); + } + }); + } + + //#endregion + + //#region Development + + async openDevTools(windowId: number, options?: OpenDevToolsOptions): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + window.win.webContents.openDevTools(options); + } + } + + async toggleDevTools(windowId: number): Promise { + const window = this.windowsMainService.getWindowById(windowId); + if (window) { + const contents = window.win.webContents; + if (isMacintosh && window.hasHiddenTitleBarStyle() && !window.isFullScreen() && !contents.isDevToolsOpened()) { + contents.openDevTools({ mode: 'undocked' }); // due to https://github.com/electron/electron/issues/3647 + } else { + contents.toggleDevTools(); + } + } + } + + async startCrashReporter(windowId: number, options: CrashReporterStartOptions): Promise { + crashReporter.start(options); + } + //#endregion } diff --git a/src/vs/platform/electron/node/electron.ts b/src/vs/platform/electron/node/electron.ts index 3de54652d5..9f8b41184b 100644 --- a/src/vs/platform/electron/node/electron.ts +++ b/src/vs/platform/electron/node/electron.ts @@ -3,8 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MessageBoxOptions, MessageBoxReturnValue } from 'electron'; +import { MessageBoxOptions, MessageBoxReturnValue, OpenDevToolsOptions, SaveDialogOptions, OpenDialogOptions, OpenDialogReturnValue, SaveDialogReturnValue, CrashReporterStartOptions } from 'electron'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { INativeOpenDialogOptions } from 'vs/platform/windows/common/windows'; +import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; export const IElectronService = createDecorator('electronService'); @@ -14,10 +16,52 @@ export interface IElectronService { // Window windowCount(): Promise; + openEmptyWindow(options?: { reuse?: boolean, remoteAuthority?: string }): Promise; + toggleFullScreen(): Promise; + handleTitleDoubleClick(): Promise; + + isMaximized(): Promise; + maximizeWindow(): Promise; + unmaximizeWindow(): Promise; + minimizeWindow(): Promise; // Dialogs showMessageBox(options: MessageBoxOptions): Promise; + showSaveDialog(options: SaveDialogOptions): Promise; + showOpenDialog(options: OpenDialogOptions): Promise; + + pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise; + pickFileAndOpen(options: INativeOpenDialogOptions): Promise; + pickFolderAndOpen(options: INativeOpenDialogOptions): Promise; + pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise; // OS showItemInFolder(path: string): Promise; + setRepresentedFilename(path: string): Promise; + setDocumentEdited(edited: boolean): Promise; + openExternal(url: string): Promise; + updateTouchBar(items: ISerializableCommandAction[][]): Promise; + + // macOS Touchbar + newWindowTab(): Promise; + showPreviousWindowTab(): Promise; + showNextWindowTab(): Promise; + moveWindowTabToNewWindow(): Promise; + mergeAllWindowTabs(): Promise; + toggleWindowTabsBar(): Promise; + + // Lifecycle + relaunch(options?: { addArgs?: string[], removeArgs?: string[] }): Promise; + reload(): Promise; + closeWorkpsace(): Promise; + closeWindow(): Promise; + quit(): Promise; + + // Development + openDevTools(options?: OpenDevToolsOptions): Promise; + toggleDevTools(): Promise; + startCrashReporter(options: CrashReporterStartOptions): Promise; + + // Connectivity + resolveProxy(url: string): Promise; } diff --git a/src/vs/platform/environment/node/argvHelper.ts b/src/vs/platform/environment/node/argvHelper.ts index 0c0581e803..c215d1b996 100644 --- a/src/vs/platform/environment/node/argvHelper.ts +++ b/src/vs/platform/environment/node/argvHelper.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { firstIndex } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { ParsedArgs } from '../common/environment'; -import { MIN_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/common/files'; +import { MIN_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/node/files'; import { parseArgs, ErrorReporter, OPTIONS } from 'vs/platform/environment/node/argv'; function parseAndValidate(cmdLineArgs: string[], reportWarnings: boolean): ParsedArgs { diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 12610f14af..92c34abf7b 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -340,7 +340,7 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller publisher: galleryExtension.publisher.publisherName, publisherDisplayName: galleryExtension.publisher.displayName, description: galleryExtension.shortDescription || '', - installCount: getStatistic(galleryExtension.statistics, 'install') + getStatistic(galleryExtension.statistics, 'updateCount'), + installCount: getStatistic(galleryExtension.statistics, 'install'), rating: getStatistic(galleryExtension.statistics, 'averagerating'), ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'), assets, diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 9691a29134..48a7480462 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -754,9 +754,6 @@ export enum FileKind { ROOT_FOLDER } -export const MIN_MAX_MEMORY_SIZE_MB = 2048; -export const FALLBACK_MAX_MEMORY_SIZE_MB = 4096; - /** * A hint to disable etag checking for reading/writing. */ diff --git a/src/vs/workbench/contrib/scm/browser/scmUtil.ts b/src/vs/platform/files/node/files.ts similarity index 59% rename from src/vs/workbench/contrib/scm/browser/scmUtil.ts rename to src/vs/platform/files/node/files.ts index 121a06aa91..12af004c29 100644 --- a/src/vs/workbench/contrib/scm/browser/scmUtil.ts +++ b/src/vs/platform/files/node/files.ts @@ -3,8 +3,5 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ISCMResourceGroup, ISCMResource } from 'vs/workbench/contrib/scm/common/scm'; - -export function isSCMResource(element: ISCMResourceGroup | ISCMResource): element is ISCMResource { - return !!(element as ISCMResource).sourceUri; -} \ No newline at end of file +export const MIN_MAX_MEMORY_SIZE_MB = 2048; +export const FALLBACK_MAX_MEMORY_SIZE_MB = 4096; diff --git a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts index 5b74e12e08..38f96bddec 100644 --- a/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/platform/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -39,10 +39,10 @@ export class NsfwWatcherService implements IWatcherService { private _verboseLogging: boolean | undefined; private enospcErrorLogged: boolean | undefined; - private _onWatchEvent = new Emitter(); + private readonly _onWatchEvent = new Emitter(); readonly onWatchEvent = this._onWatchEvent.event; - private _onLogMessage = new Emitter(); + private readonly _onLogMessage = new Emitter(); readonly onLogMessage: Event = this._onLogMessage.event; watch(options: IWatcherOptions): Event { diff --git a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts index 43115e0a63..5cc4cca7e9 100644 --- a/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts +++ b/src/vs/platform/files/node/watcher/unix/chokidarWatcherService.ts @@ -43,10 +43,10 @@ export class ChokidarWatcherService implements IWatcherService { private spamWarningLogged: boolean | undefined; private enospcErrorLogged: boolean | undefined; - private _onWatchEvent = new Emitter(); + private readonly _onWatchEvent = new Emitter(); readonly onWatchEvent = this._onWatchEvent.event; - private _onLogMessage = new Emitter(); + private readonly _onLogMessage = new Emitter(); readonly onLogMessage: Event = this._onLogMessage.event; public watch(options: IWatcherOptions): Event { diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index 3fba419c56..ff833d726a 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as arrays from 'vs/base/common/arrays'; import { IStateService } from 'vs/platform/state/common/state'; -import { app } from 'electron'; +import { app, JumpListCategory } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; import { getBaseLabel, getPathLabel } from 'vs/base/common/labels'; import { IPath } from 'vs/platform/windows/common/windows'; @@ -59,7 +59,7 @@ export class HistoryMainService implements IHistoryMainService { _serviceBrand: undefined; - private _onRecentlyOpenedChange = new Emitter(); + private readonly _onRecentlyOpenedChange = new Emitter(); readonly onRecentlyOpenedChange: CommonEvent = this._onRecentlyOpenedChange.event; private macOSRecentDocumentsUpdater: ThrottledDelayer; @@ -301,7 +301,7 @@ export class HistoryMainService implements IHistoryMainService { return; // only on windows } - const jumpList: Electron.JumpListCategory[] = []; + const jumpList: JumpListCategory[] = []; // Tasks jumpList.push({ diff --git a/src/vs/platform/ipc/electron-browser/sharedProcessService.ts b/src/vs/platform/ipc/electron-browser/sharedProcessService.ts index 9a0c78cba8..8045c1573b 100644 --- a/src/vs/platform/ipc/electron-browser/sharedProcessService.ts +++ b/src/vs/platform/ipc/electron-browser/sharedProcessService.ts @@ -6,9 +6,10 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Client } from 'vs/base/parts/ipc/common/ipc.net'; import { connect } from 'vs/base/parts/ipc/node/ipc.net'; -import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; export const ISharedProcessService = createDecorator('sharedProcessService'); @@ -17,8 +18,10 @@ export interface ISharedProcessService { _serviceBrand: undefined; getChannel(channelName: string): IChannel; - registerChannel(channelName: string, channel: IServerChannel): void; + + whenSharedProcessReady(): Promise; + toggleSharedProcessWindow(): Promise; } export class SharedProcessService implements ISharedProcessService { @@ -26,16 +29,23 @@ export class SharedProcessService implements ISharedProcessService { _serviceBrand: undefined; private withSharedProcessConnection: Promise>; + private sharedProcessMainChannel: IChannel; constructor( - @IWindowsService windowsService: IWindowsService, + @IMainProcessService mainProcessService: IMainProcessService, @IWindowService windowService: IWindowService, @IEnvironmentService environmentService: IEnvironmentService ) { - this.withSharedProcessConnection = windowsService.whenSharedProcessReady() + this.sharedProcessMainChannel = mainProcessService.getChannel('sharedProcess'); + + this.withSharedProcessConnection = this.whenSharedProcessReady() .then(() => connect(environmentService.sharedIPCHandle, `window:${windowService.windowId}`)); } + whenSharedProcessReady(): Promise { + return this.sharedProcessMainChannel.call('whenSharedProcessReady'); + } + getChannel(channelName: string): IChannel { return getDelayedChannel(this.withSharedProcessConnection.then(connection => connection.getChannel(channelName))); } @@ -43,4 +53,8 @@ export class SharedProcessService implements ISharedProcessService { registerChannel(channelName: string, channel: IServerChannel): void { this.withSharedProcessConnection.then(connection => connection.registerChannel(channelName, channel)); } + + toggleSharedProcessWindow(): Promise { + return this.sharedProcessMainChannel.call('toggleSharedProcessWindow'); + } } diff --git a/src/vs/platform/ipc/electron-main/sharedProcessMainService.ts b/src/vs/platform/ipc/electron-main/sharedProcessMainService.ts new file mode 100644 index 0000000000..4f3e12bdd8 --- /dev/null +++ b/src/vs/platform/ipc/electron-main/sharedProcessMainService.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 { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ISharedProcess } from 'vs/platform/windows/electron-main/windows'; + +export const ISharedProcessMainService = createDecorator('sharedProcessMainService'); + +export interface ISharedProcessMainService { + + _serviceBrand: undefined; + + whenSharedProcessReady(): Promise; + toggleSharedProcessWindow(): Promise; +} +export class SharedProcessMainService implements ISharedProcessMainService { + + _serviceBrand: undefined; + + constructor(private sharedProcess: ISharedProcess) { } + + whenSharedProcessReady(): Promise { + return this.sharedProcess.whenReady(); + } + + async toggleSharedProcessWindow(): Promise { + return this.sharedProcess.toggle(); + } +} diff --git a/src/vs/platform/ipc/node/simpleIpcProxy.ts b/src/vs/platform/ipc/node/simpleIpcProxy.ts index a9e9c7d157..f8621595e5 100644 --- a/src/vs/platform/ipc/node/simpleIpcProxy.ts +++ b/src/vs/platform/ipc/node/simpleIpcProxy.ts @@ -11,6 +11,32 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; // for a very basic process <=> process communication over methods. // +export type AddContextToFunctions = { + // For every property: IF property is a FUNCTION ADD context as first parameter and original parameters afterwards with same return type, otherwise preserve as is + [K in keyof Target]: Target[K] extends (...args: any) => any ? (context: Context, ...args: Parameters) => ReturnType : Target[K] +}; + +interface ISimpleChannelProxyContext { + __$simpleIPCContextMarker: boolean; + proxyContext: unknown; +} + +function serializeContext(proxyContext?: unknown): ISimpleChannelProxyContext | undefined { + if (proxyContext) { + return { __$simpleIPCContextMarker: true, proxyContext }; + } + + return undefined; +} + +function deserializeContext(candidate?: ISimpleChannelProxyContext | undefined): unknown | undefined { + if (candidate && candidate.__$simpleIPCContextMarker === true) { + return candidate.proxyContext; + } + + return undefined; +} + export class SimpleServiceProxyChannel implements IServerChannel { private service: { [key: string]: unknown }; @@ -23,9 +49,16 @@ export class SimpleServiceProxyChannel implements IServerChannel { throw new Error(`Events are currently unsupported by SimpleServiceProxyChannel: ${event}`); } - call(_: unknown, command: string, args: any[]): Promise { + call(_: unknown, command: string, args?: any[]): Promise { const target = this.service[command]; if (typeof target === 'function') { + if (Array.isArray(args)) { + const context = deserializeContext(args[0]); + if (context) { + args[0] = context; + } + } + return target.apply(this.service, args); } @@ -33,12 +66,21 @@ export class SimpleServiceProxyChannel implements IServerChannel { } } -export function createSimpleChannelProxy(channel: IChannel): T { +export function createSimpleChannelProxy(channel: IChannel, context?: unknown): T { + const serializedContext = serializeContext(context); + return new Proxy({}, { get(_target, propKey, _receiver) { if (typeof propKey === 'string') { return function (...args: any[]) { - return channel.call(propKey, args); + let methodArgs: any[]; + if (serializedContext) { + methodArgs = [context, ...args]; + } else { + methodArgs = args; + } + + return channel.call(propKey, methodArgs); }; } diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 30a9909438..4747524b53 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -7,15 +7,14 @@ 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, dialog } from 'electron'; -import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchService'; +import { BrowserWindow, ipcMain, screen, dialog, IpcMainEvent, Display } from 'electron'; +import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; 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'; import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { IWindowState } from 'vs/platform/windows/electron-main/windows'; +import { IWindowState, IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; import { listProcesses } from 'vs/base/node/ps'; const DEFAULT_BACKGROUND_COLOR = '#1E1E1E'; @@ -34,13 +33,13 @@ export class IssueMainService implements IIssueService { @ILaunchMainService private readonly launchMainService: ILaunchMainService, @ILogService private readonly logService: ILogService, @IDiagnosticsService private readonly diagnosticsService: IDiagnosticsService, - @IWindowsService private readonly windowsService: IWindowsService + @IWindowsMainService private readonly windowsMainService: IWindowsMainService ) { this.registerListeners(); } private registerListeners(): void { - ipcMain.on('vscode:issueSystemInfoRequest', async (event: Electron.IpcMainEvent) => { + ipcMain.on('vscode:issueSystemInfoRequest', async (event: IpcMainEvent) => { Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) .then(result => { const [info, remoteData] = result; @@ -50,7 +49,7 @@ export class IssueMainService implements IIssueService { }); }); - ipcMain.on('vscode:listProcesses', async (event: Electron.IpcMainEvent) => { + ipcMain.on('vscode:listProcesses', async (event: IpcMainEvent) => { const processes = []; try { @@ -79,7 +78,7 @@ export class IssueMainService implements IIssueService { event.sender.send('vscode:listProcessesResponse', processes); }); - ipcMain.on('vscode:issueReporterClipboard', (event: Electron.IpcMainEvent) => { + ipcMain.on('vscode:issueReporterClipboard', (event: 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', @@ -97,7 +96,7 @@ export class IssueMainService implements IIssueService { } }); - ipcMain.on('vscode:issuePerformanceInfoRequest', (event: Electron.IpcMainEvent) => { + ipcMain.on('vscode:issuePerformanceInfoRequest', (event: IpcMainEvent) => { this.getPerformanceInfo().then(msg => { event.sender.send('vscode:issuePerformanceInfoResponse', msg); }); @@ -147,16 +146,16 @@ export class IssueMainService implements IIssueService { }); ipcMain.on('vscode:openExternal', (_: unknown, arg: string) => { - this.windowsService.openExternal(arg); + this.windowsMainService.openExternal(arg); }); - ipcMain.on('vscode:closeIssueReporter', (event: Electron.IpcMainEvent) => { + ipcMain.on('vscode:closeIssueReporter', (event: IpcMainEvent) => { if (this._issueWindow) { this._issueWindow.close(); } }); - ipcMain.on('windowsInfoRequest', (event: Electron.IpcMainEvent) => { + ipcMain.on('windowsInfoRequest', (event: IpcMainEvent) => { this.launchMainService.getMainProcessInfo().then(info => { event.sender.send('vscode:windowsInfoResponse', info.windows); }); @@ -277,7 +276,7 @@ export class IssueMainService implements IIssueService { private getWindowPosition(parentWindow: BrowserWindow, defaultWidth: number, defaultHeight: number): IWindowState { // We want the new window to open on the same display that the parent is in - let displayToUse: Electron.Display | undefined; + let displayToUse: Display | undefined; const displays = screen.getAllDisplays(); // Single Display diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index b1fadfe2e4..0b5fab8552 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -15,6 +15,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { INotification, INotificationService, IPromptChoice, IPromptOptions, NoOpNotification, IStatusMessageOptions } from 'vs/platform/notification/common/notification'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { Disposable } from 'vs/base/common/lifecycle'; function createContext(ctx: any) { return { @@ -119,8 +120,8 @@ suite('AbstractKeybindingService', () => { let commandService: ICommandService = { _serviceBrand: undefined, - onWillExecuteCommand: () => ({ dispose: () => { } }), - onDidExecuteCommand: () => ({ dispose: () => { } }), + onWillExecuteCommand: () => Disposable.None, + onDidExecuteCommand: () => Disposable.None, executeCommand: (commandId: string, ...args: any[]): Promise => { executeCommandCalls.push({ commandId: commandId, diff --git a/src/vs/platform/launch/electron-main/launchService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts similarity index 99% rename from src/vs/platform/launch/electron-main/launchService.ts rename to src/vs/platform/launch/electron-main/launchMainService.ts index b3ac21e37e..5e2d83172a 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchMainService.ts @@ -142,7 +142,7 @@ export class LaunchMainService implements ILaunchMainService { // Create a window if there is none if (this.windowsMainService.getWindowCount() === 0) { - const window = this.windowsMainService.openNewWindow(OpenContext.DESKTOP)[0]; + const window = this.windowsMainService.openEmptyWindow(OpenContext.DESKTOP)[0]; whenWindowReady = window.ready(); } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 3469c27f9c..06863060f5 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -25,7 +25,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { attachListStyler, computeStyles, defaultListStyles } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; -import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; +import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer, CompressibleObjectTree, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree'; import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree'; @@ -212,14 +212,11 @@ function toWorkbenchListOptions(options: IListOptions, configurationServic result.openController = openController; disposables.add(openController); - if (options.keyboardNavigationLabelProvider) { - const tlp = options.keyboardNavigationLabelProvider; - - result.keyboardNavigationLabelProvider = { - getKeyboardNavigationLabel(e) { return tlp.getKeyboardNavigationLabel(e); }, - mightProducePrintableCharacter(e) { return keybindingService.mightProducePrintableCharacter(e); } - }; - } + result.keyboardNavigationDelegate = { + mightProducePrintableCharacter(e) { + return keybindingService.mightProducePrintableCharacter(e); + } + }; return [result, disposables]; } @@ -807,6 +804,33 @@ export class WorkbenchObjectTree, TFilterData = void> } } +export class WorkbenchCompressibleObjectTree, TFilterData = void> extends CompressibleObjectTree { + + private internals: WorkbenchTreeInternals; + get contextKeyService(): IContextKeyService { return this.internals.contextKeyService; } + get useAltAsMultipleSelectionModifier(): boolean { return this.internals.useAltAsMultipleSelectionModifier; } + + constructor( + user: string, + container: HTMLElement, + delegate: IListVirtualDelegate, + renderers: ICompressibleTreeRenderer[], + options: ICompressibleObjectTreeOptions, + @IContextKeyService contextKeyService: IContextKeyService, + @IListService listService: IListService, + @IThemeService themeService: IThemeService, + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService keybindingService: IKeybindingService, + @IAccessibilityService accessibilityService: IAccessibilityService + ) { + const { options: treeOptions, getAutomaticKeyboardNavigation, disposable } = workbenchTreeDataPreamble(container, options, contextKeyService, themeService, configurationService, keybindingService, accessibilityService); + super(user, container, delegate, renderers, treeOptions); + this.disposables.push(disposable); + this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, contextKeyService, listService, themeService, configurationService, accessibilityService); + this.disposables.push(this.internals); + } +} + export class WorkbenchDataTree extends DataTree { private internals: WorkbenchTreeInternals; @@ -957,7 +981,7 @@ class WorkbenchTreeInternals { private disposables: IDisposable[] = []; constructor( - tree: WorkbenchObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, + tree: WorkbenchObjectTree | CompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, options: IAbstractTreeOptions | IAsyncDataTreeOptions, getAutomaticKeyboardNavigation: () => boolean | undefined, @IContextKeyService contextKeyService: IContextKeyService, diff --git a/src/vs/platform/markers/common/markerService.ts b/src/vs/platform/markers/common/markerService.ts index a574f68767..6afd9f0cf9 100644 --- a/src/vs/platform/markers/common/markerService.ts +++ b/src/vs/platform/markers/common/markerService.ts @@ -123,7 +123,7 @@ export class MarkerService implements IMarkerService { _serviceBrand: undefined; - private _onMarkerChanged = new Emitter(); + private readonly _onMarkerChanged = new Emitter(); private _onMarkerChangedEvent: Event = Event.debounce(this._onMarkerChanged.event, MarkerService._debouncer, 0); private _byResource: MapMap = Object.create(null); private _byOwner: MapMap = Object.create(null); diff --git a/src/vs/platform/menubar/electron-browser/menubarService.ts b/src/vs/platform/menubar/electron-browser/menubarService.ts index f305702a7e..b3b08b4be3 100644 --- a/src/vs/platform/menubar/electron-browser/menubarService.ts +++ b/src/vs/platform/menubar/electron-browser/menubarService.ts @@ -3,21 +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 { IMenubarService, IMenubarData } from 'vs/platform/menubar/node/menubar'; +import { IMenubarService } from 'vs/platform/menubar/node/menubar'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { createSimpleChannelProxy } from 'vs/platform/ipc/node/simpleIpcProxy'; -export class MenubarService implements IMenubarService { +export class MenubarService { _serviceBrand: undefined; - private channel: IChannel; - constructor(@IMainProcessService mainProcessService: IMainProcessService) { - this.channel = mainProcessService.getChannel('menubar'); - } - - updateMenubar(windowId: number, menuData: IMenubarData): Promise { - return this.channel.call('updateMenubar', [windowId, menuData]); + return createSimpleChannelProxy(mainProcessService.getChannel('menubar')); } } diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 0a7566ac1f..6c7235a9b5 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { isMacintosh, language } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { app, shell, Menu, MenuItem, BrowserWindow } from 'electron'; +import { app, shell, Menu, MenuItem, BrowserWindow, MenuItemConstructorOptions, WebContents, Event, KeyboardEvent } from 'electron'; import { OpenContext, IRunActionInWindowRequest, getTitleBarStyle, IRunKeybindingInWindowRequest, IURIToOpen } from 'vs/platform/windows/common/windows'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -26,7 +26,7 @@ import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } f const telemetryFrom = 'menu'; interface IMenuItemClickHandler { - inDevTools: (contents: Electron.WebContents) => void; + inDevTools: (contents: WebContents) => void; inNoWindow: () => void; } @@ -58,7 +58,7 @@ export class Menubar { private keybindings: { [commandId: string]: IMenubarKeybinding }; - private fallbackMenuHandlers: { [id: string]: (menuItem: MenuItem, browserWindow: BrowserWindow, event: Electron.Event) => void } = {}; + private fallbackMenuHandlers: { [id: string]: (menuItem: MenuItem, browserWindow: BrowserWindow, event: Event) => void } = {}; constructor( @IUpdateService private readonly updateService: IUpdateService, @@ -109,8 +109,8 @@ export class Menubar { private addFallbackHandlers(): void { // File Menu Items - this.fallbackMenuHandlers['workbench.action.files.newUntitledFile'] = () => this.windowsMainService.openNewWindow(OpenContext.MENU); - this.fallbackMenuHandlers['workbench.action.newWindow'] = () => this.windowsMainService.openNewWindow(OpenContext.MENU); + this.fallbackMenuHandlers['workbench.action.files.newUntitledFile'] = () => this.windowsMainService.openEmptyWindow(OpenContext.MENU); + this.fallbackMenuHandlers['workbench.action.newWindow'] = () => this.windowsMainService.openEmptyWindow(OpenContext.MENU); this.fallbackMenuHandlers['workbench.action.files.openFileFolder'] = (menuItem, win, event) => this.windowsMainService.pickFileFolderAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); this.fallbackMenuHandlers['workbench.action.openWorkspace'] = (menuItem, win, event) => this.windowsMainService.pickWorkspaceAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }); @@ -249,7 +249,7 @@ export class Menubar { const menubar = new Menu(); // Mac: Application - let macApplicationMenuItem: Electron.MenuItem; + let macApplicationMenuItem: MenuItem; if (isMacintosh) { const applicationMenu = new Menu(); // {{SQL CARBON EDIT}} - Use long name @@ -263,7 +263,7 @@ export class Menubar { this.appMenuInstalled = true; const dockMenu = new Menu(); - dockMenu.append(new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsMainService.openNewWindow(OpenContext.DOCK) })); + dockMenu.append(new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsMainService.openEmptyWindow(OpenContext.DOCK) })); app.dock.setMenu(dockMenu); } @@ -326,7 +326,7 @@ export class Menubar { // {{SQL CARBON EDIT}} - End // Mac: Window - let macWindowMenuItem: Electron.MenuItem | undefined; + let macWindowMenuItem: MenuItem | undefined; if (this.shouldDrawMenu('Window')) { const windowMenu = new Menu(); macWindowMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize('mWindow', "Window")), submenu: windowMenu, role: 'window' }); @@ -354,7 +354,7 @@ export class Menubar { this.menuGC.schedule(); } - private setMacApplicationMenu(macApplicationMenu: Electron.Menu): void { + private setMacApplicationMenu(macApplicationMenu: Menu): void { const about = this.createMenuItem(nls.localize('mAbout', "About {0}", product.nameLong), 'workbench.action.showAboutDialog'); const checkForUpdates = this.getUpdateMenuItems(); @@ -430,7 +430,7 @@ export class Menubar { } - private setMenu(menu: Electron.Menu, items: Array) { + private setMenu(menu: Menu, items: Array) { items.forEach((item: MenubarMenuItem) => { if (isMenubarMenuItemSeparator(item)) { menu.append(__separator__()); @@ -464,13 +464,13 @@ export class Menubar { }); } - private setMenuById(menu: Electron.Menu, menuId: string): void { + private setMenuById(menu: Menu, menuId: string): void { if (this.menubarMenus && this.menubarMenus[menuId]) { this.setMenu(menu, this.menubarMenus[menuId].items); } } - private insertCheckForUpdatesItems(menu: Electron.Menu) { + private insertCheckForUpdatesItems(menu: Menu) { const updateItems = this.getUpdateMenuItems(); if (updateItems.length) { updateItems.forEach(i => menu.append(i)); @@ -478,7 +478,7 @@ export class Menubar { } } - private createOpenRecentMenuItem(uri: URI, label: string, commandId: string): Electron.MenuItem { + private createOpenRecentMenuItem(uri: URI, label: string, commandId: string): MenuItem { const revivedUri = URI.revive(uri); const uriToOpen: IURIToOpen = (commandId === 'openRecentFile') ? { fileUri: revivedUri } : @@ -503,12 +503,12 @@ export class Menubar { }, false)); } - private isOptionClick(event: Electron.KeyboardEvent): boolean { + private isOptionClick(event: KeyboardEvent): boolean { return !!(event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey)))); } - private createRoleMenuItem(label: string, commandId: string, role: any): Electron.MenuItem { - const options: Electron.MenuItemConstructorOptions = { + private createRoleMenuItem(label: string, commandId: string, role: any): MenuItem { + const options: MenuItemConstructorOptions = { label: this.mnemonicLabel(label), role, enabled: true @@ -517,13 +517,13 @@ export class Menubar { return new MenuItem(this.withKeybinding(commandId, options)); } - private setMacWindowMenu(macWindowMenu: Electron.Menu): void { + private setMacWindowMenu(macWindowMenu: Menu): void { const minimize = new MenuItem({ label: nls.localize('mMinimize', "Minimize"), role: 'minimize', accelerator: 'Command+M', enabled: this.windowsMainService.getWindowCount() > 0 }); const zoom = new MenuItem({ label: nls.localize('mZoom', "Zoom"), role: 'zoom', enabled: this.windowsMainService.getWindowCount() > 0 }); const bringAllToFront = new MenuItem({ label: nls.localize('mBringToFront', "Bring All to Front"), role: 'front', enabled: this.windowsMainService.getWindowCount() > 0 }); const switchWindow = this.createMenuItem(nls.localize({ key: 'miSwitchWindow', comment: ['&& denotes a mnemonic'] }, "Switch &&Window..."), 'workbench.action.switchWindow'); - const nativeTabMenuItems: Electron.MenuItem[] = []; + const nativeTabMenuItems: MenuItem[] = []; if (this.currentEnableNativeTabs) { nativeTabMenuItems.push(__separator__()); @@ -545,7 +545,7 @@ export class Menubar { ].forEach(item => macWindowMenu.append(item)); } - private getUpdateMenuItems(): Electron.MenuItem[] { + private getUpdateMenuItems(): MenuItem[] { const state = this.updateService.state; switch (state.type) { @@ -597,7 +597,7 @@ export class Menubar { } } - private static _menuItemIsTriggeredViaKeybinding(event: Electron.KeyboardEvent, userSettingsLabel: string): boolean { + private static _menuItemIsTriggeredViaKeybinding(event: 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 @@ -624,11 +624,11 @@ export class Menubar { ); } - private createMenuItem(label: string, commandId: string | string[], enabled?: boolean, checked?: boolean): Electron.MenuItem; - private createMenuItem(label: string, click: () => void, enabled?: boolean, checked?: boolean): Electron.MenuItem; - private createMenuItem(arg1: string, arg2: any, arg3?: boolean, arg4?: boolean): Electron.MenuItem { + private createMenuItem(label: string, commandId: string | string[], enabled?: boolean, checked?: boolean): MenuItem; + private createMenuItem(label: string, click: () => void, enabled?: boolean, checked?: boolean): MenuItem; + private createMenuItem(arg1: string, arg2: any, arg3?: boolean, arg4?: boolean): MenuItem { const label = this.mnemonicLabel(arg1); - const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: Electron.MenuItem & IMenuItemWithKeybinding, win: Electron.BrowserWindow, event: Electron.Event) => { + const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: MenuItem & IMenuItemWithKeybinding, win: BrowserWindow, event: Event) => { const userSettingsLabel = menuItem ? menuItem.userSettingsLabel : null; let commandId = arg2; if (Array.isArray(arg2)) { @@ -644,7 +644,7 @@ export class Menubar { const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsMainService.getWindowCount() > 0; const checked = typeof arg4 === 'boolean' ? arg4 : false; - const options: Electron.MenuItemConstructorOptions = { + const options: MenuItemConstructorOptions = { label, click, enabled @@ -749,7 +749,7 @@ export class Menubar { } } - private withKeybinding(commandId: string | undefined, options: Electron.MenuItemConstructorOptions & IMenuItemWithKeybinding): Electron.MenuItemConstructorOptions { + private withKeybinding(commandId: string | undefined, options: MenuItemConstructorOptions & IMenuItemWithKeybinding): MenuItemConstructorOptions { const binding = typeof commandId === 'string' ? this.keybindings[commandId] : undefined; // Apply binding if there is one @@ -781,7 +781,7 @@ export class Menubar { return options; } - private likeAction(commandId: string, options: Electron.MenuItemConstructorOptions, setAccelerator = !options.accelerator): Electron.MenuItemConstructorOptions { + private likeAction(commandId: string, options: MenuItemConstructorOptions, setAccelerator = !options.accelerator): MenuItemConstructorOptions { if (setAccelerator) { options = this.withKeybinding(commandId, options); } @@ -811,6 +811,6 @@ export class Menubar { } } -function __separator__(): Electron.MenuItem { +function __separator__(): MenuItem { return new MenuItem({ type: 'separator' }); } diff --git a/src/vs/platform/menubar/node/menubarIpc.ts b/src/vs/platform/menubar/node/menubarIpc.ts deleted file mode 100644 index 947a7aaaf1..0000000000 --- a/src/vs/platform/menubar/node/menubarIpc.ts +++ /dev/null @@ -1,25 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IMenubarService } from 'vs/platform/menubar/node/menubar'; -import { Event } from 'vs/base/common/event'; - -export class MenubarChannel implements IServerChannel { - - constructor(private service: IMenubarService) { } - - listen(_: unknown, event: string): Event { - throw new Error(`Event not found: ${event}`); - } - - call(_: unknown, command: string, arg?: any): Promise { - switch (command) { - case 'updateMenubar': return this.service.updateMenubar(arg[0], arg[1]); - } - - throw new Error(`Call not found: ${command}`); - } -} \ 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 28cb4de161..db56979b44 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -9,9 +9,14 @@ import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; export const IOpenerService = createDecorator('openerService'); +type OpenToSideOptions = { readonly openToSide?: boolean }; +type OpenExternalOptions = { readonly openExternal?: boolean; readonly allowTunneling?: boolean }; + +export type OpenOptions = OpenToSideOptions & OpenExternalOptions; + export interface IOpener { - open(resource: URI, options?: { openToSide?: boolean }): Promise; - open(resource: URI, options?: { openExternal?: boolean }): Promise; + open(resource: URI, options?: OpenToSideOptions): Promise; + open(resource: URI, options?: OpenExternalOptions): Promise; } export interface IValidator { @@ -19,7 +24,7 @@ export interface IValidator { } export interface IExternalUriResolver { - resolveExternalUri(resource: URI): Promise; + resolveExternalUri(resource: URI, options?: OpenOptions): Promise<{ resolved: URI, dispose(): void } | undefined>; } export interface IOpenerService { @@ -48,8 +53,10 @@ export interface IOpenerService { * @param resource A resource * @return A promise that resolves when the opening is done. */ - open(resource: URI, options?: { openToSide?: boolean }): Promise; - open(resource: URI, options?: { openExternal?: boolean }): Promise; + open(resource: URI, options?: OpenToSideOptions): Promise; + open(resource: URI, options?: OpenExternalOptions): Promise; + + resolveExternalUri(resource: URI, options?: { readonly allowTunneling?: boolean }): Promise<{ resolved: URI, dispose(): void }>; } export const NullOpenerService: IOpenerService = Object.freeze({ @@ -58,4 +65,5 @@ export const NullOpenerService: IOpenerService = Object.freeze({ registerValidator() { return Disposable.None; }, registerExternalUriResolver() { return Disposable.None; }, open() { return Promise.resolve(false); }, + async resolveExternalUri(uri: URI) { return { resolved: uri, dispose() { } }; }, }); diff --git a/src/vs/platform/product/common/productService.ts b/src/vs/platform/product/common/productService.ts index 750b92e29d..33652def04 100644 --- a/src/vs/platform/product/common/productService.ts +++ b/src/vs/platform/product/common/productService.ts @@ -108,6 +108,7 @@ export interface IProductConfiguration { readonly msftInternalDomains?: string[]; readonly linkProtectionTrustedDomains?: readonly string[]; + readonly settingsSyncStoreUrl?: string; } diff --git a/src/vs/platform/remote/common/tunnel.ts b/src/vs/platform/remote/common/tunnel.ts index ae1c1207b4..b1ed3932e3 100644 --- a/src/vs/platform/remote/common/tunnel.ts +++ b/src/vs/platform/remote/common/tunnel.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; export const ITunnelService = createDecorator('tunnelService'); @@ -17,5 +18,21 @@ export interface RemoteTunnel { export interface ITunnelService { _serviceBrand: undefined; + readonly tunnels: Promise; + openTunnel(remotePort: number): Promise | undefined; } + +export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number } | undefined { + if (uri.scheme !== 'http' && uri.scheme !== 'https') { + return undefined; + } + const localhostMatch = /^(localhost|127\.0\.0\.1):(\d+)$/.exec(uri.authority); + if (!localhostMatch) { + return undefined; + } + return { + address: localhostMatch[1], + port: +localhostMatch[2], + }; +} diff --git a/src/vs/platform/remote/common/tunnelService.ts b/src/vs/platform/remote/common/tunnelService.ts index ba5aa13862..2292d97fdc 100644 --- a/src/vs/platform/remote/common/tunnelService.ts +++ b/src/vs/platform/remote/common/tunnelService.ts @@ -8,11 +8,9 @@ import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; export class NoOpTunnelService implements ITunnelService { _serviceBrand: undefined; - public constructor( - ) { - } + public readonly tunnels: Promise = Promise.resolve([]); - openTunnel(remotePort: number): Promise | undefined { + openTunnel(_remotePort: number): Promise | undefined { return undefined; } } diff --git a/src/vs/platform/request/browser/requestService.ts b/src/vs/platform/request/browser/requestService.ts index 7e814466c3..a61b65fde2 100644 --- a/src/vs/platform/request/browser/requestService.ts +++ b/src/vs/platform/request/browser/requestService.ts @@ -8,12 +8,13 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; import { request } from 'vs/base/parts/request/browser/request'; +import { IRequestService } from 'vs/platform/request/common/request'; /** * This service exposes the `request` API, while using the global * or configured proxy settings. */ -export class RequestService { +export class RequestService implements IRequestService { _serviceBrand: undefined; @@ -32,4 +33,8 @@ export class RequestService { return request(options, token); } + + async resolveProxy(url: string): Promise { + return undefined; // not implemented in the web + } } diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts index a00d6b6e12..2db4ec2032 100644 --- a/src/vs/platform/request/common/request.ts +++ b/src/vs/platform/request/common/request.ts @@ -17,6 +17,8 @@ export interface IRequestService { _serviceBrand: undefined; request(options: IRequestOptions, token: CancellationToken): Promise; + + resolveProxy(url: string): Promise; } function isSuccess(context: IRequestContext): boolean { diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index 08c1356daf..8fe3c942d1 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -141,7 +141,9 @@ export class RequestService extends Disposable implements IRequestService { e(canceled()); }); }); - } + async resolveProxy(url: string): Promise { + return undefined; // currently not implemented in node + } } diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 0bb269a4dd..515421dba0 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -179,6 +179,7 @@ export function getColorRegistry(): IColorRegistry { export const foreground = registerColor('foreground', { dark: '#CCCCCC', light: '#616161', hc: '#FFFFFF' }, nls.localize('foreground', "Overall foreground color. This color is only used if not overridden by a component.")); export const errorForeground = registerColor('errorForeground', { dark: '#F48771', light: '#A1260D', hc: '#F48771' }, nls.localize('errorForeground', "Overall foreground color for error messages. This color is only used if not overridden by a component.")); export const descriptionForeground = registerColor('descriptionForeground', { light: '#717171', dark: transparent(foreground, 0.7), hc: transparent(foreground, 0.7) }, nls.localize('descriptionForeground', "Foreground color for description text providing additional information, for example for a label.")); +export const iconForeground = registerColor('icon.foreground', { dark: '#C5C5C5', light: '#424242', hc: '#FFFFFF' }, nls.localize('iconForeground', "The default color for icons in the workbench.")); export const focusBorder = registerColor('focusBorder', { dark: Color.fromHex('#0E639C').transparent(0.8), light: Color.fromHex('#007ACC').transparent(0.4), hc: '#F38518' }, nls.localize('focusBorder', "Overall border color for focused elements. This color is only used if not overridden by a component.")); @@ -189,8 +190,6 @@ 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.")); @@ -341,6 +340,12 @@ export const editorHoverStatusBarBackground = registerColor('editorHoverWidget.s */ export const editorActiveLinkForeground = registerColor('editorLink.activeForeground', { dark: '#4E94CE', light: Color.blue, hc: Color.cyan }, nls.localize('activeLinkForeground', 'Color of active links.')); +/** + * Editor lighbulb icon colors + */ +export const editorLightBulbForeground = registerColor('editorLightBulb.foreground', { dark: '#FFCC00', light: '#DDB100', hc: '#FFCC00' }, nls.localize('editorLightBulbForeground', "The color used for the lightbulb actions icon.")); +export const editorLightBulbAutoFixForeground = registerColor('editorLightBulbAutoFix.foreground', { dark: '#75BEFF', light: '#007ACC', hc: '#75BEFF' }, nls.localize('editorLightBulbAutoFixForeground', "The color used for the lightbulb auto fix actions icon.")); + /** * Diff Editor Colors */ diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index cb20dad3ff..d76a7d634a 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -143,7 +143,7 @@ export function attachSelectBoxStyler(widget: IThemable, themeService: IThemeSer } as ISelectBoxStyleOverrides, widget); } -export function attachFindInputBoxStyler(widget: IThemable, themeService: IThemeService, style?: IInputBoxStyleOverrides): IDisposable { +export function attachFindReplaceInputBoxStyler(widget: IThemable, themeService: IThemeService, style?: IInputBoxStyleOverrides): IDisposable { return attachStyler(themeService, { inputBackground: (style && style.inputBackground) || inputBackground, inputForeground: (style && style.inputForeground) || inputForeground, diff --git a/src/vs/platform/update/electron-browser/updateService.ts b/src/vs/platform/update/electron-browser/updateService.ts index bc13e47d9c..f107d75cdd 100644 --- a/src/vs/platform/update/electron-browser/updateService.ts +++ b/src/vs/platform/update/electron-browser/updateService.ts @@ -12,7 +12,7 @@ export class UpdateService implements IUpdateService { _serviceBrand: undefined; - private _onStateChange = new Emitter(); + private readonly _onStateChange = new Emitter(); readonly onStateChange: Event = this._onStateChange.event; private _state: State = State.Uninitialized; diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 5cf87f42b7..aad0a32f06 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -30,7 +30,7 @@ export abstract class AbstractUpdateService implements IUpdateService { private _state: State = State.Uninitialized; - private _onStateChange = new Emitter(); + private readonly _onStateChange = new Emitter(); readonly onStateChange: Event = this._onStateChange.event; get state(): State { diff --git a/src/vs/platform/update/electron-main/updateService.snap.ts b/src/vs/platform/update/electron-main/updateService.snap.ts index 21d2ae4866..5ba11e146a 100644 --- a/src/vs/platform/update/electron-main/updateService.snap.ts +++ b/src/vs/platform/update/electron-main/updateService.snap.ts @@ -21,7 +21,7 @@ abstract class AbstractUpdateService2 implements IUpdateService { private _state: State = State.Uninitialized; - private _onStateChange = new Emitter(); + private readonly _onStateChange = new Emitter(); readonly onStateChange: Event = this._onStateChange.event; get state(): State { diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index 8933049e68..5c68c4dfb0 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { IURLService } from 'vs/platform/url/common/url'; import product from 'vs/platform/product/common/product'; -import { app } from 'electron'; +import { app, Event as ElectronEvent } from 'electron'; import { URI } from 'vs/base/common/uri'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; @@ -48,7 +48,7 @@ export class ElectronURLListener { } const onOpenElectronUrl = Event.map( - Event.fromNodeEventEmitter(app, 'open-url', (event: Electron.Event, url: string) => ({ event, url })), + Event.fromNodeEventEmitter(app, 'open-url', (event: ElectronEvent, url: string) => ({ event, url })), ({ event, url }) => { // always prevent default and return the url as string event.preventDefault(); diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts new file mode 100644 index 0000000000..423ab336d9 --- /dev/null +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -0,0 +1,329 @@ +/*--------------------------------------------------------------------------------------------- + * 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, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { Emitter, Event } from 'vs/base/common/event'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { URI } from 'vs/base/common/uri'; +import { joinPath } from 'vs/base/common/resources'; +import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { keys, values } from 'vs/base/common/map'; +import { startsWith } from 'vs/base/common/strings'; +import { IFileService } from 'vs/platform/files/common/files'; +import { Queue } from 'vs/base/common/async'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + +export interface ISyncPreviewResult { + readonly added: ISyncExtension[]; + readonly removed: ISyncExtension[]; + readonly updated: ISyncExtension[]; + readonly remote: ISyncExtension[] | null; +} + +export class ExtensionsSynchroniser extends Disposable implements ISynchroniser { + + private static EXTERNAL_USER_DATA_EXTENSIONS_KEY: string = 'extensions'; + + 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 _onDidChangeLocal: Emitter = this._register(new Emitter()); + readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; + + private readonly lastSyncExtensionsResource: URI; + private readonly replaceQueue: Queue; + + constructor( + @IEnvironmentService environmentService: IEnvironmentService, + @IFileService private readonly fileService: IFileService, + @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @ILogService private readonly logService: ILogService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IConfigurationService private readonly configurationService: IConfigurationService, + ) { + super(); + this.replaceQueue = this._register(new Queue()); + this.lastSyncExtensionsResource = joinPath(environmentService.userRoamingDataHome, '.lastSyncExtensions'); + this._register( + Event.debounce( + Event.any( + Event.filter(this.extensionManagementService.onDidInstallExtension, (e => !!e.gallery)), + Event.filter(this.extensionManagementService.onDidUninstallExtension, (e => !e.error))), + () => undefined, 500)(() => this._onDidChangeLocal.fire())); + } + + private setStatus(status: SyncStatus): void { + if (this._status !== status) { + this._status = status; + this._onDidChangStatus.fire(status); + } + } + + async sync(): Promise { + if (!this.configurationService.getValue('userConfiguration.syncExtensions')) { + return false; + } + + if (this.status !== SyncStatus.Idle) { + return false; + } + + this.setStatus(SyncStatus.Syncing); + + try { + await this.doSync(); + } catch (e) { + 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 extensions as there is a new remote version available. Synchronising again...'); + return this.sync(); + } + throw e; + } + + this.setStatus(SyncStatus.Idle); + return true; + } + + async getRemoteExtensions(): Promise { + const remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null); + return remoteData.content ? JSON.parse(remoteData.content) : []; + } + + removeExtension(identifier: IExtensionIdentifier): Promise { + return this.replaceQueue.queue(async () => { + const remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null); + const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : []; + const removedExtensions = remoteExtensions.filter(e => areSameExtensions(e.identifier, identifier)); + if (removedExtensions.length) { + for (const removedExtension of removedExtensions) { + remoteExtensions.splice(remoteExtensions.indexOf(removedExtension), 1); + } + await this.writeToRemote(remoteExtensions, remoteData.ref); + } + }); + } + + private async doSync(): Promise { + const lastSyncData = await this.getLastSyncUserData(); + let remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData); + + const lastSyncExtensions: ISyncExtension[] = lastSyncData ? JSON.parse(lastSyncData.content!) : null; + const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : null; + const localExtensions = await this.getLocalExtensions(); + + const { added, removed, updated, remote } = this.merge(localExtensions, remoteExtensions, lastSyncExtensions); + + // update local + await this.updateLocalExtensions(added, removed, updated); + + if (remote) { + // update remote + remoteData = await this.writeToRemote(remote, remoteData.ref); + } + + // update last sync + await this.updateLastSyncValue(remoteData); + } + + /** + * Merge Strategy: + * - If remote does not exist, merge with local (First time sync) + * - Overwrite local with remote changes. Removed, Added, Updated. + * - Update remote with those local extension which are newly added or updated or removed and untouched in remote. + */ + private merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null): { added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], remote: ISyncExtension[] | null } { + + // First time sync + if (!remoteExtensions) { + return { added: [], removed: [], updated: [], remote: localExtensions }; + } + + const uuids: Map = new Map(); + const addUUID = (identifier: IExtensionIdentifier) => { if (identifier.uuid) { uuids.set(identifier.id.toLowerCase(), identifier.uuid); } }; + localExtensions.forEach(({ identifier }) => addUUID(identifier)); + remoteExtensions.forEach(({ identifier }) => addUUID(identifier)); + if (lastSyncExtensions) { + lastSyncExtensions.forEach(({ identifier }) => addUUID(identifier)); + } + + const addExtensionToMap = (map: Map, extension: ISyncExtension) => { + const uuid = extension.identifier.uuid || uuids.get(extension.identifier.id.toLowerCase()); + const key = uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`; + map.set(key, extension); + return map; + }; + const localExtensionsMap = localExtensions.reduce(addExtensionToMap, new Map()); + const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map()); + const newRemoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map()); + const lastSyncExtensionsMap = lastSyncExtensions ? lastSyncExtensions.reduce(addExtensionToMap, new Map()) : null; + + const localToRemote = this.compare(localExtensionsMap, remoteExtensionsMap); + if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) { + // No changes found between local and remote. + return { added: [], removed: [], updated: [], remote: null }; + } + + const added: ISyncExtension[] = []; + const removed: IExtensionIdentifier[] = []; + const updated: ISyncExtension[] = []; + + const baseToLocal = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, localExtensionsMap) : { added: keys(localExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + const baseToRemote = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, remoteExtensionsMap) : { added: keys(remoteExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set()), removed: new Set(), updated: new Set() }; + + const massageSyncExtension = (extension: ISyncExtension, key: string): ISyncExtension => { + return { + identifier: { + id: extension.identifier.id, + uuid: startsWith(key, 'uuid:') ? key.substring('uuid:'.length) : undefined + }, + enabled: extension.enabled, + version: extension.version + }; + }; + + // Remotely removed extension. + for (const key of values(baseToRemote.removed)) { + const e = localExtensionsMap.get(key); + if (e) { + removed.push(e.identifier); + } + } + + // Remotely added extension + for (const key of values(baseToRemote.added)) { + // Got added in local + if (baseToLocal.added.has(key)) { + // Is different from local to remote + if (localToRemote.updated.has(key)) { + updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key)); + } + } else { + // Add to local + added.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key)); + } + } + + // Remotely updated extensions + for (const key of values(baseToRemote.updated)) { + // If updated in local + if (baseToLocal.updated.has(key)) { + // Is different from local to remote + if (localToRemote.updated.has(key)) { + // update it in local + updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key)); + } + } + } + + // Locally added extensions + for (const key of values(baseToLocal.added)) { + // Not there in remote + if (!baseToRemote.added.has(key)) { + newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key)); + } + } + + // Locally updated extensions + for (const key of values(baseToLocal.updated)) { + // If removed in remote + if (baseToRemote.removed.has(key)) { + continue; + } + + // If not updated in remote + if (!baseToRemote.updated.has(key)) { + newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key)); + } + } + + // Locally removed extensions + for (const key of values(baseToLocal.removed)) { + // If not updated in remote + if (!baseToRemote.updated.has(key)) { + newRemoteExtensionsMap.delete(key); + } + } + + const remoteChanges = this.compare(remoteExtensionsMap, newRemoteExtensionsMap); + const remote = remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0 ? values(newRemoteExtensionsMap) : null; + return { added, removed, updated, remote }; + } + + private compare(from: Map, to: Map): { added: Set, removed: Set, updated: Set } { + const fromKeys = keys(from); + const toKeys = 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 fromExtension = from.get(key)!; + const toExtension = to.get(key); + if (!toExtension + || fromExtension.enabled !== toExtension.enabled + || fromExtension.version !== toExtension.version + ) { + updated.add(key); + } + } + + return { added, removed, updated }; + } + + private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[]): Promise { + if (removed.length) { + const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); + const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r))); + await Promise.all(extensionsToRemove.map(e => this.extensionManagementService.uninstall(e))); + } + + if (added.length || updated.length) { + await Promise.all([...added, ...updated].map(async e => { + const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier, e.version); + if (extension) { + await this.extensionManagementService.installFromGallery(extension); + } + })); + } + } + + private async getLocalExtensions(): Promise { + const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); + return installedExtensions.map(({ identifier }) => ({ identifier, enabled: true })); + } + + private async getLastSyncUserData(): Promise { + try { + const content = await this.fileService.readFile(this.lastSyncExtensionsResource); + return JSON.parse(content.value.toString()); + } catch (error) { + return null; + } + } + + private async writeToRemote(extensions: ISyncExtension[], ref: string | null): Promise { + const content = JSON.stringify(extensions); + ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref); + return { content, ref }; + } + + private async updateLastSyncValue(remoteUserData: IUserData): Promise { + await this.fileService.writeFile(this.lastSyncExtensionsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); + } + +} diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index c9bb42130f..41b638d52a 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -15,6 +15,8 @@ import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; interface ISyncPreviewResult { readonly fileContent: IFileContent | null; @@ -47,6 +49,7 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @ISettingsMergeService private readonly settingsMergeService: ISettingsMergeService, @ILogService private readonly logService: ILogService, + @IConfigurationService private readonly configurationService: IConfigurationService, ) { super(); this.lastSyncSettingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncSettings.json'); @@ -135,7 +138,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { let { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged } = await this.syncPreviewResultPromise; if (hasRemoteChanged) { - const ref = await this.writeToRemote(content, remoteUserData.ref); + const remoteContent = remoteUserData.content ? await this.settingsMergeService.computeRemoteContent(content, remoteUserData.content, this.getIgnoredSettings()) : content; + const ref = await this.writeToRemote(remoteContent, remoteUserData.ref); remoteUserData = { ref, content }; } if (hasLocalChanged) { @@ -176,45 +180,40 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { 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 (remoteContent) { + const localContent: string = fileContent ? 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); + const result = await this.settingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings()); // 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 }; } } + return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; + } + + // First time sync to remote + if (fileContent) { + 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 }; } - this.logService.trace('Settings Sync: No changes.'); return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts }; } + private getIgnoredSettings(): IStringDictionary { + return this.configurationService.getValue>('userConfiguration.ignoreSettings'); + } + private async getLastSyncUserData(): Promise { try { const content = await this.fileService.readFile(this.lastSyncSettingsResource); diff --git a/src/vs/platform/userDataSync/common/settingsSyncIpc.ts b/src/vs/platform/userDataSync/common/settingsSyncIpc.ts index 32cc62c5d6..09f1615cd9 100644 --- a/src/vs/platform/userDataSync/common/settingsSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/settingsSyncIpc.ts @@ -6,6 +6,7 @@ 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'; +import { IStringDictionary } from 'vs/base/common/collections'; export class SettingsMergeChannel implements IServerChannel { @@ -17,7 +18,8 @@ export class SettingsMergeChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { - case 'merge': return this.service.merge(args[0], args[1], args[2]); + case 'merge': return this.service.merge(args[0], args[1], args[2], args[3]); + case 'computeRemoteContent': return this.service.computeRemoteContent(args[0], args[1], args[2]); } throw new Error('Invalid call'); } @@ -30,8 +32,12 @@ export class SettingsMergeChannelClient implements ISettingsMergeService { 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]); + merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: IStringDictionary): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { + return this.channel.call('merge', [localContent, remoteContent, baseContent, ignoredSettings]); + } + + computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: IStringDictionary): Promise { + return this.channel.call('computeRemoteContent', [localContent, remoteContent, ignoredSettings]); } } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index b0b751787e..31abae98e5 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -5,6 +5,54 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; +import { IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { localize } from 'vs/nls'; + +export function registerConfiguration() { + 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 + }, + 'userConfiguration.syncExtensions': { + type: 'boolean', + description: localize('userConfiguration.syncExtensions', "When enabled extensions are synchronised while synchronising user configuration."), + default: true, + scope: ConfigurationScope.APPLICATION, + }, + 'userConfiguration.ignoreSettings': { + 'type': 'object', + description: localize('userConfiguration.ignoreSettings', "Configure settings to be ignored while syncing"), + 'default': { + 'userConfiguration.enableSync': true, + 'userConfiguration.syncExtensions': true, + 'userConfiguration.ignoreSettings': true + }, + 'scope': ConfigurationScope.APPLICATION, + 'additionalProperties': { + 'anyOf': [ + { + 'type': 'boolean', + 'description': localize('ignoredSetting', "Id of the stting to be ignored. Set to true or false to enable or disable."), + } + ] + } + } + } + }); +} export interface IUserData { ref: string; @@ -40,12 +88,18 @@ export interface IUserDataSyncStoreService { write(key: string, content: string, ref: string | null): Promise; } -export enum SyncSource { +export interface ISyncExtension { + identifier: IExtensionIdentifier; + version?: string; + enabled: boolean; +} + +export const enum SyncSource { Settings = 1, Extensions } -export enum SyncStatus { +export const enum SyncStatus { Uninitialized = 'uninitialized', Idle = 'idle', Syncing = 'syncing', @@ -66,6 +120,9 @@ export const IUserDataSyncService = createDecorator('IUser export interface IUserDataSyncService extends ISynchroniser { _serviceBrand: any; readonly conflictsSource: SyncSource | null; + + getRemoteExtensions(): Promise; + removeExtension(identifier: IExtensionIdentifier): Promise; } export const ISettingsMergeService = createDecorator('ISettingsMergeService'); @@ -74,6 +131,10 @@ export interface ISettingsMergeService { _serviceBrand: undefined; - merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }>; + merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: IStringDictionary): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }>; + + computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: IStringDictionary): Promise; } + +export const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized); diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index c3a7b4b1eb..7acc2d9432 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -22,7 +22,10 @@ export class UserDataSyncChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { case 'sync': return this.service.sync(args[0]); + case '_getInitialStatus': return Promise.resolve(this.service.status); case 'getConflictsSource': return Promise.resolve(this.service.conflictsSource); + case 'getRemoteExtensions': return this.service.getRemoteExtensions(); + case 'removeExtension': return this.service.removeExtension(args[0]); } throw new Error('Invalid call'); } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index c58fce2fc6..62f97426e8 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -3,13 +3,15 @@ * 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 { IUserDataSyncService, SyncStatus, ISynchroniser, IUserDataSyncStoreService, SyncSource, ISyncExtension } 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'; +import { ExtensionsSynchroniser } from 'vs/platform/userDataSync/common/extensionsSync'; +import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -27,14 +29,17 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ private _conflictsSource: SyncSource | null = null; get conflictsSource(): SyncSource | null { return this._conflictsSource; } + private readonly settingsSynchroniser: SettingsSynchroniser; + private readonly extensionsSynchroniser: ExtensionsSynchroniser; + constructor( @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); - this.synchronisers = [ - this.instantiationService.createInstance(SettingsSynchroniser) - ]; + this.settingsSynchroniser = this._register(this.instantiationService.createInstance(SettingsSynchroniser)); + this.extensionsSynchroniser = this._register(this.instantiationService.createInstance(ExtensionsSynchroniser)); + this.synchronisers = [this.settingsSynchroniser, this.extensionsSynchroniser]; 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)); @@ -52,6 +57,14 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return true; } + getRemoteExtensions(): Promise { + return this.extensionsSynchroniser.getRemoteExtensions(); + } + + removeExtension(identifier: IExtensionIdentifier): Promise { + return this.extensionsSynchroniser.removeExtension(identifier); + } + private updateStatus(): void { this._conflictsSource = this.computeConflictsSource(); this.setStatus(this.computeStatus()); @@ -121,8 +134,7 @@ export class UserDataAutoSync extends Disposable { } private isSyncEnabled(): boolean { - const { user: userLocal } = this.configurationService.inspect('userConfiguration.enableSync'); - return userLocal === undefined || userLocal; + return this.configurationService.getValue('userConfiguration.enableSync'); } } diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index e09ddd3a7e..5c2b429a83 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -42,7 +42,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn return Promise.reject(new Error('No settings sync store url configured.')); } - const url = joinPath(URI.parse(this.productService.settingsSyncStoreUrl!), key).toString(); + const url = joinPath(URI.parse(this.productService.settingsSyncStoreUrl!), 'resource', key, 'latest').toString(); const headers: IHeaders = {}; if (oldValue) { headers['If-None-Match'] = oldValue.ref; @@ -68,7 +68,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn return Promise.reject(new Error('No settings sync store url configured.')); } - const url = joinPath(URI.parse(this.productService.settingsSyncStoreUrl!), key).toString(); + const url = joinPath(URI.parse(this.productService.settingsSyncStoreUrl!), 'resource', key).toString(); const headers: IHeaders = { 'Content-Type': 'text/plain' }; if (ref) { headers['If-Match'] = ref; diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index df28349112..3aa588f615 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -10,7 +10,6 @@ import { IProcessEnvironment, isMacintosh, isLinux, isWeb } from 'vs/base/common import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; -import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { ExportData } from 'vs/base/common/performance'; import { LogLevel } from 'vs/platform/log/common/log'; import { DisposableStore, Disposable } from 'vs/base/common/lifecycle'; @@ -21,7 +20,6 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async export const IWindowsService = createDecorator('windowsService'); export interface INativeOpenDialogOptions { - windowId?: number; forceNewWindow?: boolean; defaultPath?: string; @@ -35,16 +33,6 @@ export interface IEnterWorkspaceResult { backupPath?: string; } -export interface CrashReporterStartOptions { - companyName?: string; - submitURL: string; - productName?: string; - uploadToServer?: boolean; - ignoreSystemCrashHandler?: boolean; - extra?: any; - crashesDirectory?: string; -} - export interface OpenDialogOptions { title?: string; defaultPath?: string; @@ -83,15 +71,6 @@ export interface SaveDialogOptions { showsTagField?: boolean; } -export interface INewWindowOptions { - remoteAuthority?: string; - reuseWindow?: boolean; -} - -export interface IDevToolsOptions { - mode: 'right' | 'bottom' | 'undocked' | 'detach'; -} - export interface IWindowsService { _serviceBrand: undefined; @@ -103,77 +82,21 @@ export interface IWindowsService { readonly onWindowUnmaximize: Event; readonly onRecentlyOpenedChange: Event; - // Dialogs - pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise; - pickFileAndOpen(options: INativeOpenDialogOptions): Promise; - pickFolderAndOpen(options: INativeOpenDialogOptions): Promise; - pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise; - showMessageBox(windowId: number, options: MessageBoxOptions): Promise; - showSaveDialog(windowId: number, options: SaveDialogOptions): Promise; - showOpenDialog(windowId: number, options: OpenDialogOptions): Promise; - - reloadWindow(windowId: number, args?: ParsedArgs): Promise; - openDevTools(windowId: number, options?: IDevToolsOptions): Promise; - toggleDevTools(windowId: number): Promise; - closeWorkspace(windowId: number): Promise; - enterWorkspace(windowId: number, path: URI): Promise; - toggleFullScreen(windowId: number): Promise; - setRepresentedFilename(windowId: number, fileName: string): Promise; addRecentlyOpened(recents: IRecent[]): Promise; removeFromRecentlyOpened(paths: URI[]): Promise; clearRecentlyOpened(): Promise; getRecentlyOpened(windowId: number): Promise; focusWindow(windowId: number): Promise; - closeWindow(windowId: number): Promise; isFocused(windowId: number): Promise; - isMaximized(windowId: number): Promise; - maximizeWindow(windowId: number): Promise; - unmaximizeWindow(windowId: number): Promise; - minimizeWindow(windowId: number): Promise; - onWindowTitleDoubleClick(windowId: number): Promise; - setDocumentEdited(windowId: number, flag: boolean): Promise; - quit(): Promise; - relaunch(options: { addArgs?: string[], removeArgs?: string[] }): Promise; - - // macOS Native Tabs - newWindowTab(): Promise; - showPreviousWindowTab(): Promise; - showNextWindowTab(): Promise; - moveWindowTabToNewWindow(): Promise; - mergeAllWindowTabs(): Promise; - toggleWindowTabsBar(): Promise; - - // macOS TouchBar - updateTouchBar(windowId: number, items: ISerializableCommandAction[][]): Promise; - - // Shared process - whenSharedProcessReady(): Promise; - toggleSharedProcess(): Promise; // Global methods openWindow(windowId: number, uris: IURIToOpen[], options: IOpenSettings): Promise; - openNewWindow(options?: INewWindowOptions): Promise; - openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise; getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]>; getActiveWindowId(): Promise; - - // This needs to be handled from browser process to prevent - // foreground ordering issues on Windows - openExternal(url: string): Promise; - - // TODO: this is a bit backwards - startCrashReporter(config: CrashReporterStartOptions): Promise; - - resolveProxy(windowId: number, url: string): Promise; } export const IWindowService = createDecorator('windowService'); -export interface IMessageBoxResult { - button: number; - checkboxChecked?: boolean; -} - export interface IOpenSettings { forceNewWindow?: boolean; forceReuseWindow?: boolean; @@ -226,37 +149,12 @@ export interface IWindowService { readonly windowId: number; - pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise; - pickFileAndOpen(options: INativeOpenDialogOptions): Promise; - pickFolderAndOpen(options: INativeOpenDialogOptions): Promise; - pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise; - reloadWindow(args?: ParsedArgs): Promise; - openDevTools(options?: IDevToolsOptions): Promise; - toggleDevTools(): Promise; - closeWorkspace(): Promise; - updateTouchBar(items: ISerializableCommandAction[][]): Promise; - enterWorkspace(path: URI): Promise; - // rationale: will eventually move to electron-browser - // tslint:disable-next-line: no-dom-globals - toggleFullScreen(target?: HTMLElement): Promise; - setRepresentedFilename(fileName: string): Promise; getRecentlyOpened(): Promise; addRecentlyOpened(recents: IRecent[]): Promise; removeFromRecentlyOpened(paths: URI[]): Promise; focusWindow(): Promise; - closeWindow(): Promise; openWindow(uris: IURIToOpen[], options?: IOpenSettings): Promise; isFocused(): Promise; - setDocumentEdited(flag: boolean): Promise; - isMaximized(): Promise; - maximizeWindow(): Promise; - unmaximizeWindow(): Promise; - minimizeWindow(): Promise; - onWindowTitleDoubleClick(): Promise; - showMessageBox(options: MessageBoxOptions): Promise; - showSaveDialog(options: SaveDialogOptions): Promise; - showOpenDialog(options: OpenDialogOptions): Promise; - resolveProxy(url: string): Promise; } export type MenuBarVisibility = 'default' | 'visible' | 'toggle' | 'hidden'; diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 8208fa4e83..ce190a4a37 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -43,20 +43,6 @@ export class WindowsChannel implements IServerChannel { call(_: unknown, command: string, arg?: any): Promise { switch (command) { - case 'pickFileFolderAndOpen': return this.service.pickFileFolderAndOpen(arg); - case 'pickFileAndOpen': return this.service.pickFileAndOpen(arg); - case 'pickFolderAndOpen': return this.service.pickFolderAndOpen(arg); - case 'pickWorkspaceAndOpen': return this.service.pickWorkspaceAndOpen(arg); - case 'showMessageBox': return this.service.showMessageBox(arg[0], arg[1]); - case 'showSaveDialog': return this.service.showSaveDialog(arg[0], arg[1]); - case 'showOpenDialog': return this.service.showOpenDialog(arg[0], arg[1]); - case 'reloadWindow': return this.service.reloadWindow(arg[0], arg[1]); - case 'openDevTools': return this.service.openDevTools(arg[0], arg[1]); - case 'toggleDevTools': return this.service.toggleDevTools(arg); - case 'closeWorkspace': return this.service.closeWorkspace(arg); - case 'enterWorkspace': return this.service.enterWorkspace(arg[0], URI.revive(arg[1])); - case 'toggleFullScreen': return this.service.toggleFullScreen(arg); - case 'setRepresentedFilename': return this.service.setRepresentedFilename(arg[0], arg[1]); case 'addRecentlyOpened': return this.service.addRecentlyOpened(arg.map((recent: IRecent) => { if (isRecentFile(recent)) { recent.fileUri = URI.revive(recent.fileUri); @@ -69,23 +55,9 @@ export class WindowsChannel implements IServerChannel { })); case 'removeFromRecentlyOpened': return this.service.removeFromRecentlyOpened(arg.map(URI.revive)); case 'clearRecentlyOpened': return this.service.clearRecentlyOpened(); - case 'newWindowTab': return this.service.newWindowTab(); - case 'showPreviousWindowTab': return this.service.showPreviousWindowTab(); - case 'showNextWindowTab': return this.service.showNextWindowTab(); - case 'moveWindowTabToNewWindow': return this.service.moveWindowTabToNewWindow(); - case 'mergeAllWindowTabs': return this.service.mergeAllWindowTabs(); - case 'toggleWindowTabsBar': return this.service.toggleWindowTabsBar(); - case 'updateTouchBar': return this.service.updateTouchBar(arg[0], arg[1]); case 'getRecentlyOpened': return this.service.getRecentlyOpened(arg); case 'focusWindow': return this.service.focusWindow(arg); - case 'closeWindow': return this.service.closeWindow(arg); case 'isFocused': return this.service.isFocused(arg); - case 'isMaximized': return this.service.isMaximized(arg); - case 'maximizeWindow': return this.service.maximizeWindow(arg); - case 'unmaximizeWindow': return this.service.unmaximizeWindow(arg); - case 'minimizeWindow': return this.service.minimizeWindow(arg); - case 'onWindowTitleDoubleClick': return this.service.onWindowTitleDoubleClick(arg); - case 'setDocumentEdited': return this.service.setDocumentEdited(arg[0], arg[1]); case 'openWindow': { const urisToOpen: IURIToOpen[] = arg[1]; const options: IOpenSettings = arg[2]; @@ -101,17 +73,9 @@ export class WindowsChannel implements IServerChannel { options.waitMarkerFileURI = options.waitMarkerFileURI && URI.revive(options.waitMarkerFileURI); return this.service.openWindow(arg[0], urisToOpen, options); } - case 'openNewWindow': return this.service.openNewWindow(arg); - case 'openExtensionDevelopmentHostWindow': return this.service.openExtensionDevelopmentHostWindow(arg[0], arg[1]); + case 'openExtensionDevelopmentHostWindow': return (this.service as any).openExtensionDevelopmentHostWindow(arg[0], arg[1]); // TODO@Isidor move case 'getWindows': return this.service.getWindows(); - 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 'getActiveWindowId': return this.service.getActiveWindowId(); - case 'openExternal': return this.service.openExternal(arg); - case 'startCrashReporter': return this.service.startCrashReporter(arg); - case 'resolveProxy': return this.service.resolveProxy(arg[0], arg[1]); } throw new Error(`Call not found: ${command}`); diff --git a/src/vs/platform/windows/electron-browser/windowsService.ts b/src/vs/platform/windows/electron-browser/windowsService.ts index 136f0aea0b..f144d497d8 100644 --- a/src/vs/platform/windows/electron-browser/windowsService.ts +++ b/src/vs/platform/windows/electron-browser/windowsService.ts @@ -5,10 +5,9 @@ import { Event } from 'vs/base/common/event'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, CrashReporterStartOptions, IMessageBoxResult, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IDevToolsOptions, INewWindowOptions, IURIToOpen, IOpenSettings } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IURIToOpen, IOpenSettings } from 'vs/platform/windows/common/windows'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IRecentlyOpened, IRecent, isRecentWorkspace } from 'vs/platform/history/common/history'; -import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; @@ -31,67 +30,6 @@ export class WindowsService implements IWindowsService { this.channel = mainProcessService.getChannel('windows'); } - pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise { - return this.channel.call('pickFileFolderAndOpen', options); - } - - pickFileAndOpen(options: INativeOpenDialogOptions): Promise { - return this.channel.call('pickFileAndOpen', options); - } - - pickFolderAndOpen(options: INativeOpenDialogOptions): Promise { - return this.channel.call('pickFolderAndOpen', options); - } - - pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise { - return this.channel.call('pickWorkspaceAndOpen', options); - } - - showMessageBox(windowId: number, options: MessageBoxOptions): Promise { - return this.channel.call('showMessageBox', [windowId, options]); - } - - showSaveDialog(windowId: number, options: SaveDialogOptions): Promise { - return this.channel.call('showSaveDialog', [windowId, options]); - } - - showOpenDialog(windowId: number, options: OpenDialogOptions): Promise { - return this.channel.call('showOpenDialog', [windowId, options]); - } - - reloadWindow(windowId: number, args?: ParsedArgs): Promise { - return this.channel.call('reloadWindow', [windowId, args]); - } - - openDevTools(windowId: number, options?: IDevToolsOptions): Promise { - return this.channel.call('openDevTools', [windowId, options]); - } - - toggleDevTools(windowId: number): Promise { - return this.channel.call('toggleDevTools', windowId); - } - - closeWorkspace(windowId: number): Promise { - return this.channel.call('closeWorkspace', windowId); - } - - async enterWorkspace(windowId: number, path: URI): Promise { - const result: IEnterWorkspaceResult = await this.channel.call('enterWorkspace', [windowId, path]); - if (result) { - result.workspace = reviveWorkspaceIdentifier(result.workspace); - } - - return result; - } - - toggleFullScreen(windowId: number): Promise { - return this.channel.call('toggleFullScreen', windowId); - } - - setRepresentedFilename(windowId: number, fileName: string): Promise { - return this.channel.call('setRepresentedFilename', [windowId, fileName]); - } - addRecentlyOpened(recent: IRecent[]): Promise { return this.channel.call('addRecentlyOpened', recent); } @@ -112,90 +50,18 @@ export class WindowsService implements IWindowsService { return recentlyOpened; } - newWindowTab(): Promise { - return this.channel.call('newWindowTab'); - } - - showPreviousWindowTab(): Promise { - return this.channel.call('showPreviousWindowTab'); - } - - showNextWindowTab(): Promise { - return this.channel.call('showNextWindowTab'); - } - - moveWindowTabToNewWindow(): Promise { - return this.channel.call('moveWindowTabToNewWindow'); - } - - mergeAllWindowTabs(): Promise { - return this.channel.call('mergeAllWindowTabs'); - } - - toggleWindowTabsBar(): Promise { - return this.channel.call('toggleWindowTabsBar'); - } - focusWindow(windowId: number): Promise { return this.channel.call('focusWindow', windowId); } - closeWindow(windowId: number): Promise { - return this.channel.call('closeWindow', windowId); - } - isFocused(windowId: number): Promise { return this.channel.call('isFocused', windowId); } - isMaximized(windowId: number): Promise { - return this.channel.call('isMaximized', windowId); - } - - maximizeWindow(windowId: number): Promise { - return this.channel.call('maximizeWindow', windowId); - } - - unmaximizeWindow(windowId: number): Promise { - return this.channel.call('unmaximizeWindow', windowId); - } - - minimizeWindow(windowId: number): Promise { - return this.channel.call('minimizeWindow', windowId); - } - - onWindowTitleDoubleClick(windowId: number): Promise { - return this.channel.call('onWindowTitleDoubleClick', windowId); - } - - setDocumentEdited(windowId: number, flag: boolean): Promise { - return this.channel.call('setDocumentEdited', [windowId, flag]); - } - - quit(): Promise { - return this.channel.call('quit'); - } - - relaunch(options: { addArgs?: string[], removeArgs?: string[] }): Promise { - return this.channel.call('relaunch', [options]); - } - - whenSharedProcessReady(): Promise { - return this.channel.call('whenSharedProcessReady'); - } - - toggleSharedProcess(): Promise { - return this.channel.call('toggleSharedProcess'); - } - openWindow(windowId: number, uris: IURIToOpen[], options: IOpenSettings): Promise { return this.channel.call('openWindow', [windowId, uris, options]); } - openNewWindow(options?: INewWindowOptions): Promise { - return this.channel.call('openNewWindow', options); - } - openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { return this.channel.call('openExtensionDevelopmentHostWindow', [args, env]); } @@ -225,20 +91,4 @@ export class WindowsService implements IWindowsService { getActiveWindowId(): Promise { return this.channel.call('getActiveWindowId'); } - - openExternal(url: string): Promise { - return this.channel.call('openExternal', url); - } - - startCrashReporter(config: CrashReporterStartOptions): Promise { - return this.channel.call('startCrashReporter', config); - } - - updateTouchBar(windowId: number, items: ISerializableCommandAction[][]): Promise { - return this.channel.call('updateTouchBar', [windowId, items]); - } - - resolveProxy(windowId: number, url: string): Promise { - return Promise.resolve(this.channel.call('resolveProxy', [windowId, url])); - } } diff --git a/src/vs/platform/windows/electron-main/legacyWindowsMainService.ts b/src/vs/platform/windows/electron-main/legacyWindowsMainService.ts new file mode 100644 index 0000000000..704b46d9a4 --- /dev/null +++ b/src/vs/platform/windows/electron-main/legacyWindowsMainService.ts @@ -0,0 +1,203 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { assign } from 'vs/base/common/objects'; +import { URI } from 'vs/base/common/uri'; +import { IWindowsService, OpenContext, IOpenSettings, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; +import { app, MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue, BrowserWindow, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions } from 'electron'; +import { Event } from 'vs/base/common/event'; +import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; +import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; +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 { Schemas } from 'vs/base/common/network'; +import { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; +import { ILogService } from 'vs/platform/log/common/log'; + +// @deprecated this should eventually go away and be implemented by host & electron service +export class LegacyWindowsMainService extends Disposable implements IWindowsService, IURLHandler { + + _serviceBrand: undefined; + + private readonly disposables = this._register(new DisposableStore()); + + private _activeWindowId: number | undefined; + + readonly onWindowOpen: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-created', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); + readonly onWindowBlur: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-blur', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); + readonly onWindowMaximize: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-maximize', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); + readonly onWindowUnmaximize: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-unmaximize', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); + readonly onWindowFocus: Event = Event.any( + Event.map(Event.filter(Event.map(this.windowsMainService.onWindowsCountChanged, () => this.windowsMainService.getLastActiveWindow()), w => !!w), w => w!.id), + Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (_, w: BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)) + ); + + readonly onRecentlyOpenedChange: Event = this.historyMainService.onRecentlyOpenedChange; + + constructor( + @IWindowsMainService private readonly windowsMainService: IWindowsMainService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IURLService urlService: IURLService, + @IHistoryMainService private readonly historyMainService: IHistoryMainService, + @ILogService private readonly logService: ILogService + ) { + super(); + + urlService.registerHandler(this); + + // remember last active window id + Event.latch(Event.any(this.onWindowOpen, this.onWindowFocus)) + (id => this._activeWindowId = id, null, this.disposables); + } + + async showMessageBox(windowId: number, options: MessageBoxOptions): Promise { + this.logService.trace('windowsService#showMessageBox', windowId); + + return this.withWindow(windowId, codeWindow => this.windowsMainService.showMessageBox(options, codeWindow), () => this.windowsMainService.showMessageBox(options))!; + } + + async showSaveDialog(windowId: number, options: SaveDialogOptions): Promise { + this.logService.trace('windowsService#showSaveDialog', windowId); + + return this.withWindow(windowId, codeWindow => this.windowsMainService.showSaveDialog(options, codeWindow), () => this.windowsMainService.showSaveDialog(options))!; + } + + async showOpenDialog(windowId: number, options: OpenDialogOptions): Promise { + this.logService.trace('windowsService#showOpenDialog', windowId); + + return this.withWindow(windowId, codeWindow => this.windowsMainService.showOpenDialog(options, codeWindow), () => this.windowsMainService.showOpenDialog(options))!; + } + + async addRecentlyOpened(recents: IRecent[]): Promise { + this.logService.trace('windowsService#addRecentlyOpened'); + this.historyMainService.addRecentlyOpened(recents); + } + + async removeFromRecentlyOpened(paths: URI[]): Promise { + this.logService.trace('windowsService#removeFromRecentlyOpened'); + + this.historyMainService.removeFromRecentlyOpened(paths); + } + + async clearRecentlyOpened(): Promise { + this.logService.trace('windowsService#clearRecentlyOpened'); + + this.historyMainService.clearRecentlyOpened(); + } + + async getRecentlyOpened(windowId: number): Promise { + this.logService.trace('windowsService#getRecentlyOpened', windowId); + + return this.withWindow(windowId, codeWindow => this.historyMainService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpenOrCreate), () => this.historyMainService.getRecentlyOpened())!; + } + + async focusWindow(windowId: number): Promise { + this.logService.trace('windowsService#focusWindow', windowId); + + if (isMacintosh) { + return this.withWindow(windowId, codeWindow => codeWindow.win.show()); + } else { + return this.withWindow(windowId, codeWindow => codeWindow.win.focus()); + } + } + + async isFocused(windowId: number): Promise { + this.logService.trace('windowsService#isFocused', windowId); + + return this.withWindow(windowId, codeWindow => codeWindow.win.isFocused(), () => false)!; + } + + async openWindow(windowId: number, urisToOpen: IURIToOpen[], options: IOpenSettings): Promise { + this.logService.trace('windowsService#openWindow'); + if (!urisToOpen || !urisToOpen.length) { + return undefined; + } + + this.windowsMainService.open({ + context: OpenContext.API, + contextWindowId: windowId, + urisToOpen: urisToOpen, + cli: options.args ? { ...this.environmentService.args, ...options.args } : this.environmentService.args, + forceNewWindow: options.forceNewWindow, + forceReuseWindow: options.forceReuseWindow, + diffMode: options.diffMode, + addMode: options.addMode, + gotoLineMode: options.gotoLineMode, + noRecentEntry: options.noRecentEntry, + waitMarkerFileURI: options.waitMarkerFileURI + }); + } + + async openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + this.logService.trace('windowsService#openExtensionDevelopmentHostWindow ' + JSON.stringify(args)); + + const extDevPaths = args.extensionDevelopmentPath; + if (extDevPaths) { + this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, { + context: OpenContext.API, + cli: args, + userEnv: Object.keys(env).length > 0 ? env : undefined + }); + } + } + + async getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { + this.logService.trace('windowsService#getWindows'); + + const windows = this.windowsMainService.getWindows(); + + return windows.map(window => ({ + id: window.id, + workspace: window.openedWorkspace, + folderUri: window.openedFolderUri, + title: window.win.getTitle(), + filename: window.getRepresentedFilename() + })); + } + + async getWindowCount(): Promise { + this.logService.trace('windowsService#getWindowCount'); + + return this.windowsMainService.getWindows().length; + } + + async getActiveWindowId(): Promise { + return this._activeWindowId; + } + + async handleURL(uri: URI): Promise { + + // Catch file URLs + if (uri.authority === Schemas.file && !!uri.path) { + this.openFileForURI({ fileUri: URI.file(uri.fsPath) }); // using fsPath on a non-file URI... + return true; + } + + return false; + } + + private openFileForURI(uri: IURIToOpen): void { + const cli = assign(Object.create(null), this.environmentService.args); + const urisToOpen = [uri]; + + this.windowsMainService.open({ context: OpenContext.API, cli, urisToOpen, gotoLineMode: true }); + } + + private withWindow(windowId: number, fn: (window: ICodeWindow) => T, fallback?: () => T): T | undefined { + const codeWindow = this.windowsMainService.getWindowById(windowId); + if (codeWindow) { + return fn(codeWindow); + } + + if (fallback) { + return fallback(); + } + + return undefined; + } +} diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index c8a46a9a31..f7bafd705e 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OpenContext, IWindowConfiguration, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { OpenContext, IWindowConfiguration, INativeOpenDialogOptions, IEnterWorkspaceResult, IURIToOpen } from 'vs/platform/windows/common/windows'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -11,6 +11,7 @@ import { IProcessEnvironment } from 'vs/base/common/platform'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { URI } from 'vs/base/common/uri'; +import { MessageBoxReturnValue, SaveDialogReturnValue, OpenDialogReturnValue, Rectangle, BrowserWindow, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions } from 'electron'; export interface IWindowState { width?: number; @@ -30,7 +31,7 @@ export const enum WindowMode { export interface ICodeWindow { readonly id: number; - readonly win: Electron.BrowserWindow; + readonly win: BrowserWindow; readonly config: IWindowConfiguration; readonly openedFolderUri?: URI; @@ -49,13 +50,13 @@ export interface ICodeWindow { addTabbedWindow(window: ICodeWindow): void; - load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void; + load(config: IWindowConfiguration, isReload?: boolean): void; reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void; focus(): void; close(): void; - getBounds(): Electron.Rectangle; + getBounds(): Rectangle; send(channel: string, ...args: any[]): void; sendWhenReady(channel: string, ...args: any[]): void; @@ -66,7 +67,7 @@ export interface ICodeWindow { hasHiddenTitleBarStyle(): boolean; setRepresentedFilename(name: string): void; getRepresentedFilename(): string; - onWindowTitleDoubleClick(): void; + handleTitleDoubleClick(): void; updateTouchBar(items: ISerializableCommandAction[][]): void; @@ -97,18 +98,19 @@ export interface IWindowsMainService { closeWorkspace(win: ICodeWindow): void; open(openConfig: IOpenConfiguration): ICodeWindow[]; openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): void; - pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise; - pickFolderAndOpen(options: INativeOpenDialogOptions): Promise; - pickFileAndOpen(options: INativeOpenDialogOptions): Promise; - pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise; - showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): Promise; - showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): Promise; - showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): Promise; + pickFileFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise; + pickFolderAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise; + pickFileAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise; + pickWorkspaceAndOpen(options: INativeOpenDialogOptions, win?: ICodeWindow): Promise; + showMessageBox(options: MessageBoxOptions, win?: ICodeWindow): Promise; + showSaveDialog(options: SaveDialogOptions, win?: ICodeWindow): Promise; + showOpenDialog(options: OpenDialogOptions, win?: ICodeWindow): Promise; focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow; getLastActiveWindow(): ICodeWindow | undefined; waitForWindowCloseOrLoad(windowId: number): Promise; - openNewWindow(context: OpenContext, options?: INewWindowOptions): ICodeWindow[]; + openEmptyWindow(context: OpenContext, options?: { reuse?: boolean, remoteAuthority?: string }): ICodeWindow[]; openNewTabbedWindow(context: OpenContext): ICodeWindow[]; + openExternal(url: string): Promise; sendToFocused(channel: string, ...args: any[]): void; sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void; getFocusedWindow(): ICodeWindow | undefined; diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts deleted file mode 100644 index 0797cb5cc0..0000000000 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ /dev/null @@ -1,424 +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 { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { assign } from 'vs/base/common/objects'; -import { URI } from 'vs/base/common/uri'; -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 } from 'electron'; -import { Event } from 'vs/base/common/event'; -import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; -import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { IWindowsMainService, ISharedProcess, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; -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 { isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; -import { ILogService } from 'vs/platform/log/common/log'; - -export class WindowsService extends Disposable implements IWindowsService, IURLHandler { - - _serviceBrand: undefined; - - private readonly disposables = this._register(new DisposableStore()); - - private _activeWindowId: number | undefined; - - readonly onWindowOpen: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-created', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); - readonly onWindowBlur: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-blur', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); - readonly onWindowMaximize: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-maximize', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); - readonly onWindowUnmaximize: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-unmaximize', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); - readonly onWindowFocus: Event = Event.any( - Event.map(Event.filter(Event.map(this.windowsMainService.onWindowsCountChanged, () => this.windowsMainService.getLastActiveWindow()), w => !!w), w => w!.id), - Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)) - ); - - readonly onRecentlyOpenedChange: Event = this.historyMainService.onRecentlyOpenedChange; - - constructor( - private sharedProcess: ISharedProcess, - @IWindowsMainService private readonly windowsMainService: IWindowsMainService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IURLService urlService: IURLService, - @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, - @IHistoryMainService private readonly historyMainService: IHistoryMainService, - @ILogService private readonly logService: ILogService - ) { - super(); - - urlService.registerHandler(this); - - // remember last active window id - Event.latch(Event.any(this.onWindowOpen, this.onWindowFocus)) - (id => this._activeWindowId = id, null, this.disposables); - } - - async pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise { - this.logService.trace('windowsService#pickFileFolderAndOpen'); - - this.windowsMainService.pickFileFolderAndOpen(options); - } - - async pickFileAndOpen(options: INativeOpenDialogOptions): Promise { - this.logService.trace('windowsService#pickFileAndOpen'); - - this.windowsMainService.pickFileAndOpen(options); - } - - async pickFolderAndOpen(options: INativeOpenDialogOptions): Promise { - this.logService.trace('windowsService#pickFolderAndOpen'); - - this.windowsMainService.pickFolderAndOpen(options); - } - - async pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise { - this.logService.trace('windowsService#pickWorkspaceAndOpen'); - - this.windowsMainService.pickWorkspaceAndOpen(options); - } - - async showMessageBox(windowId: number, options: Electron.MessageBoxOptions): Promise { - this.logService.trace('windowsService#showMessageBox', windowId); - - return this.withWindow(windowId, codeWindow => this.windowsMainService.showMessageBox(options, codeWindow), () => this.windowsMainService.showMessageBox(options))!; - } - - async showSaveDialog(windowId: number, options: Electron.SaveDialogOptions): Promise { - this.logService.trace('windowsService#showSaveDialog', windowId); - - return this.withWindow(windowId, codeWindow => this.windowsMainService.showSaveDialog(options, codeWindow), () => this.windowsMainService.showSaveDialog(options))!; - } - - async showOpenDialog(windowId: number, options: Electron.OpenDialogOptions): Promise { - this.logService.trace('windowsService#showOpenDialog', windowId); - - return this.withWindow(windowId, codeWindow => this.windowsMainService.showOpenDialog(options, codeWindow), () => this.windowsMainService.showOpenDialog(options))!; - } - - async reloadWindow(windowId: number, args: ParsedArgs): Promise { - this.logService.trace('windowsService#reloadWindow', windowId); - - return this.withWindow(windowId, codeWindow => this.windowsMainService.reload(codeWindow, args)); - } - - async openDevTools(windowId: number, options?: IDevToolsOptions): Promise { - this.logService.trace('windowsService#openDevTools', windowId); - - return this.withWindow(windowId, codeWindow => codeWindow.win.webContents.openDevTools(options)); - } - - async toggleDevTools(windowId: number): Promise { - this.logService.trace('windowsService#toggleDevTools', windowId); - - return this.withWindow(windowId, codeWindow => { - const contents = codeWindow.win.webContents; - if (isMacintosh && codeWindow.hasHiddenTitleBarStyle() && !codeWindow.isFullScreen() && !contents.isDevToolsOpened()) { - contents.openDevTools({ mode: 'undocked' }); // due to https://github.com/electron/electron/issues/3647 - } else { - contents.toggleDevTools(); - } - }); - } - - async updateTouchBar(windowId: number, items: ISerializableCommandAction[][]): Promise { - this.logService.trace('windowsService#updateTouchBar', windowId); - - return this.withWindow(windowId, codeWindow => codeWindow.updateTouchBar(items)); - } - - async closeWorkspace(windowId: number): Promise { - this.logService.trace('windowsService#closeWorkspace', windowId); - - return this.withWindow(windowId, codeWindow => this.windowsMainService.closeWorkspace(codeWindow)); - } - - async enterWorkspace(windowId: number, path: URI): Promise { - this.logService.trace('windowsService#enterWorkspace', windowId); - - return this.withWindow(windowId, codeWindow => this.windowsMainService.enterWorkspace(codeWindow, path)); - } - - async toggleFullScreen(windowId: number): Promise { - this.logService.trace('windowsService#toggleFullScreen', windowId); - - return this.withWindow(windowId, codeWindow => codeWindow.toggleFullScreen()); - } - - async setRepresentedFilename(windowId: number, fileName: string): Promise { - this.logService.trace('windowsService#setRepresentedFilename', windowId); - - return this.withWindow(windowId, codeWindow => codeWindow.setRepresentedFilename(fileName)); - } - - async addRecentlyOpened(recents: IRecent[]): Promise { - this.logService.trace('windowsService#addRecentlyOpened'); - this.historyMainService.addRecentlyOpened(recents); - } - - async removeFromRecentlyOpened(paths: URI[]): Promise { - this.logService.trace('windowsService#removeFromRecentlyOpened'); - - this.historyMainService.removeFromRecentlyOpened(paths); - } - - async clearRecentlyOpened(): Promise { - this.logService.trace('windowsService#clearRecentlyOpened'); - - this.historyMainService.clearRecentlyOpened(); - } - - async getRecentlyOpened(windowId: number): Promise { - this.logService.trace('windowsService#getRecentlyOpened', windowId); - - return this.withWindow(windowId, codeWindow => this.historyMainService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpenOrCreate), () => this.historyMainService.getRecentlyOpened())!; - } - - async newWindowTab(): Promise { - this.logService.trace('windowsService#newWindowTab'); - - this.windowsMainService.openNewTabbedWindow(OpenContext.API); - } - - async showPreviousWindowTab(): Promise { - this.logService.trace('windowsService#showPreviousWindowTab'); - - Menu.sendActionToFirstResponder('selectPreviousTab:'); - } - - async showNextWindowTab(): Promise { - this.logService.trace('windowsService#showNextWindowTab'); - - Menu.sendActionToFirstResponder('selectNextTab:'); - } - - async moveWindowTabToNewWindow(): Promise { - this.logService.trace('windowsService#moveWindowTabToNewWindow'); - - Menu.sendActionToFirstResponder('moveTabToNewWindow:'); - } - - async mergeAllWindowTabs(): Promise { - this.logService.trace('windowsService#mergeAllWindowTabs'); - - Menu.sendActionToFirstResponder('mergeAllWindows:'); - } - - async toggleWindowTabsBar(): Promise { - this.logService.trace('windowsService#toggleWindowTabsBar'); - - Menu.sendActionToFirstResponder('toggleTabBar:'); - } - - async focusWindow(windowId: number): Promise { - this.logService.trace('windowsService#focusWindow', windowId); - - if (isMacintosh) { - return this.withWindow(windowId, codeWindow => codeWindow.win.show()); - } else { - return this.withWindow(windowId, codeWindow => codeWindow.win.focus()); - } - } - - async closeWindow(windowId: number): Promise { - this.logService.trace('windowsService#closeWindow', windowId); - - return this.withWindow(windowId, codeWindow => codeWindow.win.close()); - } - - async isFocused(windowId: number): Promise { - this.logService.trace('windowsService#isFocused', windowId); - - return this.withWindow(windowId, codeWindow => codeWindow.win.isFocused(), () => false)!; - } - - async isMaximized(windowId: number): Promise { - this.logService.trace('windowsService#isMaximized', windowId); - - return this.withWindow(windowId, codeWindow => codeWindow.win.isMaximized(), () => false)!; - } - - async maximizeWindow(windowId: number): Promise { - this.logService.trace('windowsService#maximizeWindow', windowId); - - return this.withWindow(windowId, codeWindow => codeWindow.win.maximize()); - } - - async unmaximizeWindow(windowId: number): Promise { - this.logService.trace('windowsService#unmaximizeWindow', windowId); - - return this.withWindow(windowId, codeWindow => codeWindow.win.unmaximize()); - } - - async minimizeWindow(windowId: number): Promise { - this.logService.trace('windowsService#minimizeWindow', windowId); - - return this.withWindow(windowId, codeWindow => codeWindow.win.minimize()); - } - - async onWindowTitleDoubleClick(windowId: number): Promise { - this.logService.trace('windowsService#onWindowTitleDoubleClick', windowId); - - return this.withWindow(windowId, codeWindow => codeWindow.onWindowTitleDoubleClick()); - } - - async setDocumentEdited(windowId: number, flag: boolean): Promise { - this.logService.trace('windowsService#setDocumentEdited', windowId); - - return this.withWindow(windowId, codeWindow => { - if (codeWindow.win.isDocumentEdited() !== flag) { - codeWindow.win.setDocumentEdited(flag); - } - }); - } - - async openWindow(windowId: number, urisToOpen: IURIToOpen[], options: IOpenSettings): Promise { - this.logService.trace('windowsService#openWindow'); - if (!urisToOpen || !urisToOpen.length) { - return undefined; - } - - this.windowsMainService.open({ - context: OpenContext.API, - contextWindowId: windowId, - urisToOpen: urisToOpen, - cli: options.args ? { ...this.environmentService.args, ...options.args } : this.environmentService.args, - forceNewWindow: options.forceNewWindow, - forceReuseWindow: options.forceReuseWindow, - diffMode: options.diffMode, - addMode: options.addMode, - gotoLineMode: options.gotoLineMode, - noRecentEntry: options.noRecentEntry, - waitMarkerFileURI: options.waitMarkerFileURI - }); - } - - async openNewWindow(options?: INewWindowOptions): Promise { - this.logService.trace('windowsService#openNewWindow ' + JSON.stringify(options)); - - this.windowsMainService.openNewWindow(OpenContext.API, options); - } - - async openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { - this.logService.trace('windowsService#openExtensionDevelopmentHostWindow ' + JSON.stringify(args)); - - const extDevPaths = args.extensionDevelopmentPath; - if (extDevPaths) { - this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, { - context: OpenContext.API, - cli: args, - userEnv: Object.keys(env).length > 0 ? env : undefined - }); - } - } - - async getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { - this.logService.trace('windowsService#getWindows'); - - const windows = this.windowsMainService.getWindows(); - - return windows.map(window => ({ - id: window.id, - workspace: window.openedWorkspace, - folderUri: window.openedFolderUri, - title: window.win.getTitle(), - filename: window.getRepresentedFilename() - })); - } - - async getWindowCount(): Promise { - this.logService.trace('windowsService#getWindowCount'); - - return this.windowsMainService.getWindows().length; - } - - async getActiveWindowId(): Promise { - return this._activeWindowId; - } - - async openExternal(url: string): Promise { - this.logService.trace('windowsService#openExternal'); - - shell.openExternal(url); - return true; - } - - async startCrashReporter(config: Electron.CrashReporterStartOptions): Promise { - this.logService.trace('windowsService#startCrashReporter'); - - crashReporter.start(config); - } - - async quit(): Promise { - this.logService.trace('windowsService#quit'); - - this.windowsMainService.quit(); - } - - async relaunch(options: { addArgs?: string[], removeArgs?: string[] }): Promise { - this.logService.trace('windowsService#relaunch'); - - this.lifecycleMainService.relaunch(options); - } - - async whenSharedProcessReady(): Promise { - this.logService.trace('windowsService#whenSharedProcessReady'); - - return this.sharedProcess.whenReady(); - } - - async toggleSharedProcess(): Promise { - this.logService.trace('windowsService#toggleSharedProcess'); - - this.sharedProcess.toggle(); - - } - - async handleURL(uri: URI): Promise { - - // Catch file URLs - if (uri.authority === Schemas.file && !!uri.path) { - this.openFileForURI({ fileUri: URI.file(uri.fsPath) }); // using fsPath on a non-file URI... - return true; - } - - return false; - } - - private openFileForURI(uri: IURIToOpen): void { - const cli = assign(Object.create(null), this.environmentService.args); - const urisToOpen = [uri]; - - this.windowsMainService.open({ context: OpenContext.API, cli, urisToOpen, gotoLineMode: true }); - } - - async resolveProxy(windowId: number, url: string): Promise { - return new Promise(resolve => { - const codeWindow = this.windowsMainService.getWindowById(windowId); - if (codeWindow) { - codeWindow.win.webContents.session.resolveProxy(url, proxy => { - resolve(proxy); - }); - } else { - resolve(); - } - }); - } - - private withWindow(windowId: number, fn: (window: ICodeWindow) => T, fallback?: () => T): T | undefined { - const codeWindow = this.windowsMainService.getWindowById(windowId); - if (codeWindow) { - return fn(codeWindow); - } - - if (fallback) { - return fallback(); - } - - return undefined; - } -} diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index 149e0e9c63..1e5c993c88 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -17,6 +17,7 @@ import { normalizeDriveLetter } from 'vs/base/common/labels'; import { toSlashes } from 'vs/base/common/extpath'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; +import { IEnterWorkspaceResult } from 'vs/platform/windows/common/windows'; export const WORKSPACE_EXTENSION = 'code-workspace'; export const WORKSPACE_FILTER = [{ name: localize('codeWorkspace', "Code Workspace"), extensions: [WORKSPACE_EXTENSION] }]; @@ -97,6 +98,8 @@ export interface IWorkspacesService { _serviceBrand: undefined; + enterWorkspace(path: URI): Promise; + createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise; deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise; diff --git a/src/vs/platform/workspaces/electron-main/workspacesIpc.ts b/src/vs/platform/workspaces/electron-main/workspacesIpc.ts index 17450695ae..c74159e705 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesIpc.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesIpc.ts @@ -8,10 +8,14 @@ import { IWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/ import { IWorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; +import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; export class WorkspacesChannel implements IServerChannel { - constructor(private service: IWorkspacesMainService) { } + constructor( + private workspacesMainService: IWorkspacesMainService, + private windowsMainService: IWindowsMainService + ) { } listen(_: unknown, event: string): Event { throw new Error(`Event not found: ${event}`); @@ -32,14 +36,20 @@ export class WorkspacesChannel implements IServerChannel { }); } - return this.service.createUntitledWorkspace(folders, remoteAuthority); + return this.workspacesMainService.createUntitledWorkspace(folders, remoteAuthority); } case 'deleteUntitledWorkspace': { - const w: IWorkspaceIdentifier = arg; - return this.service.deleteUntitledWorkspace({ id: w.id, configPath: URI.revive(w.configPath) }); + const identifier: IWorkspaceIdentifier = arg; + return this.workspacesMainService.deleteUntitledWorkspace({ id: identifier.id, configPath: URI.revive(identifier.configPath) }); } case 'getWorkspaceIdentifier': { - return this.service.getWorkspaceIdentifier(URI.revive(arg)); + return this.workspacesMainService.getWorkspaceIdentifier(URI.revive(arg)); + } + case 'enterWorkspace': { + const window = this.windowsMainService.getWindowById(arg[0]); + if (window) { + return this.windowsMainService.enterWorkspace(window, URI.revive(arg[1])); + } } } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 7caa11a98a..f2f92bca7d 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8198,7 +8198,7 @@ declare module 'vscode' { * @param options Immutable metadata about the provider. * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ - export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider, options?: { isCaseSensitive?: boolean, isReadonly?: boolean }): Disposable; + export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider, options?: { readonly isCaseSensitive?: boolean, readonly isReadonly?: boolean }): Disposable; } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d950190072..6931f1fff4 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -616,6 +616,56 @@ declare module 'vscode' { debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult; } + /** + * Debug console mode used by debug session, see [options](#DebugSessionOptions). + */ + export enum DebugConsoleMode { + /** + * Debug session should have a separate debug console. + */ + Separate = 0, + + /** + * Debug session should share debug console with its parent session. + * This value has no effect for sessions which do not have a parent session. + */ + MergeWithParent = 1 + } + + /** + * Options for [starting a debug session](#debug.startDebugging). + */ + export interface DebugSessionOptions { + + /** + * When specified the newly created debug session is registered as a "child" session of this + * "parent" debug session. + */ + parentSession?: DebugSession; + + /** + * Controls whether this session should have a separate debug console or share it + * with the parent session. Has no effect for sessions which do not have a parent session. + * Defaults to Separate. + */ + consoleMode?: DebugConsoleMode; + } + + export namespace debug { + /** + * Start debugging by using either a named launch or named compound configuration, + * or by directly passing a [DebugConfiguration](#DebugConfiguration). + * The named configurations are looked up in '.vscode/launch.json' found in the given folder. + * Before debugging starts, all unsaved files are saved and the launch configurations are brought up-to-date. + * Folder specific variables used in the configuration (e.g. '${workspaceFolder}') are resolved against the given folder. + * @param folder The [workspace folder](#WorkspaceFolder) for looking up named configurations and resolving variables or `undefined` for a non-folder setup. + * @param nameOrConfiguration Either the name of a debug or compound configuration or a [DebugConfiguration](#DebugConfiguration) object. + * @param parentSessionOrOptions Debug sesison options. When passed a parent [debug session](#DebugSession), assumes options with just this parent session. + * @return A thenable that resolves when debugging could be successfully started. + */ + export function startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, parentSessionOrOptions?: DebugSession | DebugSessionOptions): Thenable; + } + //#endregion //#region Rob, Matt: logging @@ -1089,4 +1139,30 @@ declare module 'vscode' { } //#endregion + + // #region resolveExternalUri — mjbvz + + namespace env { + /** + * Resolves an *external* uri, such as a `http:` or `https:` link, from where the extension is running to a + * uri to the same resource on the client machine. + * + * This is a no-op if the extension is running locally. Currently only supports `https:` and `http:`. + * + * If the extension is running remotely, this function automatically establishes port forwarding from + * the local machine to `target` on the remote and returns a local uri that can be used to for this connection. + * + * Extensions should not store the result of `resolveExternalUri` as the resolved uri may become invalid due to + * a system or user action — for example, in remote cases, a user may close a port that was forwarded by + * `resolveExternalUri`. + * + * Note: uris passed through `openExternal` are automatically resolved and you should not call `resolveExternalUri` + * on them. + * + * @return A uri that can be used on the client machine. + */ + export function resolveExternalUri(target: Uri): Thenable; + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 543b72d70c..95ee945efc 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -33,7 +33,7 @@ export class MainThreadCommentThread implements modes.CommentThread { this._onDidChangeInput.fire(value); } - private _onDidChangeInput = new Emitter(); + private readonly _onDidChangeInput = new Emitter(); get onDidChangeInput(): Event { return this._onDidChangeInput.event; } private _label: string | undefined; @@ -57,7 +57,7 @@ export class MainThreadCommentThread implements modes.CommentThread { this._contextValue = context; } - private _onDidChangeLabel = new Emitter(); + private readonly _onDidChangeLabel = new Emitter(); readonly onDidChangeLabel: Event = this._onDidChangeLabel.event; private _comments: modes.Comment[] | undefined; @@ -71,7 +71,7 @@ export class MainThreadCommentThread implements modes.CommentThread { this._onDidChangeComments.fire(this._comments); } - private _onDidChangeComments = new Emitter(); + private readonly _onDidChangeComments = new Emitter(); get onDidChangeComments(): Event { return this._onDidChangeComments.event; } set range(range: IRange) { @@ -83,7 +83,7 @@ export class MainThreadCommentThread implements modes.CommentThread { return this._range; } - private _onDidChangeRange = new Emitter(); + private readonly _onDidChangeRange = new Emitter(); public onDidChangeRange = this._onDidChangeRange.event; private _collapsibleState: modes.CommentThreadCollapsibleState | undefined; @@ -96,7 +96,7 @@ export class MainThreadCommentThread implements modes.CommentThread { this._onDidChangeCollasibleState.fire(this._collapsibleState); } - private _onDidChangeCollasibleState = new Emitter(); + private readonly _onDidChangeCollasibleState = new Emitter(); public onDidChangeCollasibleState = this._onDidChangeCollasibleState.event; private _isDisposed: boolean; diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index ec635fcad1..bc0a47fb48 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { DisposableStore } from 'vs/base/common/lifecycle'; -import { URI as uri } from 'vs/base/common/uri'; -import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; +import { URI as uri, UriComponents } from 'vs/base/common/uri'; +import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug'; import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, - IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto, IDataBreakpointDto + IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto, IDataBreakpointDto, IStartDebuggingOptions, IDebugConfiguration } from 'vs/workbench/api/common/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import severity from 'vs/base/common/severity'; @@ -218,10 +218,15 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return undefined; } - public $startDebugging(_folderUri: uri | undefined, nameOrConfiguration: string | IConfig, parentSessionID: DebugSessionUUID | undefined): Promise { - const folderUri = _folderUri ? uri.revive(_folderUri) : undefined; + public $startDebugging(folder: UriComponents | undefined, nameOrConfig: string | IDebugConfiguration, options: IStartDebuggingOptions): Promise { + const folderUri = folder ? uri.revive(folder) : undefined; const launch = this.debugService.getConfigurationManager().getLaunch(folderUri); - return this.debugService.startDebugging(launch, nameOrConfiguration, false, this.getSession(parentSessionID)).then(success => { + const debugOptions: IDebugSessionOptions = { + noDebug: false, + parentSession: this.getSession(options.parentSessionID), + repl: options.repl + }; + return this.debugService.startDebugging(launch, nameOrConfig, debugOptions).then(success => { return success; }, err => { return Promise.reject(new Error(err && err.message ? err.message : 'cannot start debugging')); diff --git a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts index a385805478..11bb71394b 100644 --- a/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadDocumentsAndEditors.ts @@ -308,10 +308,10 @@ export class MainThreadDocumentsAndEditors { private readonly _proxy: ExtHostDocumentsAndEditorsShape; private readonly _textEditors = new Map(); - private _onTextEditorAdd = new Emitter(); - private _onTextEditorRemove = new Emitter(); - private _onDocumentAdd = new Emitter(); - private _onDocumentRemove = new Emitter(); + private readonly _onTextEditorAdd = new Emitter(); + private readonly _onTextEditorRemove = new Emitter(); + private readonly _onDocumentAdd = new Emitter(); + private readonly _onDocumentRemove = new Emitter(); readonly onTextEditorAdd: Event = this._onTextEditorAdd.event; readonly onTextEditorRemove: Event = this._onTextEditorRemove.event; diff --git a/src/vs/workbench/api/browser/mainThreadExtensionService.ts b/src/vs/workbench/api/browser/mainThreadExtensionService.ts index 8355249a29..1538591502 100644 --- a/src/vs/workbench/api/browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/browser/mainThreadExtensionService.ts @@ -14,10 +14,11 @@ import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { IExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; @extHostNamedCustomer(MainContext.MainThreadExtensionService) export class MainThreadExtensionService implements MainThreadExtensionServiceShape { @@ -25,7 +26,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha private readonly _extensionService: IExtensionService; private readonly _notificationService: INotificationService; private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService; - private readonly _windowService: IWindowService; + private readonly _hostService: IHostService; private readonly _extensionEnablementService: IExtensionEnablementService; constructor( @@ -33,27 +34,27 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha @IExtensionService extensionService: IExtensionService, @INotificationService notificationService: INotificationService, @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWindowService windowService: IWindowService, + @IHostService hostService: IHostService, @IExtensionEnablementService extensionEnablementService: IExtensionEnablementService ) { this._extensionService = extensionService; this._notificationService = notificationService; this._extensionsWorkbenchService = extensionsWorkbenchService; - this._windowService = windowService; + this._hostService = hostService; this._extensionEnablementService = extensionEnablementService; } public dispose(): void { } - $activateExtension(extensionId: ExtensionIdentifier, activationEvent: string): Promise { - return this._extensionService._activateById(extensionId, activationEvent); + $activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { + return this._extensionService._activateById(extensionId, reason); } $onWillActivateExtension(extensionId: ExtensionIdentifier): void { this._extensionService._onWillActivateExtension(extensionId); } - $onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void { - this._extensionService._onDidActivateExtension(extensionId, startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent); + $onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void { + this._extensionService._onDidActivateExtension(extensionId, codeLoadingTime, activateCallTime, activateResolvedTime, activationReason); } $onExtensionRuntimeError(extensionId: ExtensionIdentifier, data: SerializedError): void { const error = new Error(); @@ -92,7 +93,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha severity: Severity.Error, message: localize('reload window', "Cannot activate the '{0}' extension because it depends on the '{1}' extension, which is not loaded. Would you like to reload the window to load the extension?", extName, missingInstalledDependency.manifest.displayName || missingInstalledDependency.manifest.name), actions: { - primary: [new Action('reload', localize('reload', "Reload Window"), '', true, () => this._windowService.reloadWindow())] + primary: [new Action('reload', localize('reload', "Reload Window"), '', true, () => this._hostService.reload())] } }); } else { @@ -103,7 +104,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha actions: { primary: [new Action('enable', localize('enable dep', "Enable and Reload"), '', true, () => this._extensionEnablementService.setEnablement([missingInstalledDependency], enablementState === EnablementState.DisabledGlobally ? EnablementState.EnabledGlobally : EnablementState.EnabledWorkspace) - .then(() => this._windowService.reloadWindow(), e => this._notificationService.error(e)))] + .then(() => this._hostService.reload(), e => this._notificationService.error(e)))] } }); } @@ -119,7 +120,7 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha actions: { primary: [new Action('install', localize('install missing dep', "Install and Reload"), '', true, () => this._extensionsWorkbenchService.install(dependencyExtension) - .then(() => this._windowService.reloadWindow(), e => this._notificationService.error(e)))] + .then(() => this._hostService.reload(), e => this._notificationService.error(e)))] } }); } else { diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 87e1c120b6..1047fabd35 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -18,12 +18,12 @@ class MainThreadSCMResourceGroup implements ISCMResourceGroup { readonly elements: ISCMResource[] = []; - private _onDidSplice = new Emitter>(); + private readonly _onDidSplice = new Emitter>(); readonly onDidSplice = this._onDidSplice.event; get hideWhenEmpty(): boolean { return !!this.features.hideWhenEmpty; } - private _onDidChange = new Emitter(); + private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; constructor( @@ -104,7 +104,7 @@ class MainThreadSCMProvider implements ISCMProvider { // // .filter(g => g.resources.elements.length > 0 || !g.features.hideWhenEmpty); // } - private _onDidChangeResources = new Emitter(); + private readonly _onDidChangeResources = new Emitter(); readonly onDidChangeResources: Event = this._onDidChangeResources.event; private features: SCMProviderFeatures = {}; @@ -119,13 +119,13 @@ class MainThreadSCMProvider implements ISCMProvider { get statusBarCommands(): Command[] | undefined { return this.features.statusBarCommands; } get count(): number | undefined { return this.features.count; } - private _onDidChangeCommitTemplate = new Emitter(); + private readonly _onDidChangeCommitTemplate = new Emitter(); readonly onDidChangeCommitTemplate: Event = this._onDidChangeCommitTemplate.event; - private _onDidChangeStatusBarCommands = new Emitter(); + private readonly _onDidChangeStatusBarCommands = new Emitter(); get onDidChangeStatusBarCommands(): Event { return this._onDidChangeStatusBarCommands.event; } - private _onDidChange = new Emitter(); + private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; constructor( diff --git a/src/vs/workbench/api/browser/mainThreadUrls.ts b/src/vs/workbench/api/browser/mainThreadUrls.ts index 04bd156ae8..5c4dccd26a 100644 --- a/src/vs/workbench/api/browser/mainThreadUrls.ts +++ b/src/vs/workbench/api/browser/mainThreadUrls.ts @@ -8,7 +8,7 @@ import { extHostNamedCustomer } from '../common/extHostCustomers'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IExtensionUrlHandler } from 'vs/workbench/services/extensions/common/extensionUrlHandler'; +import { IExtensionUrlHandler } from 'vs/workbench/services/extensions/browser/extensionUrlHandler'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; class ExtensionUrlHandler implements IURLHandler { diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index c90189c265..87170849af 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -96,7 +96,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews // This should trigger the real reviver to be registered from the extension host side. this._register(_webviewEditorService.registerResolver({ canResolve: (webview: WebviewInput) => { - if (!webview.webview.state && webview.getTypeId() === WebviewInput.typeId) { // TODO: The typeid check is a workaround for the CustomFileEditorInput case + if (webview.getTypeId() === CustomFileEditorInput.typeId) { return false; } @@ -198,7 +198,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews this._revivers.set(viewType, this._webviewEditorService.registerResolver({ canResolve: (webviewEditorInput) => { - return !!webviewEditorInput.webview.state && webviewEditorInput.viewType === this.getInternalWebviewViewType(viewType); + return webviewEditorInput.viewType === this.getInternalWebviewViewType(viewType); }, resolveWebview: async (webviewEditorInput): Promise => { const viewType = this.fromInternalWebviewViewType(webviewEditorInput.viewType); @@ -207,7 +207,7 @@ export class MainThreadWebviews extends Disposable implements MainThreadWebviews return; } - const handle = generateUuid(); + const handle = webviewEditorInput.id; this._webviewEditorInputs.add(handle, webviewEditorInput); this.hookupWebviewEventDelegate(handle, webviewEditorInput); diff --git a/src/vs/workbench/api/browser/mainThreadWindow.ts b/src/vs/workbench/api/browser/mainThreadWindow.ts index 81ff9c95cf..425356f9e8 100644 --- a/src/vs/workbench/api/browser/mainThreadWindow.ts +++ b/src/vs/workbench/api/browser/mainThreadWindow.ts @@ -4,29 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; -import { ExtHostContext, ExtHostWindowShape, IExtHostContext, MainContext, MainThreadWindowShape, IOpenUriOptions } from '../common/extHost.protocol'; -import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { extractLocalHostUriMetaDataForPortMapping } from 'vs/workbench/contrib/webview/common/portMapping'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ExtHostContext, ExtHostWindowShape, IExtHostContext, IOpenUriOptions, MainContext, MainThreadWindowShape } from '../common/extHost.protocol'; @extHostNamedCustomer(MainContext.MainThreadWindow) export class MainThreadWindow implements MainThreadWindowShape { private readonly proxy: ExtHostWindowShape; private readonly disposables = new DisposableStore(); - private readonly _tunnels = new Map>(); + private readonly resolved = new Map(); constructor( extHostContext: IExtHostContext, @IWindowService private readonly windowService: IWindowService, @IOpenerService private readonly openerService: IOpenerService, - @ITunnelService private readonly tunnelService: ITunnelService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService ) { this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostWindow); @@ -37,40 +32,24 @@ export class MainThreadWindow implements MainThreadWindowShape { dispose(): void { this.disposables.dispose(); - for (const tunnel of this._tunnels.values()) { - tunnel.then(tunnel => tunnel.dispose()); + for (const value of this.resolved.values()) { + value.dispose(); } - this._tunnels.clear(); + this.resolved.clear(); } $getWindowVisibility(): Promise { return this.windowService.isFocused(); } - async $openUri(uriComponent: UriComponents, options: IOpenUriOptions): Promise { - let uri = URI.revive(uriComponent); - if (options.allowTunneling && !!this.environmentService.configuration.remoteAuthority) { - const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri); - if (portMappingRequest) { - const tunnel = await this.getOrCreateTunnel(portMappingRequest.port); - if (tunnel) { - uri = uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}` }); - } - } - } - - return this.openerService.open(uri, { openExternal: true }); + async $openUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise { + const uri = URI.from(uriComponents); + return this.openerService.open(uri, { openExternal: true, allowTunneling: options.allowTunneling }); } - private getOrCreateTunnel(remotePort: number): Promise | undefined { - const existing = this._tunnels.get(remotePort); - if (existing) { - return existing; - } - const tunnel = this.tunnelService.openTunnel(remotePort); - if (tunnel) { - this._tunnels.set(remotePort, tunnel); - } - return tunnel; + async $resolveExternalUri(uriComponents: UriComponents, options: IOpenUriOptions): Promise { + const uri = URI.revive(uriComponents); + const result = await this.openerService.resolveExternalUri(uri, options); + return result.resolved; } } diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 31ec9b9e7a..26ce01c497 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -12,7 +12,6 @@ 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'; @@ -24,6 +23,7 @@ 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'; +import { IRequestService } from 'vs/platform/request/common/request'; @extHostNamedCustomer(MainContext.MainThreadWorkspace) export class MainThreadWorkspace implements MainThreadWorkspaceShape { @@ -40,7 +40,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { @ITextFileService private readonly _textFileService: ITextFileService, @IWorkspaceEditingService private readonly _workspaceEditingService: IWorkspaceEditingService, @INotificationService private readonly _notificationService: INotificationService, - @IWindowService private readonly _windowService: IWindowService, + @IRequestService private readonly _requestService: IRequestService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILabelService private readonly _labelService: ILabelService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @@ -218,6 +218,6 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } $resolveProxy(url: string): Promise { - return this._windowService.resolveProxy(url); + return this._requestService.resolveProxy(url); } } diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 55a348d834..e439baf56f 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -80,7 +80,10 @@ interface IUserFriendlyViewDescriptor { id: string; name: string; when?: string; + + // From 'remoteViewDescriptor' type group?: string; + remoteAuthority?: string; } const viewDescriptor: IJSONSchema = { @@ -101,7 +104,7 @@ const viewDescriptor: IJSONSchema = { } }; -const nestableViewDescriptor: IJSONSchema = { +const remoteViewDescriptor: IJSONSchema = { type: 'object', properties: { id: { @@ -119,6 +122,10 @@ const nestableViewDescriptor: IJSONSchema = { group: { description: localize('vscode.extension.contributes.view.group', 'Nested group in the viewlet'), type: 'string' + }, + remoteAuthority: { + description: localize('vscode.extension.contributes.view.remoteAuthority', 'The remote authority associated with this view'), + type: 'string' } } }; @@ -153,7 +160,7 @@ const viewsContribution: IJSONSchema = { 'remote': { description: localize('views.remote', "Contributes views to Remote container in the Activity bar. To contribute to this container, enableProposedApi needs to be turned on"), type: 'array', - items: nestableViewDescriptor, + items: remoteViewDescriptor, default: [] } }, @@ -427,7 +434,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution { order: order, extensionId: extension.description.identifier, originalContainerId: entry.key, - group: item.group + group: item.group, + remoteAuthority: item.remoteAuthority }; viewIds.push(viewDescriptor.id); diff --git a/src/vs/workbench/api/common/apiCommands.ts b/src/vs/workbench/api/common/apiCommands.ts index c453b79961..05fc93ed02 100644 --- a/src/vs/workbench/api/common/apiCommands.ts +++ b/src/vs/workbench/api/common/apiCommands.ts @@ -70,12 +70,16 @@ CommandsRegistry.registerCommand({ interface INewWindowAPICommandOptions { reuseWindow?: boolean; + remoteAuthority?: string; } export class NewWindowAPICommand { public static ID = 'vscode.newWindow'; public static execute(executor: ICommandsExecutor, options?: INewWindowAPICommandOptions): Promise { - return executor.executeCommand('_files.newWindow', options); + return executor.executeCommand('_files.newWindow', { + reuse: options && options.reuseWindow, + remoteAuthority: options && options.remoteAuthority + }); } } CommandsRegistry.registerCommand({ diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index 08564825f6..31d712567a 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -238,7 +238,7 @@ function validateProperties(configuration: IConfigurationNode, extension: IExten const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); jsonRegistry.registerSchema('vscode://schemas/workspaceConfig', { allowComments: true, - allowsTrailingCommas: true, + allowTrailingCommas: true, default: { folders: [ { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index d5d8703885..c0a07dcc39 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -27,7 +27,6 @@ import { ExtHostDocumentContentProvider } from 'vs/workbench/api/common/extHostD import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { ExtensionActivatedByAPI } from 'vs/workbench/api/common/extHostExtensionActivator'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem'; import { ExtHostFileSystemEventService } from 'vs/workbench/api/common/extHostFileSystemEventService'; @@ -250,6 +249,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I openExternal(uri: URI) { return extHostWindow.openUri(uri, { allowTunneling: !!initData.remote.isRemote }); }, + resolveExternalUri(uri: URI) { + checkProposedApiEnabled(extension); + return extHostWindow.resolveExternalUri(uri, { allowTunneling: !!initData.remote.isRemote }); + }, get remoteName() { return getRemoteName(initData.remote.authority); }, @@ -272,12 +275,12 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I getExtension(extensionId: string): Extension | undefined { const desc = extensionRegistry.getExtensionDescription(extensionId); if (desc) { - return new Extension(extensionService, desc, extensionKind); + return new Extension(extensionService, extension.identifier, desc, extensionKind); } return undefined; }, get all(): Extension[] { - return extensionRegistry.getAllExtensionDescriptions().map((desc) => new Extension(extensionService, desc, extensionKind)); + return extensionRegistry.getAllExtensionDescriptions().map((desc) => new Extension(extensionService, extension.identifier, desc, extensionKind)); }, get onDidChange() { return extensionRegistry.onDidChange; @@ -760,7 +763,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I registerDebugAdapterTrackerFactory(debugType: string, factory: vscode.DebugAdapterTrackerFactory) { return undefined; }, - startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSession?: vscode.DebugSession) { + startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSessionOrOptions?: vscode.DebugSession | vscode.DebugSessionOptions) { return undefined; }, addBreakpoints(breakpoints: vscode.Breakpoint[]) { @@ -907,6 +910,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I CallHierarchyOutgoingCall: extHostTypes.CallHierarchyOutgoingCall, CallHierarchyIncomingCall: extHostTypes.CallHierarchyIncomingCall, CallHierarchyItem: extHostTypes.CallHierarchyItem, + DebugConsoleMode: extHostTypes.DebugConsoleMode, Decoration: extHostTypes.Decoration, WebviewEditorState: extHostTypes.WebviewEditorState, UIKind: UIKind @@ -917,6 +921,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I class Extension implements vscode.Extension { private _extensionService: IExtHostExtensionService; + private _originExtensionId: ExtensionIdentifier; private _identifier: ExtensionIdentifier; readonly id: string; @@ -924,8 +929,9 @@ class Extension implements vscode.Extension { readonly packageJSON: IExtensionDescription; readonly extensionKind: vscode.ExtensionKind; - constructor(extensionService: IExtHostExtensionService, description: IExtensionDescription, kind: extHostTypes.ExtensionKind) { + constructor(extensionService: IExtHostExtensionService, originExtensionId: ExtensionIdentifier, description: IExtensionDescription, kind: extHostTypes.ExtensionKind) { this._extensionService = extensionService; + this._originExtensionId = originExtensionId; this._identifier = description.identifier; this.id = description.identifier.value; this.extensionPath = path.normalize(originalFSPath(description.extensionLocation)); @@ -945,6 +951,6 @@ class Extension implements vscode.Extension { } activate(): Thenable { - return this._extensionService.activateByIdWithErrors(this._identifier, new ExtensionActivatedByAPI(false)).then(() => this.exports); + return this._extensionService.activateByIdWithErrors(this._identifier, { startup: false, extensionId: this._originExtensionId, activationEvent: 'api' }).then(() => this.exports); } } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 55f777c71a..ad9220438a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -39,13 +39,14 @@ import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import * as tasks from 'vs/workbench/api/common/shared/tasks'; import { IRevealOptions, ITreeItem } from 'vs/workbench/common/views'; -import { IAdapterDescriptor, IConfig } from 'vs/workbench/contrib/debug/common/debug'; +import { IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug'; import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import { ITerminalDimensions, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol } from 'vs/workbench/services/extensions/common/proxyIdentifier'; import * as search from 'vs/workbench/services/search/common/search'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; // {{SQL CARBON EDIT}} import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views'; @@ -649,9 +650,9 @@ export interface MainThreadTaskShape extends IDisposable { } export interface MainThreadExtensionServiceShape extends IDisposable { - $activateExtension(extensionId: ExtensionIdentifier, activationEvent: string | null): Promise; + $activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; $onWillActivateExtension(extensionId: ExtensionIdentifier): void; - $onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string | null): void; + $onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void; $onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise; $onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void; $onExtensionHostExit(code: number): void; @@ -716,6 +717,11 @@ export interface IDebugConfiguration { [key: string]: any; } +export interface IStartDebuggingOptions { + parentSessionID?: DebugSessionUUID; + repl?: IDebugSessionReplMode; +} + export interface MainThreadDebugServiceShape extends IDisposable { $registerDebugTypes(debugTypes: string[]): void; $sessionCached(sessionID: string): void; @@ -726,7 +732,7 @@ export interface MainThreadDebugServiceShape extends IDisposable { $registerDebugAdapterDescriptorFactory(type: string, handle: number): Promise; $unregisterDebugConfigurationProvider(handle: number): void; $unregisterDebugAdapterDescriptorFactory(handle: number): void; - $startDebugging(folder: UriComponents | undefined, nameOrConfig: string | IDebugConfiguration, parentSessionID: string | undefined): Promise; + $startDebugging(folder: UriComponents | undefined, nameOrConfig: string | IDebugConfiguration, options: IStartDebuggingOptions): Promise; $setDebugSessionName(id: DebugSessionUUID, name: string): void; $customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Promise; $appendDebugConsole(value: string): void; @@ -742,6 +748,7 @@ export interface IOpenUriOptions { export interface MainThreadWindowShape extends IDisposable { $getWindowVisibility(): Promise; $openUri(uri: UriComponents, options: IOpenUriOptions): Promise; + $resolveExternalUri(uri: UriComponents, options: IOpenUriOptions): Promise; } // -- extension host @@ -882,7 +889,7 @@ export interface ExtHostExtensionServiceShape { $resolveAuthority(remoteAuthority: string, resolveAttempt: number): Promise; $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise; $activateByEvent(activationEvent: string): Promise; - $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise; + $activate(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; $setRemoteEnvironment(env: { [key: string]: string | null }): Promise; $deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise; diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 0809490625..31711a5c9e 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -239,7 +239,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { return this._uri; } - private _onDidUpdateCommentThread = new Emitter(); + private readonly _onDidUpdateCommentThread = new Emitter(); readonly onDidUpdateCommentThread = this._onDidUpdateCommentThread.event; set range(range: vscode.Range) { diff --git a/src/vs/workbench/api/common/extHostDebugService.ts b/src/vs/workbench/api/common/extHostDebugService.ts index 708bd00980..6180477b18 100644 --- a/src/vs/workbench/api/common/extHostDebugService.ts +++ b/src/vs/workbench/api/common/extHostDebugService.ts @@ -26,7 +26,7 @@ export interface IExtHostDebugService extends ExtHostDebugServiceShape { addBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise; removeBreakpoints(breakpoints0: vscode.Breakpoint[]): Promise; - startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSession?: vscode.DebugSession): Promise; + startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise; registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable; registerDebugAdapterDescriptorFactory(extension: IExtensionDescription, type: string, factory: vscode.DebugAdapterDescriptorFactory): vscode.Disposable; registerDebugAdapterTrackerFactory(type: string, factory: vscode.DebugAdapterTrackerFactory): vscode.Disposable; diff --git a/src/vs/workbench/api/common/extHostDocuments.ts b/src/vs/workbench/api/common/extHostDocuments.ts index 3b51b2db46..298957e986 100644 --- a/src/vs/workbench/api/common/extHostDocuments.ts +++ b/src/vs/workbench/api/common/extHostDocuments.ts @@ -15,10 +15,10 @@ import * as vscode from 'vscode'; export class ExtHostDocuments implements ExtHostDocumentsShape { - private _onDidAddDocument = new Emitter(); - private _onDidRemoveDocument = new Emitter(); - private _onDidChangeDocument = new Emitter(); - private _onDidSaveDocument = new Emitter(); + private readonly _onDidAddDocument = new Emitter(); + private readonly _onDidRemoveDocument = new Emitter(); + private readonly _onDidChangeDocument = new Emitter(); + private readonly _onDidSaveDocument = new Emitter(); readonly onDidAddDocument: Event = this._onDidAddDocument.event; readonly onDidRemoveDocument: Event = this._onDidRemoveDocument.event; diff --git a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts index 2472bf9105..b8bcf2973a 100644 --- a/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts +++ b/src/vs/workbench/api/common/extHostDocumentsAndEditors.ts @@ -7,20 +7,17 @@ import * as assert from 'vs/base/common/assert'; import { Emitter, Event } from 'vs/base/common/event'; import { dispose } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostDocumentsAndEditorsShape, IDocumentsAndEditorsDelta, MainContext } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; -import { Disposable } from 'vs/workbench/api/common/extHostTypes'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape { readonly _serviceBrand: undefined; - private _disposables: Disposable[] = []; - private _activeEditorId: string | null = null; private readonly _editors = new Map(); @@ -40,10 +37,6 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha @IExtHostRpcService private readonly _extHostRpc: IExtHostRpcService, ) { } - dispose() { - this._disposables = dispose(this._disposables); - } - $acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void { const removedDocuments: ExtHostDocumentData[] = []; diff --git a/src/vs/workbench/api/common/extHostExtensionActivator.ts b/src/vs/workbench/api/common/extHostExtensionActivator.ts index c39b99e46e..be047c9bea 100644 --- a/src/vs/workbench/api/common/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/common/extHostExtensionActivator.ts @@ -161,20 +161,13 @@ export interface IExtensionsActivatorHost { actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; } -export class ExtensionActivatedByEvent { - constructor( - public readonly startup: boolean, - public readonly activationEvent: string - ) { } +export interface ExtensionActivationReason { + readonly startup: boolean; + readonly extensionId: ExtensionIdentifier; + readonly activationEvent: string; } -export class ExtensionActivatedByAPI { - constructor( - public readonly startup: boolean - ) { } -} - -export type ExtensionActivationReason = ExtensionActivatedByEvent | ExtensionActivatedByAPI; +type ActivationIdAndReason = { id: ExtensionIdentifier, reason: ExtensionActivationReason }; export class ExtensionsActivator { @@ -217,12 +210,15 @@ export class ExtensionsActivator { return activatedExtension; } - public activateByEvent(activationEvent: string, reason: ExtensionActivationReason): Promise { + public activateByEvent(activationEvent: string, startup: boolean): Promise { if (this._alreadyActivatedEvents[activationEvent]) { return NO_OP_VOID_PROMISE; } const activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent); - return this._activateExtensions(activateExtensions.map(e => e.identifier), reason).then(() => { + return this._activateExtensions(activateExtensions.map(e => ({ + id: e.identifier, + reason: { startup, extensionId: e.identifier, activationEvent } + }))).then(() => { this._alreadyActivatedEvents[activationEvent] = true; }); } @@ -233,20 +229,23 @@ export class ExtensionsActivator { throw new Error('Extension `' + extensionId + '` is not known'); } - return this._activateExtensions([desc.identifier], reason); + return this._activateExtensions([{ + id: desc.identifier, + reason + }]); } /** * Handle semantics related to dependencies for `currentExtension`. * semantics: `redExtensions` must wait for `greenExtensions`. */ - private _handleActivateRequest(currentExtensionId: ExtensionIdentifier, greenExtensions: { [id: string]: ExtensionIdentifier; }, redExtensions: ExtensionIdentifier[]): void { - if (this._hostExtensionsMap.has(ExtensionIdentifier.toKey(currentExtensionId))) { - greenExtensions[ExtensionIdentifier.toKey(currentExtensionId)] = currentExtensionId; + private _handleActivateRequest(currentActivation: ActivationIdAndReason, greenExtensions: { [id: string]: ActivationIdAndReason; }, redExtensions: ActivationIdAndReason[]): void { + if (this._hostExtensionsMap.has(ExtensionIdentifier.toKey(currentActivation.id))) { + greenExtensions[ExtensionIdentifier.toKey(currentActivation.id)] = currentActivation; return; } - const currentExtension = this._registry.getExtensionDescription(currentExtensionId)!; + const currentExtension = this._registry.getExtensionDescription(currentActivation.id)!; const depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies); let currentExtensionGetsGreenLight = true; @@ -276,7 +275,10 @@ export class ExtensionsActivator { if (this._hostExtensionsMap.has(ExtensionIdentifier.toKey(depId))) { // must first wait for the dependency to activate currentExtensionGetsGreenLight = false; - greenExtensions[ExtensionIdentifier.toKey(depId)] = this._hostExtensionsMap.get(ExtensionIdentifier.toKey(depId))!; + greenExtensions[ExtensionIdentifier.toKey(depId)] = { + id: this._hostExtensionsMap.get(ExtensionIdentifier.toKey(depId))!, + reason: currentActivation.reason + }; continue; } @@ -284,7 +286,10 @@ export class ExtensionsActivator { if (depDesc) { // must first wait for the dependency to activate currentExtensionGetsGreenLight = false; - greenExtensions[ExtensionIdentifier.toKey(depId)] = depDesc.identifier; + greenExtensions[ExtensionIdentifier.toKey(depId)] = { + id: depDesc.identifier, + reason: currentActivation.reason + }; continue; } @@ -296,33 +301,33 @@ export class ExtensionsActivator { } if (currentExtensionGetsGreenLight) { - greenExtensions[ExtensionIdentifier.toKey(currentExtension.identifier)] = currentExtensionId; + greenExtensions[ExtensionIdentifier.toKey(currentExtension.identifier)] = currentActivation; } else { - redExtensions.push(currentExtensionId); + redExtensions.push(currentActivation); } } - private _activateExtensions(extensionIds: ExtensionIdentifier[], reason: ExtensionActivationReason): Promise { - // console.log('_activateExtensions: ', extensionIds.map(p => p.value)); - if (extensionIds.length === 0) { + private _activateExtensions(extensions: ActivationIdAndReason[]): Promise { + // console.log('_activateExtensions: ', extensions.map(p => p.id.value)); + if (extensions.length === 0) { return Promise.resolve(undefined); } - extensionIds = extensionIds.filter((p) => !this._activatedExtensions.has(ExtensionIdentifier.toKey(p))); - if (extensionIds.length === 0) { + extensions = extensions.filter((p) => !this._activatedExtensions.has(ExtensionIdentifier.toKey(p.id))); + if (extensions.length === 0) { return Promise.resolve(undefined); } - const greenMap: { [id: string]: ExtensionIdentifier; } = Object.create(null), - red: ExtensionIdentifier[] = []; + const greenMap: { [id: string]: ActivationIdAndReason; } = Object.create(null), + red: ActivationIdAndReason[] = []; - for (let i = 0, len = extensionIds.length; i < len; i++) { - this._handleActivateRequest(extensionIds[i], greenMap, red); + for (let i = 0, len = extensions.length; i < len; i++) { + this._handleActivateRequest(extensions[i], greenMap, red); } // Make sure no red is also green for (let i = 0, len = red.length; i < len; i++) { - const redExtensionKey = ExtensionIdentifier.toKey(red[i]); + const redExtensionKey = ExtensionIdentifier.toKey(red[i].id); if (greenMap[redExtensionKey]) { delete greenMap[redExtensionKey]; } @@ -330,16 +335,16 @@ export class ExtensionsActivator { const green = Object.keys(greenMap).map(id => greenMap[id]); - // console.log('greenExtensions: ', green.map(p => p.id)); - // console.log('redExtensions: ', red.map(p => p.id)); + // console.log('greenExtensions: ', green.map(p => p.id.value)); + // console.log('redExtensions: ', red.map(p => p.id.value)); if (red.length === 0) { // Finally reached only leafs! - return Promise.all(green.map((p) => this._activateExtension(p, reason))).then(_ => undefined); + return Promise.all(green.map((p) => this._activateExtension(p.id, p.reason))).then(_ => undefined); } - return this._activateExtensions(green, reason).then(_ => { - return this._activateExtensions(red, reason); + return this._activateExtensions(green).then(_ => { + return this._activateExtensions(red); }); } diff --git a/src/vs/workbench/api/common/extHostExtensionService.ts b/src/vs/workbench/api/common/extHostExtensionService.ts index e8a05f05d2..663fb6f89e 100644 --- a/src/vs/workbench/api/common/extHostExtensionService.ts +++ b/src/vs/workbench/api/common/extHostExtensionService.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostExtensionServiceShape, IInitData, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape, IResolveAuthorityResult } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostConfiguration, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; -import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from 'vs/workbench/api/common/extHostExtensionActivator'; +import { ActivatedExtension, EmptyExtension, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionModule, HostExtension, ExtensionActivationTimesFragment } from 'vs/workbench/api/common/extHostExtensionActivator'; import { ExtHostStorage, IExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; @@ -60,6 +60,7 @@ type TelemetryActivationEventFragment = { activationEvents: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; isBuiltin: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; reason: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; + reasonId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' }; }; export abstract class AbstractExtHostExtensionService implements ExtHostExtensionServiceShape { @@ -139,8 +140,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise => { if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) { - const activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); - await this._mainThreadExtensionsProxy.$activateExtension(extensionId, activationEvent); + await this._mainThreadExtensionsProxy.$activateExtension(extensionId, reason); return new HostExtension(); } const extensionDescription = this._registry.getExtensionDescription(extensionId)!; @@ -195,8 +195,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio } private _activateByEvent(activationEvent: string, startup: boolean): Promise { - const reason = new ExtensionActivatedByEvent(startup, activationEvent); - return this._activator.activateByEvent(activationEvent, reason); + return this._activator.activateByEvent(activationEvent, startup); } private _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { @@ -285,8 +284,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio this._mainThreadExtensionsProxy.$onWillActivateExtension(extensionDescription.identifier); return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => { const activationTimes = activatedExtension.activationTimes; - const activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); - this._mainThreadExtensionsProxy.$onDidActivateExtension(extensionDescription.identifier, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, activationEvent); + this._mainThreadExtensionsProxy.$onDidActivateExtension(extensionDescription.identifier, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, reason); this._logExtensionActivationTimes(extensionDescription, reason, 'success', activationTimes); return activatedExtension; }, (err) => { @@ -467,7 +465,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio if (await this._hostUtils.exists(path.join(URI.revive(uri).fsPath, fileName))) { // the file was found return ( - this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${fileName}`)) + this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContains:${fileName}` }) .then(undefined, err => console.error(err)) ); } @@ -488,7 +486,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio const timer = setTimeout(async () => { tokenSource.cancel(); - this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContainsTimeout:${globPatterns.join(',')}`)) + this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContainsTimeout:${globPatterns.join(',')}` }) .then(undefined, err => console.error(err)); }, AbstractExtHostExtensionService.WORKSPACE_CONTAINS_TIMEOUT); @@ -507,7 +505,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio if (exists) { // a file was found matching one of the glob patterns return ( - this._activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${globPatterns.join(',')}`)) + this._activateById(extensionId, { startup: true, extensionId, activationEvent: `workspaceContains:${globPatterns.join(',')}` }) .then(undefined, err => console.error(err)) ); } @@ -681,13 +679,13 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio ); } - public async $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise { + public async $activate(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { await this._readyToRunExtensions.wait(); if (!this._registry.getExtensionDescription(extensionId)) { // unknown extension => ignore return false; } - await this._activateById(extensionId, new ExtensionActivatedByEvent(false, activationEvent)); + await this._activateById(extensionId, reason); return true; } @@ -743,12 +741,10 @@ type TelemetryActivationEvent = { activationEvents: string | null; isBuiltin: boolean; reason: string; + reasonId: string; }; function getTelemetryActivationEvent(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): TelemetryActivationEvent { - const reasonStr = reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : - reason instanceof ExtensionActivatedByAPI ? 'api' : - ''; const event = { id: extensionDescription.identifier.value, name: extensionDescription.name, @@ -756,7 +752,8 @@ function getTelemetryActivationEvent(extensionDescription: IExtensionDescription publisherDisplayName: extensionDescription.publisher, activationEvents: extensionDescription.activationEvents ? extensionDescription.activationEvents.join(',') : null, isBuiltin: extensionDescription.isBuiltin, - reason: reasonStr + reason: reason.activationEvent, + reasonId: reason.extensionId.value, }; return event; diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts index d295d969dd..e295d4f4b3 100644 --- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts @@ -16,9 +16,9 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' class FileSystemWatcher implements vscode.FileSystemWatcher { - private _onDidCreate = new Emitter(); - private _onDidChange = new Emitter(); - private _onDidDelete = new Emitter(); + private readonly _onDidCreate = new Emitter(); + private readonly _onDidChange = new Emitter(); + private readonly _onDidDelete = new Emitter(); private _disposable: Disposable; private _config: number; diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index 3e0ccfb5a6..d05d24ad64 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -246,10 +246,10 @@ class ExtHostQuickInput implements QuickInput { private _placeholder: string; private _buttons: QuickInputButton[] = []; private _handlesToButtons = new Map(); - private _onDidAcceptEmitter = new Emitter(); - private _onDidChangeValueEmitter = new Emitter(); - private _onDidTriggerButtonEmitter = new Emitter(); - private _onDidHideEmitter = new Emitter(); + private readonly _onDidAcceptEmitter = new Emitter(); + private readonly _onDidChangeValueEmitter = new Emitter(); + private readonly _onDidTriggerButtonEmitter = new Emitter(); + private readonly _onDidHideEmitter = new Emitter(); private _updateTimeout: any; private _pendingUpdate: TransferQuickInput = { id: this._id }; @@ -486,9 +486,9 @@ class ExtHostQuickPick extends ExtHostQuickInput implem private _matchOnDescription = true; private _matchOnDetail = true; private _activeItems: T[] = []; - private _onDidChangeActiveEmitter = new Emitter(); + private readonly _onDidChangeActiveEmitter = new Emitter(); private _selectedItems: T[] = []; - private _onDidChangeSelectionEmitter = new Emitter(); + private readonly _onDidChangeSelectionEmitter = new Emitter(); constructor(proxy: MainThreadQuickOpenShape, extensionId: ExtensionIdentifier, enableProposedApi: boolean, onDispose: () => void) { super(proxy, extensionId, onDispose); diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 67b56749c1..9bac08e22c 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -157,7 +157,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { this.updateValue(value); } - private _onDidChange = new Emitter(); + private readonly _onDidChange = new Emitter(); get onDidChange(): Event { return this._onDidChange.event; @@ -233,9 +233,9 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG private _resourceStatesMap: Map = new Map(); private _resourceStatesCommandsMap: Map = new Map(); - private _onDidUpdateResourceStates = new Emitter(); + private readonly _onDidUpdateResourceStates = new Emitter(); readonly onDidUpdateResourceStates = this._onDidUpdateResourceStates.event; - private _onDidDispose = new Emitter(); + private readonly _onDidDispose = new Emitter(); readonly onDidDispose = this._onDidDispose.event; private _handlesSnapshot: number[] = []; @@ -451,7 +451,7 @@ class ExtHostSourceControl implements vscode.SourceControl { return this._selected; } - private _onDidChangeSelection = new Emitter(); + private readonly _onDidChangeSelection = new Emitter(); readonly onDidChangeSelection = this._onDidChangeSelection.event; private handle: number = ExtHostSourceControl._handlePool++; @@ -535,7 +535,7 @@ export class ExtHostSCM implements ExtHostSCMShape { private _sourceControls: Map = new Map(); private _sourceControlsByExtension: Map = new Map(); - private _onDidChangeActiveProvider = new Emitter(); + private readonly _onDidChangeActiveProvider = new Emitter(); get onDidChangeActiveProvider(): Event { return this._onDidChangeActiveProvider.event; } private _selectedSourceControlHandles = new Set(); diff --git a/src/vs/workbench/api/common/extHostStorage.ts b/src/vs/workbench/api/common/extHostStorage.ts index b1827092e8..dded7e593d 100644 --- a/src/vs/workbench/api/common/extHostStorage.ts +++ b/src/vs/workbench/api/common/extHostStorage.ts @@ -20,7 +20,7 @@ export class ExtHostStorage implements ExtHostStorageShape { private _proxy: MainThreadStorageShape; - private _onDidChangeStorage = new Emitter(); + private readonly _onDidChangeStorage = new Emitter(); readonly onDidChangeStorage = this._onDidChangeStorage.event; constructor(mainContext: IExtHostRpcService) { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index a05b4c48c6..cf1160e40e 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2352,6 +2352,22 @@ export enum CommentMode { //#endregion +//#region debug +export enum DebugConsoleMode { + /** + * Debug session should have a separate debug console. + */ + Separate = 0, + + /** + * Debug session should share debug console with its parent session. + * This value has no effect for sessions which do not have a parent session. + */ + MergeWithParent = 1 +} + +//#endregion + @es5ClassCompat export class QuickInputButtons { diff --git a/src/vs/workbench/api/common/extHostWindow.ts b/src/vs/workbench/api/common/extHostWindow.ts index b0f720493a..fb64f6e6d1 100644 --- a/src/vs/workbench/api/common/extHostWindow.ts +++ b/src/vs/workbench/api/common/extHostWindow.ts @@ -18,7 +18,7 @@ export class ExtHostWindow implements ExtHostWindowShape { private _proxy: MainThreadWindowShape; - private _onDidChangeWindowState = new Emitter(); + private readonly _onDidChangeWindowState = new Emitter(); readonly onDidChangeWindowState: Event = this._onDidChangeWindowState.event; private _state = ExtHostWindow.InitialState; @@ -53,4 +53,15 @@ export class ExtHostWindow implements ExtHostWindowShape { } return this._proxy.$openUri(stringOrUri, options); } + + async resolveExternalUri(uri: URI, options: IOpenUriOptions): Promise { + if (isFalsyOrWhitespace(uri.scheme)) { + return Promise.reject('Invalid scheme - cannot be empty'); + } else if (!new Set([Schemas.http, Schemas.https]).has(uri.scheme)) { + return Promise.reject(`Invalid scheme '${uri.scheme}'`); + } + + const result = await this._proxy.$resolveExternalUri(uri, options); + return URI.from(result); + } } diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index a1b8cd5f4a..3d3b9e270f 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -39,8 +39,9 @@ namespace schema { case 'menuBar/file': return MenuId.MenubarFileMenu; case 'scm/title': return MenuId.SCMTitle; case 'scm/sourceControl': return MenuId.SCMSourceControl; - case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext; case 'scm/resourceState/context': return MenuId.SCMResourceContext; + case 'scm/resourceFolder/context': return MenuId.SCMResourceFolderContext; + case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext; case 'scm/change/title': return MenuId.SCMChangeContext; case 'statusBar/windowIndicator': return MenuId.StatusBarWindowIndicatorMenu; case 'view/title': return MenuId.ViewTitle; diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 217d2926f1..9db6409bf3 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -14,7 +14,7 @@ import { IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto } from 'vs/workbench/api/common/extHost.protocol'; import * as vscode from 'vscode'; -import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint } from 'vs/workbench/api/common/extHostTypes'; +import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint, DebugConsoleMode } from 'vs/workbench/api/common/extHostTypes'; import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; @@ -252,8 +252,11 @@ export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugSe return this._debugServiceProxy.$unregisterBreakpoints(ids, fids, dids); } - public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSession?: vscode.DebugSession): Promise { - return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig, parentSession ? parentSession.id : undefined); + public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, options: vscode.DebugSessionOptions): Promise { + return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig, { + parentSessionID: options.parentSession ? options.parentSession.id : undefined, + repl: options.consoleMode === DebugConsoleMode.MergeWithParent ? 'mergeWithParent' : 'separate' + }); } public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable { diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index 48469cbcc3..e4f5b43deb 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -6,7 +6,6 @@ import 'vs/css!./media/screencast'; import { Action } from 'vs/base/common/actions'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import * as nls from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { domEvent } from 'vs/base/browser/event'; @@ -35,8 +34,7 @@ class InspectContextKeysAction extends Action { constructor( id: string, label: string, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IWindowService private readonly windowService: IWindowService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(id, label); } @@ -82,7 +80,6 @@ class InspectContextKeysAction extends Action { const context = this.contextKeyService.getContext(e.target as HTMLElement) as Context; console.log(context.collectAllValues()); - this.windowService.openDevTools(); dispose(disposables); }, null, disposables); @@ -203,16 +200,13 @@ class LogStorageAction extends Action { constructor( id: string, label: string, - @IStorageService private readonly storageService: IStorageService, - @IWindowService private readonly windowService: IWindowService + @IStorageService private readonly storageService: IStorageService ) { super(id, label); } - run(): Promise { + async run(): Promise { this.storageService.logStorage(); - - return this.windowService.openDevTools(); } } diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index dc75450968..80ee997a4a 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -118,7 +118,7 @@ export class ToggleEditorLayoutAction extends Action { ) { super(id, label); - this.class = 'flip-editor-layout'; + this.class = 'codicon-editor-layout'; this.updateEnablement(); this.registerListeners(); @@ -230,7 +230,7 @@ export class ToggleEditorVisibilityAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorVisibilityAction, ToggleEditorVisibilityAction.ID, ToggleEditorVisibilityAction.LABEL), 'View: Toggle Editor Area Visibility', viewCategory, ContextKeyExpr.equals('config.workbench.useExperimentalGridLayout', true)); +registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleEditorVisibilityAction, ToggleEditorVisibilityAction.ID, ToggleEditorVisibilityAction.LABEL), 'View: Toggle Editor Area Visibility', viewCategory); export class ToggleSidebarVisibilityAction extends Action { diff --git a/src/vs/workbench/browser/actions/media/actions.css b/src/vs/workbench/browser/actions/media/actions.css index 7048de665e..9f532bf471 100644 --- a/src/vs/workbench/browser/actions/media/actions.css +++ b/src/vs/workbench/browser/actions/media/actions.css @@ -2,24 +2,3 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -.vs .monaco-workbench .flip-editor-layout { - background-image: url('layout-light.svg'); -} - -.vs-dark .monaco-workbench .flip-editor-layout { - background-image: url('layout-dark.svg'); -} - -.hc-black .monaco-workbench .flip-editor-layout { - background-image: url('layout-hc.svg'); -} - -.vs .action-remove-from-recently-opened { - background: url("remove-light.svg") center center no-repeat; -} - -.vs-dark .action-remove-from-recently-opened, -.hc-black .action-remove-from-recently-opened { - background: url("remove-dark.svg") center center no-repeat; -} diff --git a/src/vs/workbench/browser/actions/media/layout-dark.svg b/src/vs/workbench/browser/actions/media/layout-dark.svg deleted file mode 100644 index f7e50f481b..0000000000 --- a/src/vs/workbench/browser/actions/media/layout-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/actions/media/layout-hc.svg b/src/vs/workbench/browser/actions/media/layout-hc.svg deleted file mode 100644 index 40c1b46b19..0000000000 --- a/src/vs/workbench/browser/actions/media/layout-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/actions/media/layout-light.svg b/src/vs/workbench/browser/actions/media/layout-light.svg deleted file mode 100644 index 1f5e31274e..0000000000 --- a/src/vs/workbench/browser/actions/media/layout-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/actions/media/remove-dark.svg b/src/vs/workbench/browser/actions/media/remove-dark.svg deleted file mode 100644 index f8af265cc4..0000000000 --- a/src/vs/workbench/browser/actions/media/remove-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/actions/media/remove-light.svg b/src/vs/workbench/browser/actions/media/remove-light.svg deleted file mode 100644 index 7acc410338..0000000000 --- a/src/vs/workbench/browser/actions/media/remove-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 1b38d90ee7..6944c46e03 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -14,7 +14,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IsFullscreenContext, IsDevelopmentContext, IsMacNativeContext } from 'vs/workbench/browser/contextkeys'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickInputButton, IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -31,6 +30,7 @@ import { IKeyMods } from 'vs/base/parts/quickopen/common/quickOpen'; import { isMacintosh } from 'vs/base/common/platform'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { inQuickOpenContext, getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export const inRecentFilesPickerContextKey = 'inRecentFilesPicker'; @@ -195,15 +195,13 @@ class ToggleFullScreenAction extends Action { constructor( id: string, label: string, - @IWindowService private readonly windowService: IWindowService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService) { + @IHostService private readonly hostService: IHostService + ) { super(id, label); } run(): Promise { - const container = this.layoutService.getWorkbenchElement(); - - return this.windowService.toggleFullScreen(container); + return this.hostService.toggleFullScreen(); } } @@ -215,13 +213,13 @@ export class ReloadWindowAction extends Action { constructor( id: string, label: string, - @IWindowService private readonly windowService: IWindowService + @IHostService private readonly hostService: IHostService ) { super(id, label); } async run(): Promise { - await this.windowService.reloadWindow(); + await this.hostService.reload(); return true; } @@ -245,11 +243,30 @@ class ShowAboutDialogAction extends Action { } } +export class NewWindowAction extends Action { + + static readonly ID = 'workbench.action.newWindow'; + static LABEL = nls.localize('newWindow', "New Window"); + + constructor( + id: string, + label: string, + @IHostService private readonly hostService: IHostService + ) { + super(id, label); + } + + run(): Promise { + return this.hostService.openEmptyWindow(); + } +} + const registry = Registry.as(Extensions.WorkbenchActions); // --- Actions Registration const fileCategory = nls.localize('file', "File"); +registry.registerWorkbenchAction(new SyncActionDescriptor(NewWindowAction, NewWindowAction.ID, NewWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window'); registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', fileCategory); @@ -295,6 +312,15 @@ KeybindingsRegistry.registerKeybindingRule({ // --- Menu Registration +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '1_new', + command: { + id: NewWindowAction.ID, + title: nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window") + }, + order: 2 +}); + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { title: nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent"), submenu: MenuId.MenubarRecentMenu, diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 1c6e1f1840..f1b9e6fbc9 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -13,10 +13,14 @@ import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/c 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 { MenuRegistry, MenuId, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { WorkbenchStateContext, SupportsWorkspacesContext } from 'vs/workbench/browser/contextkeys'; +import { WorkbenchStateContext, SupportsWorkspacesContext, WorkspaceFolderCountContext } 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'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; export class OpenFileAction extends Action { @@ -90,6 +94,32 @@ export class OpenWorkspaceAction extends Action { } } +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, + @IHostService private readonly hostService: IHostService + ) { + 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.hostService.closeWorkspace(); + } +} + export class OpenWorkspaceConfigFileAction extends Action { static readonly ID = 'workbench.action.openWorkspaceConfigFile'; @@ -170,6 +200,7 @@ 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, SupportsWorkspacesContext); +registry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'File: Close Workspace', workspacesCategory, SupportsWorkspacesContext); // --- Menu Registration @@ -194,3 +225,24 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { }, when: WorkbenchStateContext.isEqualTo('workspace') }); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '6_close', + command: { + id: CloseWorkspaceAction.ID, + title: nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), + precondition: WorkspaceFolderCountContext.notEqualsTo('0') + }, + order: 3, + when: WorkbenchStateContext.notEqualsTo('workspace') +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '6_close', + command: { + id: CloseWorkspaceAction.ID, + title: nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace") + }, + order: 3, + when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), SupportsWorkspacesContext) +}); diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index e5c2df5af2..1ae8ec799f 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -20,7 +20,7 @@ import { DataTransfers } from 'vs/base/browser/dnd'; import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { MIME_BINARY } from 'vs/base/common/mime'; -import { isWindows, isLinux } from 'vs/base/common/platform'; +import { isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; @@ -328,9 +328,12 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: event.dataTransfer.setData(DataTransfers.TEXT, sources.map(source => source.resource.scheme === Schemas.file ? normalize(normalizeDriveLetter(source.resource.fsPath)) : source.resource.toString()).join(lineDelimiter)); const envService = accessor.get(IWorkbenchEnvironmentService); - if (!(isLinux && envService.configuration.remoteAuthority)) { + const hasRemote = !!envService.configuration.remoteAuthority; + if ( + !(isLinux && hasRemote) && // Not supported on linux remote due to chrome limitation https://github.com/microsoft/vscode-remote-release/issues/849 + !isWeb // Does not seem to work anymore when running from web, the file ends up being empty (and PWA crashes) + ) { // Download URL: enables support to drag a tab as file to desktop (only single file supported) - // Not supported on linux remote due to chrome limitation https://github.com/microsoft/vscode-remote-release/issues/849 event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(firstSource.resource), firstSource.resource.toString()].join(':')); } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index fb8b886d95..c688ce6ec6 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -6,7 +6,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, position, size, EventHelper, Dimension } from 'vs/base/browser/dom'; -import { onDidChangeFullscreen, isFullscreen, getZoomFactor } from 'vs/base/browser/browser'; +import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { Registry } from 'vs/platform/registry/common/platform'; import { isWindows, isLinux, isMacintosh, isWeb, isNative } from 'vs/base/common/platform'; @@ -14,7 +14,7 @@ import { pathsToEditors } from 'vs/workbench/common/editor'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; -import { Position, Parts, IWorkbenchLayoutService, ILayoutOptions } from 'vs/workbench/services/layout/browser/layoutService'; +import { Position, Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -23,12 +23,12 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { LifecyclePhase, StartupKind, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWindowService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { MenuBarVisibility, getTitleBarStyle } 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 { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { Grid, SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction } from 'vs/base/browser/ui/grid/grid'; -import { WorkbenchLegacyLayout } from 'vs/workbench/browser/legacyLayout'; +import { SerializableGrid, ISerializableView, ISerializedGrid, Orientation, ISerializedNode, ISerializedLeafNode, Direction, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; @@ -58,7 +58,8 @@ enum Storage { PANEL_HIDDEN = 'workbench.panel.hidden', PANEL_POSITION = 'workbench.panel.location', PANEL_SIZE = 'workbench.panel.size', - PANEL_SIZE_BEFORE_MAXIMIZED = 'workbench.panel.sizeBeforeMaximized', + PANEL_LAST_NON_MAXIMIZED_WIDTH = 'workbench.panel.lastNonMaximizedWidth', + PANEL_LAST_NON_MAXIMIZED_HEIGHT = 'workbench.panel.lastNonMaximizedHeight', EDITOR_HIDDEN = 'workbench.editor.hidden', @@ -82,6 +83,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi _serviceBrand: undefined; + //#region Events + private readonly _onTitleBarVisibilityChange: Emitter = this._register(new Emitter()); readonly onTitleBarVisibilityChange: Event = this._onTitleBarVisibilityChange.event; @@ -100,6 +103,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private readonly _onLayout = this._register(new Emitter()); readonly onLayout: Event = this._onLayout.event; + //#endregion + private _dimension: IDimension; get dimension(): IDimension { return this._dimension; } @@ -108,7 +113,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private parts: Map = new Map(); - private workbenchGrid: SerializableGrid | WorkbenchLegacyLayout; + private workbenchGrid: SerializableGrid; private disposed: boolean; @@ -123,7 +128,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private configurationService: IConfigurationService; private lifecycleService: ILifecycleService; private storageService: IStorageService; - private windowService: IWindowService; + private hostService: IHostService; private editorService: IEditorService; private editorGroupService: IEditorGroupsService; private panelService: IPanelService; @@ -161,8 +166,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi panel: { hidden: false, - sizeBeforeMaximize: 0, position: Position.BOTTOM, + lastNonMaximizedWidth: 300, + lastNonMaximizedHeight: 300, panelToRestore: undefined as string | undefined }, @@ -198,7 +204,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.environmentService = accessor.get(IWorkbenchEnvironmentService); this.configurationService = accessor.get(IConfigurationService); this.lifecycleService = accessor.get(ILifecycleService); - this.windowService = accessor.get(IWindowService); + this.hostService = accessor.get(IHostService); this.contextService = accessor.get(IWorkspaceContextService); this.storageService = accessor.get(IStorageService); this.backupFileService = accessor.get(IBackupFileService); @@ -222,13 +228,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private registerLayoutListeners(): void { // Restore editor if hidden and it changes + // The editor service will always trigger this + // on startup so we can ignore the first one + let firstTimeEditorActivation = true; const showEditorIfHidden = () => { - if (this.state.editor.hidden) { + if (!firstTimeEditorActivation && this.state.editor.hidden) { this.toggleMaximizedPanel(); } + + firstTimeEditorActivation = false; }; - + // Restore editor part on any editor change this._register(this.editorService.onDidVisibleEditorsChange(showEditorIfHidden)); this._register(this.editorGroupService.onDidActivateGroup(showEditorIfHidden)); @@ -263,9 +274,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default')) { this._onTitleBarVisibilityChange.fire(); - if (this.workbenchGrid instanceof SerializableGrid) { - this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); - } + // Propagate to grid + this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); this.layout(); } @@ -290,9 +300,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { this._onTitleBarVisibilityChange.fire(); - if (this.workbenchGrid instanceof SerializableGrid) { - this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); - } + // Propagate to grid + this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); this.layout(); // handle title bar when fullscreen changes } @@ -356,23 +365,19 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi sideBar.updateStyles(); // Layout - if (this.workbenchGrid instanceof Grid) { - if (!wasHidden) { - this.state.sideBar.width = this.workbenchGrid.getViewSize(this.sideBarPartView).width; - } - - if (position === Position.LEFT) { - this.workbenchGrid.moveViewTo(this.activityBarPartView, [1, 0]); - this.workbenchGrid.moveViewTo(this.sideBarPartView, [1, 1]); - } else { - this.workbenchGrid.moveViewTo(this.sideBarPartView, [1, 4]); - this.workbenchGrid.moveViewTo(this.activityBarPartView, [1, 4]); - } - - this.layout(); - } else { - this.workbenchGrid.layout(); + if (!wasHidden) { + this.state.sideBar.width = this.workbenchGrid.getViewSize(this.sideBarPartView).width; } + + if (position === Position.LEFT) { + this.workbenchGrid.moveViewTo(this.activityBarPartView, [1, 0]); + this.workbenchGrid.moveViewTo(this.sideBarPartView, [1, 1]); + } else { + this.workbenchGrid.moveViewTo(this.sideBarPartView, [1, 4]); + this.workbenchGrid.moveViewTo(this.activityBarPartView, [1, 4]); + } + + this.layout(); } private initLayoutState(lifecycleService: ILifecycleService, fileService: IFileService): void { @@ -410,6 +415,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + // Editor visibility + this.state.editor.hidden = this.storageService.getBoolean(Storage.EDITOR_HIDDEN, StorageScope.WORKSPACE, false); + // Editor centered layout this.state.editor.restoreCentered = this.storageService.getBoolean(Storage.CENTERED_LAYOUT_ENABLED, StorageScope.WORKSPACE, false); @@ -439,7 +447,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Panel size before maximized - this.state.panel.sizeBeforeMaximize = this.storageService.getNumber(Storage.PANEL_SIZE_BEFORE_MAXIMIZED, StorageScope.GLOBAL, 0); + this.state.panel.lastNonMaximizedHeight = this.storageService.getNumber(Storage.PANEL_LAST_NON_MAXIMIZED_HEIGHT, StorageScope.GLOBAL, 300); + this.state.panel.lastNonMaximizedWidth = this.storageService.getNumber(Storage.PANEL_LAST_NON_MAXIMIZED_WIDTH, StorageScope.GLOBAL, 300); // Statusbar visibility this.state.statusBar.hidden = !this.configurationService.getValue(Settings.STATUSBAR_VISIBLE); @@ -450,7 +459,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // TODO @misolori update this when finished this.state.octiconsUpdate.enabled = this.configurationService.getValue(Settings.OCTICONS_UPDATE_ENABLED); this.setOcticonsUpdate(this.state.octiconsUpdate.enabled); - } private resolveEditorsToOpen(fileService: IFileService): Promise | IResourceEditor[] { @@ -583,7 +591,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi case Parts.ACTIVITYBAR_PART: return !this.state.activityBar.hidden; case Parts.EDITOR_PART: - return this.workbenchGrid instanceof Grid ? !this.state.editor.hidden : true; + return !this.state.editor.hidden; } return true; // any other part cannot be hidden @@ -596,15 +604,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi getTitleBarOffset(): number { let offset = 0; if (this.isVisible(Parts.TITLEBAR_PART)) { - if (this.workbenchGrid instanceof Grid) { - offset = this.getPart(Parts.TITLEBAR_PART).maximumHeight; - } else { - offset = this.workbenchGrid.partLayoutInfo.titlebar.height; - - if (isMacintosh || this.state.menuBar.visibility === 'hidden') { - offset /= getZoomFactor(); - } - } + offset = this.getPart(Parts.TITLEBAR_PART).maximumHeight; } return offset; @@ -728,7 +728,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } if (toggleFullScreen) { - this.windowService.toggleFullScreen(); + this.hostService.toggleFullScreen(); } // Event @@ -762,16 +762,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Propagate to grid - if (this.workbenchGrid instanceof Grid) { - this.workbenchGrid.setViewVisible(this.statusBarPartView, !hidden); - } - - // Layout - if (!skipLayout) { - if (!(this.workbenchGrid instanceof Grid)) { - this.workbenchGrid.layout(); - } - } + this.workbenchGrid.setViewVisible(this.statusBarPartView, !hidden); } // TODO @misolori update this when finished @@ -784,7 +775,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } else { document.body.dataset.octiconsUpdate = ''; } - } protected createWorkbenchLayout(instantiationService: IInstantiationService): void { @@ -803,87 +793,67 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.panelPartView = panelPart; this.statusBarPartView = statusBar; - if (this.configurationService.getValue('workbench.useExperimentalGridLayout')) { - const viewMap = { - [Parts.ACTIVITYBAR_PART]: this.activityBarPartView, - [Parts.TITLEBAR_PART]: this.titleBarPartView, - [Parts.EDITOR_PART]: this.editorPartView, - [Parts.PANEL_PART]: this.panelPartView, - [Parts.SIDEBAR_PART]: this.sideBarPartView, - [Parts.STATUSBAR_PART]: this.statusBarPartView - }; + const viewMap = { + [Parts.ACTIVITYBAR_PART]: this.activityBarPartView, + [Parts.TITLEBAR_PART]: this.titleBarPartView, + [Parts.EDITOR_PART]: this.editorPartView, + [Parts.PANEL_PART]: this.panelPartView, + [Parts.SIDEBAR_PART]: this.sideBarPartView, + [Parts.STATUSBAR_PART]: this.statusBarPartView + }; - const fromJSON = ({ type }: { type: Parts }) => viewMap[type]; - const workbenchGrid = SerializableGrid.deserialize( - this.createGridDescriptor(), - { fromJSON }, - { proportionalLayout: false } - ); + const fromJSON = ({ type }: { type: Parts }) => viewMap[type]; + const workbenchGrid = SerializableGrid.deserialize( + this.createGridDescriptor(), + { fromJSON }, + { proportionalLayout: false } + ); - this.container.prepend(workbenchGrid.element); - this.workbenchGrid = workbenchGrid; + this.container.prepend(workbenchGrid.element); + this.workbenchGrid = workbenchGrid; - this._register((this.sideBarPartView as SidebarPart).onDidVisibilityChange((visible) => { - this.setSideBarHidden(!visible, true); - })); + this._register((this.sideBarPartView as SidebarPart).onDidVisibilityChange((visible) => { + this.setSideBarHidden(!visible, true); + })); - this._register((this.panelPartView as PanelPart).onDidVisibilityChange((visible) => { - this.setPanelHidden(!visible, true); - })); + this._register((this.panelPartView as PanelPart).onDidVisibilityChange((visible) => { + this.setPanelHidden(!visible, true); + })); - this._register((this.editorPartView as PanelPart).onDidVisibilityChange((visible) => { - this.setEditorHidden(!visible, true); - })); + this._register((this.editorPartView as PanelPart).onDidVisibilityChange((visible) => { + this.setEditorHidden(!visible, true); + })); - this._register(this.storageService.onWillSaveState(() => { - const grid = this.workbenchGrid as SerializableGrid; + this._register(this.storageService.onWillSaveState(() => { + const grid = this.workbenchGrid as SerializableGrid; - const sideBarSize = this.state.sideBar.hidden - ? grid.getViewCachedVisibleSize(this.sideBarPartView) - : grid.getViewSize(this.sideBarPartView).width; + const sideBarSize = this.state.sideBar.hidden + ? grid.getViewCachedVisibleSize(this.sideBarPartView) + : grid.getViewSize(this.sideBarPartView).width; - this.storageService.store(Storage.SIDEBAR_SIZE, sideBarSize, StorageScope.GLOBAL); + this.storageService.store(Storage.SIDEBAR_SIZE, sideBarSize, StorageScope.GLOBAL); - const panelSize = this.state.panel.hidden - ? grid.getViewCachedVisibleSize(this.panelPartView) - : (this.state.panel.position === Position.BOTTOM ? grid.getViewSize(this.panelPartView).height : grid.getViewSize(this.panelPartView).width); + const panelSize = this.state.panel.hidden + ? grid.getViewCachedVisibleSize(this.panelPartView) + : (this.state.panel.position === Position.BOTTOM ? grid.getViewSize(this.panelPartView).height : grid.getViewSize(this.panelPartView).width); - this.storageService.store(Storage.PANEL_SIZE, panelSize, StorageScope.GLOBAL); + this.storageService.store(Storage.PANEL_SIZE, panelSize, StorageScope.GLOBAL); - const gridSize = grid.getViewSize(); - this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL); - this.storageService.store(Storage.GRID_HEIGHT, gridSize.height, StorageScope.GLOBAL); - })); - } else { - this.workbenchGrid = instantiationService.createInstance( - WorkbenchLegacyLayout, - this.parent, - this.container, - { - titlebar: titleBar, - activitybar: activityBar, - editor: editorPart, - sidebar: sideBar, - panel: panelPart, - statusbar: statusBar, - } - ); - } + const gridSize = grid.getViewSize(); + this.storageService.store(Storage.GRID_WIDTH, gridSize.width, StorageScope.GLOBAL); + this.storageService.store(Storage.GRID_HEIGHT, gridSize.height, StorageScope.GLOBAL); + })); } - layout(options?: ILayoutOptions): void { + layout(): void { if (!this.disposed) { this._dimension = getClientArea(this.parent); - if (this.workbenchGrid instanceof Grid) { - position(this.container, 0, 0, 0, 0, 'relative'); - size(this.container, this._dimension.width, this._dimension.height); + position(this.container, 0, 0, 0, 0, 'relative'); + size(this.container, this._dimension.width, this._dimension.height); - // Layout the grid widget - this.workbenchGrid.layout(this._dimension.width, this._dimension.height); - } else { - this.workbenchGrid.layout(options); - } + // Layout the grid widget + this.workbenchGrid.layout(this._dimension.width, this._dimension.height); // Emit as event this._onLayout.fire(this._dimension); @@ -917,63 +887,59 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } resizePart(part: Parts, sizeChange: number): void { - if (this.workbenchGrid instanceof Grid) { - let viewSize; - const sizeChangePxWidth = this.workbenchGrid.width * sizeChange / 100; - const sizeChangePxHeight = this.workbenchGrid.height * sizeChange / 100; + const sizeChangePxWidth = this.workbenchGrid.width * sizeChange / 100; + const sizeChangePxHeight = this.workbenchGrid.height * sizeChange / 100; - switch (part) { - case Parts.SIDEBAR_PART: - viewSize = this.workbenchGrid.getViewSize(this.sideBarPartView); - this.workbenchGrid.resizeView(this.sideBarPartView, - { - width: viewSize.width + sizeChangePxWidth, - height: viewSize.height - }); + let viewSize: IViewSize; - break; - case Parts.PANEL_PART: - viewSize = this.workbenchGrid.getViewSize(this.panelPartView); + switch (part) { + case Parts.SIDEBAR_PART: + viewSize = this.workbenchGrid.getViewSize(this.sideBarPartView); + this.workbenchGrid.resizeView(this.sideBarPartView, + { + width: viewSize.width + sizeChangePxWidth, + height: viewSize.height + }); - this.workbenchGrid.resizeView(this.panelPartView, - { - width: viewSize.width + (this.getPanelPosition() !== Position.BOTTOM ? sizeChangePxWidth : 0), - height: viewSize.height + (this.getPanelPosition() !== Position.BOTTOM ? 0 : sizeChangePxHeight) - }); + break; + case Parts.PANEL_PART: + viewSize = this.workbenchGrid.getViewSize(this.panelPartView); - break; - case Parts.EDITOR_PART: - viewSize = this.workbenchGrid.getViewSize(this.editorPartView); + this.workbenchGrid.resizeView(this.panelPartView, + { + width: viewSize.width + (this.getPanelPosition() !== Position.BOTTOM ? sizeChangePxWidth : 0), + height: viewSize.height + (this.getPanelPosition() !== Position.BOTTOM ? 0 : sizeChangePxHeight) + }); - // Single Editor Group - if (this.editorGroupService.count === 1) { - if (this.isVisible(Parts.SIDEBAR_PART)) { - this.workbenchGrid.resizeView(this.editorPartView, - { - width: viewSize.width + sizeChangePxWidth, - height: viewSize.height - }); - } else if (this.isVisible(Parts.PANEL_PART)) { - this.workbenchGrid.resizeView(this.editorPartView, - { - width: viewSize.width + (this.getPanelPosition() !== Position.BOTTOM ? sizeChangePxWidth : 0), - height: viewSize.height + (this.getPanelPosition() !== Position.BOTTOM ? 0 : sizeChangePxHeight) - }); - } - } else { - const activeGroup = this.editorGroupService.activeGroup; + break; + case Parts.EDITOR_PART: + viewSize = this.workbenchGrid.getViewSize(this.editorPartView); - const { width, height } = this.editorGroupService.getSize(activeGroup); - this.editorGroupService.setSize(activeGroup, { width: width + sizeChangePxWidth, height: height + sizeChangePxHeight }); + // Single Editor Group + if (this.editorGroupService.count === 1) { + if (this.isVisible(Parts.SIDEBAR_PART)) { + this.workbenchGrid.resizeView(this.editorPartView, + { + width: viewSize.width + sizeChangePxWidth, + height: viewSize.height + }); + } else if (this.isVisible(Parts.PANEL_PART)) { + this.workbenchGrid.resizeView(this.editorPartView, + { + width: viewSize.width + (this.getPanelPosition() !== Position.BOTTOM ? sizeChangePxWidth : 0), + height: viewSize.height + (this.getPanelPosition() !== Position.BOTTOM ? 0 : sizeChangePxHeight) + }); } + } else { + const activeGroup = this.editorGroupService.activeGroup; - break; - default: - return; // Cannot resize other parts - } - } else { - // Legacy Layout - this.workbenchGrid.resizePart(part, sizeChange); + const { width, height } = this.editorGroupService.getSize(activeGroup); + this.editorGroupService.setSize(activeGroup, { width: width + sizeChangePxWidth, height: height + sizeChangePxHeight }); + } + + break; + default: + return; // Cannot resize other parts } } @@ -981,23 +947,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.state.activityBar.hidden = hidden; // Propagate to grid - if (this.workbenchGrid instanceof Grid) { - this.workbenchGrid.setViewVisible(this.activityBarPartView, !hidden); - } - - // Layout - if (!skipLayout) { - if (!(this.workbenchGrid instanceof Grid)) { - this.workbenchGrid.layout(); - } - } + this.workbenchGrid.setViewVisible(this.activityBarPartView, !hidden); } setEditorHidden(hidden: boolean, skipLayout?: boolean): void { - if (!(this.workbenchGrid instanceof Grid)) { - return; - } - this.state.editor.hidden = hidden; // Adjust CSS @@ -1068,9 +1021,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } // Propagate to grid - if (this.workbenchGrid instanceof Grid) { - this.workbenchGrid.setViewVisible(this.sideBarPartView, !hidden); - } + this.workbenchGrid.setViewVisible(this.sideBarPartView, !hidden); // Remember in settings const defaultHidden = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; @@ -1079,13 +1030,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } else { this.storageService.remove(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE); } - - // Layout - if (!skipLayout) { - if (!(this.workbenchGrid instanceof Grid)) { - this.workbenchGrid.layout(); - } - } } setPanelHidden(hidden: boolean, skipLayout?: boolean): void { @@ -1113,11 +1057,14 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } - // Propagate to grid - if (this.workbenchGrid instanceof Grid) { - this.workbenchGrid.setViewVisible(this.panelPartView, !hidden); + // If not maximized and hiding, unmaximize before hiding to allow caching of size + if (this.isPanelMaximized() && hidden) { + this.toggleMaximizedPanel(); } + // Propagate to grid + this.workbenchGrid.setViewVisible(this.panelPartView, !hidden); + // Remember in settings if (!hidden) { this.storageService.store(Storage.PANEL_HIDDEN, 'false', StorageScope.WORKSPACE); @@ -1129,33 +1076,25 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (hidden && this.state.editor.hidden) { this.setEditorHidden(false, true); } - - // Layout - if (!skipLayout) { - if (!(this.workbenchGrid instanceof Grid)) { - this.workbenchGrid.layout(); - } - } } toggleMaximizedPanel(): void { - if (this.workbenchGrid instanceof Grid) { - const size = this.workbenchGrid.getViewSize(this.panelPartView); - if (!this.isPanelMaximized()) { - if (this.state.panel.hidden) { - this.state.panel.sizeBeforeMaximize = this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView) || this.panelPartView.minimumHeight; + const size = this.workbenchGrid.getViewSize(this.panelPartView); + if (!this.isPanelMaximized()) { + if (!this.state.panel.hidden) { + if (this.state.panel.position === Position.BOTTOM) { + this.state.panel.lastNonMaximizedHeight = size.height; + this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_HEIGHT, this.state.panel.lastNonMaximizedHeight, StorageScope.GLOBAL); } else { - this.state.panel.sizeBeforeMaximize = this.state.panel.position === Position.BOTTOM ? size.height : size.width; + this.state.panel.lastNonMaximizedWidth = size.width; + this.storageService.store(Storage.PANEL_LAST_NON_MAXIMIZED_WIDTH, this.state.panel.lastNonMaximizedWidth, StorageScope.GLOBAL); } - - this.storageService.store(Storage.PANEL_SIZE_BEFORE_MAXIMIZED, this.state.panel.sizeBeforeMaximize, StorageScope.GLOBAL); - this.setEditorHidden(true); - } else { - this.setEditorHidden(false); - this.workbenchGrid.resizeView(this.panelPartView, { width: this.state.panel.position === Position.BOTTOM ? size.width : this.state.panel.sizeBeforeMaximize, height: this.state.panel.position === Position.BOTTOM ? this.state.panel.sizeBeforeMaximize : size.height }); } + + this.setEditorHidden(true); } else { - this.workbenchGrid.layout({ toggleMaximizedPanel: true, source: Parts.PANEL_PART }); + this.setEditorHidden(false); + this.workbenchGrid.resizeView(this.panelPartView, { width: this.state.panel.position === Position.BOTTOM ? size.width : this.state.panel.lastNonMaximizedWidth, height: this.state.panel.position === Position.BOTTOM ? this.state.panel.lastNonMaximizedHeight : size.height }); } } @@ -1164,11 +1103,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return false; } - if (this.workbenchGrid instanceof Grid) { - return this.state.editor.hidden; - } else { - return this.workbenchGrid.isPanelMaximized(); - } + return this.state.editor.hidden; } getSideBarPosition(): Position { @@ -1181,11 +1116,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Layout if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); - } else { - this.workbenchGrid.layout(); - } + this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); } } } @@ -1216,6 +1147,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } } + // Save panel position this.storageService.store(Storage.PANEL_POSITION, positionToString(this.state.panel.position), StorageScope.WORKSPACE); // Adjust CSS @@ -1226,22 +1158,31 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi panelPart.updateStyles(); // Layout - if (this.workbenchGrid instanceof Grid) { - const size = this.workbenchGrid.getViewSize(this.panelPartView); - const sideBarSize = this.workbenchGrid.getViewSize(this.sideBarPartView); + const size = this.workbenchGrid.getViewSize(this.panelPartView); + const sideBarSize = this.workbenchGrid.getViewSize(this.sideBarPartView); + // Save last non-maximized size for panel before move + if (newPositionValue !== oldPositionValue && !this.state.editor.hidden) { + + // Save the current size of the panel for the new orthogonal direction + // If moving down, save the width of the panel + // Otherwise, save the height of the panel if (position === Position.BOTTOM) { - this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.height : size.width, this.editorPartView, Direction.Down); + this.state.panel.lastNonMaximizedWidth = size.width; } else { - this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : size.height, this.editorPartView, Direction.Right); + this.state.panel.lastNonMaximizedHeight = size.height; } - - // Reset sidebar to original size before shifting the panel - this.workbenchGrid.resizeView(this.sideBarPartView, sideBarSize); - } else { - this.workbenchGrid.layout(); } + if (position === Position.BOTTOM) { + this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.height : this.state.panel.lastNonMaximizedHeight, this.editorPartView, Direction.Down); + } else { + this.workbenchGrid.moveView(this.panelPartView, this.state.editor.hidden ? size.width : this.state.panel.lastNonMaximizedWidth, this.editorPartView, Direction.Right); + } + + // Reset sidebar to original size before shifting the panel + this.workbenchGrid.resizeView(this.sideBarPartView, sideBarSize); + this._onPanelPositionChange.fire(positionToString(this.state.panel.position)); } @@ -1252,7 +1193,6 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // At some point, we will not fall back to old keys from legacy layout, but for now, let's migrate the keys const sideBarSize = this.storageService.getNumber(Storage.SIDEBAR_SIZE, StorageScope.GLOBAL, this.storageService.getNumber('workbench.sidebar.width', StorageScope.GLOBAL, Math.min(workbenchDimensions.width / 4, 300))!); const panelSize = this.storageService.getNumber(Storage.PANEL_SIZE, StorageScope.GLOBAL, this.storageService.getNumber(this.state.panel.position === Position.BOTTOM ? 'workbench.panel.height' : 'workbench.panel.width', StorageScope.GLOBAL, workbenchDimensions.height / 3)); - const wasEditorHidden = this.storageService.getBoolean(Storage.EDITOR_HIDDEN, StorageScope.WORKSPACE, false); const titleBarHeight = this.titleBarPartView.minimumHeight; const statusBarHeight = this.statusBarPartView.minimumHeight; @@ -1277,14 +1217,16 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const editorNode: ISerializedLeafNode = { type: 'leaf', data: { type: Parts.EDITOR_PART }, - size: this.state.panel.position === Position.BOTTOM ? middleSectionHeight - (this.state.panel.hidden ? 0 : panelSize) : editorSectionWidth - (this.state.panel.hidden ? 0 : panelSize), - visible: true + size: this.state.panel.position === Position.BOTTOM ? + middleSectionHeight - (this.state.panel.hidden ? 0 : panelSize) : + editorSectionWidth - (this.state.panel.hidden ? 0 : panelSize), + visible: !this.state.editor.hidden }; const panelNode: ISerializedLeafNode = { type: 'leaf', data: { type: Parts.PANEL_PART }, - size: wasEditorHidden ? this.state.panel.sizeBeforeMaximize : panelSize, + size: panelSize, visible: !this.state.panel.hidden }; diff --git a/src/vs/workbench/browser/legacyLayout.ts b/src/vs/workbench/browser/legacyLayout.ts deleted file mode 100644 index 56df927d4b..0000000000 --- a/src/vs/workbench/browser/legacyLayout.ts +++ /dev/null @@ -1,738 +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 { Sash, ISashEvent, IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider, Orientation } from 'vs/base/browser/ui/sash/sash'; -import { IWorkbenchLayoutService, Position, ILayoutOptions, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { isMacintosh, isWeb } from 'vs/base/common/platform'; -import { memoize } from 'vs/base/common/decorators'; -import { Dimension, getClientArea, size, position, hide, show } from 'vs/base/browser/dom'; -import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { getZoomFactor } from 'vs/base/browser/browser'; -import { Part } from 'vs/workbench/browser/part'; - -const TITLE_BAR_HEIGHT = isMacintosh && !isWeb ? 22 : 30; -const STATUS_BAR_HEIGHT = 22; -const ACTIVITY_BAR_WIDTH = 48; - -const MIN_SIDEBAR_PART_WIDTH = 170; -const DEFAULT_SIDEBAR_PART_WIDTH = 300; -const HIDE_SIDEBAR_WIDTH_THRESHOLD = 50; - -const MIN_PANEL_PART_HEIGHT = 77; -const MIN_PANEL_PART_WIDTH = 300; -const DEFAULT_PANEL_PART_SIZE = 350; -const DEFAULT_PANEL_SIZE_COEFFICIENT = 0.4; -const PANEL_SIZE_BEFORE_MAXIMIZED_BOUNDARY = 0.7; -const HIDE_PANEL_HEIGHT_THRESHOLD = 50; -const HIDE_PANEL_WIDTH_THRESHOLD = 100; - -/** - * @deprecated to be replaced by new Grid layout - */ -export class WorkbenchLegacyLayout extends Disposable implements IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider { - - private static readonly sashXOneWidthSettingsKey = 'workbench.sidebar.width'; - private static readonly sashXTwoWidthSettingsKey = 'workbench.panel.width'; - private static readonly sashYHeightSettingsKey = 'workbench.panel.height'; - private static readonly panelSizeBeforeMaximizedKey = 'workbench.panel.sizeBeforeMaximized'; - - 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; - - constructor( - private parent: HTMLElement, - private workbenchContainer: HTMLElement, - private parts: { - titlebar: Part, - activitybar: Part, - editor: Part, - sidebar: Part, - panel: Part, - statusbar: Part - }, - @IStorageService private readonly storageService: IStorageService, - @IContextViewService private readonly contextViewService: IContextViewService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IViewletService private readonly viewletService: IViewletService, - @IThemeService private readonly themeService: IThemeService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService - ) { - super(); - - // Restore state - this.restorePreviousState(); - - // Create layout sashes - this.sashXOne = new Sash(this.workbenchContainer, this); - this.sashXTwo = new Sash(this.workbenchContainer, this); - this.sashY = new Sash(this.workbenchContainer, this, { orientation: Orientation.HORIZONTAL }); - - this.registerListeners(); - } - - private restorePreviousState(): void { - this._sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, this.storageService.getNumber(WorkbenchLegacyLayout.sashXOneWidthSettingsKey, StorageScope.GLOBAL, DEFAULT_SIDEBAR_PART_WIDTH)); - - this._panelWidth = Math.max(this.partLayoutInfo.panel.minWidth, this.storageService.getNumber(WorkbenchLegacyLayout.sashXTwoWidthSettingsKey, StorageScope.GLOBAL, DEFAULT_PANEL_PART_SIZE)); - this._panelHeight = Math.max(this.partLayoutInfo.panel.minHeight, this.storageService.getNumber(WorkbenchLegacyLayout.sashYHeightSettingsKey, StorageScope.GLOBAL, DEFAULT_PANEL_PART_SIZE)); - - this.panelMaximized = false; - this.panelSizeBeforeMaximized = this.storageService.getNumber(WorkbenchLegacyLayout.panelSizeBeforeMaximizedKey, StorageScope.GLOBAL, 0); - } - - private registerListeners(): void { - this._register(this.themeService.onThemeChange(_ => this.layout())); - this._register((this.parts.editor as any).onDidSizeConstraintsChange(() => this.onDidEditorSizeConstraintsChange())); - - this.registerSashListeners(); - } - - private onDidEditorSizeConstraintsChange(): void { - if (this.workbenchSize && (this.sidebarWidth || this.panelHeight)) { - if (this.editorGroupService.count > 1) { - const minimumEditorPartSize = new Dimension(this.parts.editor.minimumWidth, this.parts.editor.minimumHeight); - - const sidebarOverflow = this.workbenchSize.width - this.sidebarWidth < minimumEditorPartSize.width; - - let panelOverflow = false; - if (this.layoutService.getPanelPosition() === Position.RIGHT) { - panelOverflow = this.workbenchSize.width - this.panelWidth - this.sidebarWidth < minimumEditorPartSize.width; - } else { - panelOverflow = this.workbenchSize.height - this.panelHeight < minimumEditorPartSize.height; - } - - // Trigger a layout if we detect that either sidebar or panel overflow - // as a matter of a new editor group being added to the editor part - if (sidebarOverflow || panelOverflow) { - this.layout(); - } - } - } - } - - private get activitybarWidth(): number { - if (this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) { - return this.partLayoutInfo.activitybar.width; - } - - return 0; - } - - private get panelHeight(): number { - const panelPosition = this.layoutService.getPanelPosition(); - if (panelPosition === Position.RIGHT) { - return this.sidebarHeight; - } - - return this._panelHeight; - } - - private set panelHeight(value: number) { - this._panelHeight = Math.min(this.computeMaxPanelHeight(), Math.max(this.partLayoutInfo.panel.minHeight, value)); - } - - private get panelWidth(): number { - const panelPosition = this.layoutService.getPanelPosition(); - if (panelPosition === Position.BOTTOM) { - return this.workbenchSize.width - this.activitybarWidth - this.sidebarWidth; - } - - return this._panelWidth; - } - - private set panelWidth(value: number) { - this._panelWidth = Math.min(this.computeMaxPanelWidth(), Math.max(this.partLayoutInfo.panel.minWidth, value)); - } - - private computeMaxPanelWidth(): number { - let minSidebarWidth: number; - if (this.layoutService.isVisible(Parts.SIDEBAR_PART)) { - if (this.layoutService.getSideBarPosition() === Position.LEFT) { - minSidebarWidth = this.partLayoutInfo.sidebar.minWidth; - } else { - minSidebarWidth = this.sidebarWidth; - } - } else { - minSidebarWidth = 0; - } - - return Math.max(this.partLayoutInfo.panel.minWidth, this.workbenchSize.width - this.parts.editor.minimumWidth - minSidebarWidth - this.activitybarWidth); - } - - private computeMaxPanelHeight(): number { - return Math.max(this.partLayoutInfo.panel.minHeight, this.sidebarHeight /* simplification for: window.height - status.height - title-height */ - this.parts.editor.minimumHeight); - } - - private get sidebarWidth(): number { - if (this.layoutService.isVisible(Parts.SIDEBAR_PART)) { - return this._sidebarWidth; - } - - return 0; - } - - private set sidebarWidth(value: number) { - const panelMinWidth = this.layoutService.getPanelPosition() === Position.RIGHT && this.layoutService.isVisible(Parts.PANEL_PART) ? this.partLayoutInfo.panel.minWidth : 0; - const maxSidebarWidth = this.workbenchSize.width - this.activitybarWidth - this.parts.editor.minimumWidth - panelMinWidth; - - this._sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, Math.min(maxSidebarWidth, value)); - } - - @memoize - public get partLayoutInfo() { - return { - titlebar: { - height: TITLE_BAR_HEIGHT - }, - activitybar: { - width: ACTIVITY_BAR_WIDTH - }, - sidebar: { - minWidth: MIN_SIDEBAR_PART_WIDTH - }, - panel: { - minHeight: MIN_PANEL_PART_HEIGHT, - minWidth: MIN_PANEL_PART_WIDTH - }, - statusbar: { - height: STATUS_BAR_HEIGHT - } - }; - } - - private registerSashListeners(): void { - let startX: number = 0; - let startY: number = 0; - let startXTwo: number = 0; - let startSidebarWidth: number; - let startPanelHeight: number; - let startPanelWidth: number; - - this._register(this.sashXOne.onDidStart((e: ISashEvent) => { - startSidebarWidth = this.sidebarWidth; - startX = e.startX; - })); - - this._register(this.sashY.onDidStart((e: ISashEvent) => { - startPanelHeight = this.panelHeight; - startY = e.startY; - })); - - this._register(this.sashXTwo.onDidStart((e: ISashEvent) => { - startPanelWidth = this.panelWidth; - startXTwo = e.startX; - })); - - this._register(this.sashXOne.onDidChange((e: ISashEvent) => { - let doLayout = false; - let sidebarPosition = this.layoutService.getSideBarPosition(); - let isSidebarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART); - let newSashWidth = (sidebarPosition === Position.LEFT) ? startSidebarWidth + e.currentX - startX : startSidebarWidth - e.currentX + startX; - - // Sidebar visible - if (isSidebarVisible) { - - // Automatically hide side bar when a certain threshold is met - if (newSashWidth + HIDE_SIDEBAR_WIDTH_THRESHOLD < this.partLayoutInfo.sidebar.minWidth) { - let dragCompensation = this.partLayoutInfo.sidebar.minWidth - HIDE_SIDEBAR_WIDTH_THRESHOLD; - this.layoutService.setSideBarHidden(true); - startX = (sidebarPosition === Position.LEFT) ? Math.max(this.activitybarWidth, e.currentX - dragCompensation) : Math.min(e.currentX + dragCompensation, this.workbenchSize.width - this.activitybarWidth); - this.sidebarWidth = startSidebarWidth; // when restoring sidebar, restore to the sidebar width we started from - } - - // Otherwise size the sidebar accordingly - else { - this.sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, newSashWidth); // Sidebar can not become smaller than MIN_PART_WIDTH - doLayout = newSashWidth >= this.partLayoutInfo.sidebar.minWidth; - } - } - - // Sidebar hidden - else { - if ((sidebarPosition === Position.LEFT && e.currentX - startX >= this.partLayoutInfo.sidebar.minWidth) || - (sidebarPosition === Position.RIGHT && startX - e.currentX >= this.partLayoutInfo.sidebar.minWidth)) { - startSidebarWidth = this.partLayoutInfo.sidebar.minWidth - (sidebarPosition === Position.LEFT ? e.currentX - startX : startX - e.currentX); - this.sidebarWidth = this.partLayoutInfo.sidebar.minWidth; - this.layoutService.setSideBarHidden(false); - } - } - - if (doLayout) { - this.layout({ source: Parts.SIDEBAR_PART }); - } - })); - - this._register(this.sashY.onDidChange((e: ISashEvent) => { - let doLayout = false; - let isPanelVisible = this.layoutService.isVisible(Parts.PANEL_PART); - let newSashHeight = startPanelHeight - (e.currentY - startY); - - // Panel visible - if (isPanelVisible) { - - // Automatically hide panel when a certain threshold is met - if (newSashHeight + HIDE_PANEL_HEIGHT_THRESHOLD < this.partLayoutInfo.panel.minHeight) { - let dragCompensation = this.partLayoutInfo.panel.minHeight - HIDE_PANEL_HEIGHT_THRESHOLD; - this.layoutService.setPanelHidden(true); - startY = Math.min(this.sidebarHeight - this.statusbarHeight - this.titlebarHeight, e.currentY + dragCompensation); - this.panelHeight = startPanelHeight; // when restoring panel, restore to the panel height we started from - } - - // Otherwise size the panel accordingly - else { - this.panelHeight = Math.max(this.partLayoutInfo.panel.minHeight, newSashHeight); // Panel can not become smaller than MIN_PART_HEIGHT - doLayout = newSashHeight >= this.partLayoutInfo.panel.minHeight; - } - } - - // Panel hidden - else { - if (startY - e.currentY >= this.partLayoutInfo.panel.minHeight) { - startPanelHeight = 0; - this.panelHeight = this.partLayoutInfo.panel.minHeight; - this.layoutService.setPanelHidden(false); - } - } - - if (doLayout) { - this.layout({ source: Parts.PANEL_PART }); - } - })); - - this._register(this.sashXTwo.onDidChange((e: ISashEvent) => { - let doLayout = false; - let isPanelVisible = this.layoutService.isVisible(Parts.PANEL_PART); - let newSashWidth = startPanelWidth - (e.currentX - startXTwo); - - // Panel visible - if (isPanelVisible) { - - // Automatically hide panel when a certain threshold is met - if (newSashWidth + HIDE_PANEL_WIDTH_THRESHOLD < this.partLayoutInfo.panel.minWidth) { - let dragCompensation = this.partLayoutInfo.panel.minWidth - HIDE_PANEL_WIDTH_THRESHOLD; - this.layoutService.setPanelHidden(true); - startXTwo = Math.min(this.workbenchSize.width - this.activitybarWidth, e.currentX + dragCompensation); - this.panelWidth = startPanelWidth; // when restoring panel, restore to the panel height we started from - } - - // Otherwise size the panel accordingly - else { - this.panelWidth = newSashWidth; - doLayout = newSashWidth >= this.partLayoutInfo.panel.minWidth; - } - } - - // Panel hidden - else { - if (startXTwo - e.currentX >= this.partLayoutInfo.panel.minWidth) { - startPanelWidth = 0; - this.panelWidth = this.partLayoutInfo.panel.minWidth; - this.layoutService.setPanelHidden(false); - } - } - - if (doLayout) { - this.layout({ source: Parts.PANEL_PART }); - } - })); - - this._register(this.sashXOne.onDidEnd(() => { - this.storageService.store(WorkbenchLegacyLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL); - })); - - this._register(this.sashY.onDidEnd(() => { - this.storageService.store(WorkbenchLegacyLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL); - })); - - this._register(this.sashXTwo.onDidEnd(() => { - this.storageService.store(WorkbenchLegacyLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL); - })); - - this._register(this.sashY.onDidReset(() => { - this.panelHeight = this.sidebarHeight * DEFAULT_PANEL_SIZE_COEFFICIENT; - this.storageService.store(WorkbenchLegacyLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL); - - this.layout(); - })); - - this._register(this.sashXOne.onDidReset(() => { - const activeViewlet = this.viewletService.getActiveViewlet(); - const optimalWidth = activeViewlet ? activeViewlet.getOptimalWidth() : null; - this.sidebarWidth = typeof optimalWidth === 'number' ? Math.max(optimalWidth, DEFAULT_SIDEBAR_PART_WIDTH) : DEFAULT_SIDEBAR_PART_WIDTH; - this.storageService.store(WorkbenchLegacyLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL); - - this.layoutService.setSideBarHidden(false); - this.layout(); - })); - - this._register(this.sashXTwo.onDidReset(() => { - this.panelWidth = (this.workbenchSize.width - this.sidebarWidth - this.activitybarWidth) * DEFAULT_PANEL_SIZE_COEFFICIENT; - this.storageService.store(WorkbenchLegacyLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL); - - this.layout(); - })); - } - - layout(options?: ILayoutOptions): void { - this.workbenchSize = getClientArea(this.parent); - - const isActivityBarHidden = !this.layoutService.isVisible(Parts.ACTIVITYBAR_PART); - const isTitlebarHidden = !this.layoutService.isVisible(Parts.TITLEBAR_PART); - const isPanelHidden = !this.layoutService.isVisible(Parts.PANEL_PART); - const isStatusbarHidden = !this.layoutService.isVisible(Parts.STATUSBAR_PART); - const isSidebarHidden = !this.layoutService.isVisible(Parts.SIDEBAR_PART); - const sidebarPosition = this.layoutService.getSideBarPosition(); - const panelPosition = this.layoutService.getPanelPosition(); - const menubarVisibility = this.layoutService.getMenubarVisibility(); - - // Sidebar - if (this.sidebarWidth === -1) { - this.sidebarWidth = this.workbenchSize.width / 5; - } - - this.statusbarHeight = isStatusbarHidden ? 0 : this.partLayoutInfo.statusbar.height; - 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 = { width: this.sidebarWidth, height: this.sidebarHeight }; - - // Activity Bar - let activityBarSize = new Dimension(this.activitybarWidth, sidebarSize.height); - - // Panel part - let panelHeight: number; - let panelWidth: number; - const maxPanelHeight = this.computeMaxPanelHeight(); - const maxPanelWidth = this.computeMaxPanelWidth(); - - if (isPanelHidden) { - panelHeight = 0; - panelWidth = 0; - } else if (panelPosition === Position.BOTTOM) { - if (this.panelHeight > 0) { - panelHeight = Math.min(maxPanelHeight, Math.max(this.partLayoutInfo.panel.minHeight, this.panelHeight)); - } else { - panelHeight = sidebarSize.height * DEFAULT_PANEL_SIZE_COEFFICIENT; - } - panelWidth = this.workbenchSize.width - sidebarSize.width - activityBarSize.width; - - if (options && options.toggleMaximizedPanel) { - panelHeight = this.panelMaximized ? Math.max(this.partLayoutInfo.panel.minHeight, Math.min(this.panelSizeBeforeMaximized, maxPanelHeight)) : maxPanelHeight; - } - - this.panelMaximized = panelHeight === maxPanelHeight; - if (panelHeight / maxPanelHeight < PANEL_SIZE_BEFORE_MAXIMIZED_BOUNDARY) { - this.panelSizeBeforeMaximized = panelHeight; - } - } else { - panelHeight = sidebarSize.height; - if (this.panelWidth > 0) { - panelWidth = Math.min(maxPanelWidth, Math.max(this.partLayoutInfo.panel.minWidth, this.panelWidth)); - } else { - panelWidth = (this.workbenchSize.width - activityBarSize.width - sidebarSize.width) * DEFAULT_PANEL_SIZE_COEFFICIENT; - } - - if (options && options.toggleMaximizedPanel) { - panelWidth = this.panelMaximized ? Math.max(this.partLayoutInfo.panel.minWidth, Math.min(this.panelSizeBeforeMaximized, maxPanelWidth)) : maxPanelWidth; - } - - this.panelMaximized = panelWidth === maxPanelWidth; - if (panelWidth / maxPanelWidth < PANEL_SIZE_BEFORE_MAXIMIZED_BOUNDARY) { - this.panelSizeBeforeMaximized = panelWidth; - } - } - - this.storageService.store(WorkbenchLegacyLayout.panelSizeBeforeMaximizedKey, this.panelSizeBeforeMaximized, StorageScope.GLOBAL); - - const panelDimension = { width: panelWidth, height: panelHeight }; - - // Editor - let editorSize = { - width: 0, - height: 0 - }; - - editorSize.width = this.workbenchSize.width - sidebarSize.width - activityBarSize.width - (panelPosition === Position.RIGHT ? panelDimension.width : 0); - editorSize.height = sidebarSize.height - (panelPosition === Position.BOTTOM ? panelDimension.height : 0); - - // Adjust for Editor Part minimum width - const minimumEditorPartSize = new Dimension(this.parts.editor.minimumWidth, this.parts.editor.minimumHeight); - if (editorSize.width < minimumEditorPartSize.width) { - const missingPreferredEditorWidth = minimumEditorPartSize.width - editorSize.width; - let outstandingMissingPreferredEditorWidth = missingPreferredEditorWidth; - - // Take from Panel if Panel Position on the Right and Visible - if (!isPanelHidden && panelPosition === Position.RIGHT && (!options || options.source !== Parts.PANEL_PART)) { - const oldPanelWidth = panelDimension.width; - panelDimension.width = Math.max(this.partLayoutInfo.panel.minWidth, panelDimension.width - outstandingMissingPreferredEditorWidth); - outstandingMissingPreferredEditorWidth -= oldPanelWidth - panelDimension.width; - } - - // Take from Sidebar if Visible - if (!isSidebarHidden && outstandingMissingPreferredEditorWidth > 0) { - const oldSidebarWidth = sidebarSize.width; - sidebarSize.width = Math.max(this.partLayoutInfo.sidebar.minWidth, sidebarSize.width - outstandingMissingPreferredEditorWidth); - outstandingMissingPreferredEditorWidth -= oldSidebarWidth - sidebarSize.width; - } - - editorSize.width += missingPreferredEditorWidth - outstandingMissingPreferredEditorWidth; - if (!isPanelHidden && panelPosition === Position.BOTTOM) { - panelDimension.width = editorSize.width; // ensure panel width is always following editor width - } - } - - // Adjust for Editor Part minimum height - if (editorSize.height < minimumEditorPartSize.height) { - const missingPreferredEditorHeight = minimumEditorPartSize.height - editorSize.height; - let outstandingMissingPreferredEditorHeight = missingPreferredEditorHeight; - - // Take from Panel if Panel Position on the Bottom and Visible - if (!isPanelHidden && panelPosition === Position.BOTTOM) { - const oldPanelHeight = panelDimension.height; - panelDimension.height = Math.max(this.partLayoutInfo.panel.minHeight, panelDimension.height - outstandingMissingPreferredEditorHeight); - outstandingMissingPreferredEditorHeight -= oldPanelHeight - panelDimension.height; - } - - editorSize.height += missingPreferredEditorHeight - outstandingMissingPreferredEditorHeight; - } - - if (!isSidebarHidden) { - this.sidebarWidth = sidebarSize.width; - this.storageService.store(WorkbenchLegacyLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL); - } - - if (!isPanelHidden) { - if (panelPosition === Position.BOTTOM) { - this.panelHeight = panelDimension.height; - this.storageService.store(WorkbenchLegacyLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL); - } else { - this.panelWidth = panelDimension.width; - this.storageService.store(WorkbenchLegacyLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL); - } - } - - // Workbench - position(this.workbenchContainer, 0, 0, 0, 0, 'relative'); - size(this.workbenchContainer, this.workbenchSize.width, this.workbenchSize.height); - - // Bug on Chrome: Sometimes Chrome wants to scroll the workbench container on layout changes. The fix is to reset scrolling in this case. - // uses set time to ensure this happens in th next frame (RAF will be at the end of this JS time slice and we don't want that) - setTimeout(() => { - const workbenchContainer = this.workbenchContainer; - if (workbenchContainer.scrollTop > 0) { - workbenchContainer.scrollTop = 0; - } - if (workbenchContainer.scrollLeft > 0) { - workbenchContainer.scrollLeft = 0; - } - }); - - // Title Part - const titleContainer = this.parts.titlebar.getContainer(); - if (isTitlebarHidden) { - hide(titleContainer); - } else { - show(titleContainer); - } - - // Editor Part and Panel part - const editorContainer = this.parts.editor.getContainer(); - const panelContainer = this.parts.panel.getContainer(); - size(editorContainer, editorSize.width, editorSize.height); - size(panelContainer, panelDimension.width, panelDimension.height); - - if (panelPosition === Position.BOTTOM) { - if (sidebarPosition === Position.LEFT) { - position(editorContainer, this.titlebarHeight, 0, this.statusbarHeight + panelDimension.height, sidebarSize.width + activityBarSize.width); - position(panelContainer, editorSize.height + this.titlebarHeight, 0, this.statusbarHeight, sidebarSize.width + activityBarSize.width); - } else { - position(editorContainer, this.titlebarHeight, sidebarSize.width, this.statusbarHeight + panelDimension.height, 0); - position(panelContainer, editorSize.height + this.titlebarHeight, sidebarSize.width, this.statusbarHeight, 0); - } - } else { - if (sidebarPosition === Position.LEFT) { - position(editorContainer, this.titlebarHeight, panelDimension.width, this.statusbarHeight, sidebarSize.width + activityBarSize.width); - position(panelContainer, this.titlebarHeight, 0, this.statusbarHeight, sidebarSize.width + activityBarSize.width + editorSize.width); - } else { - position(editorContainer, this.titlebarHeight, sidebarSize.width + activityBarSize.width + panelWidth, this.statusbarHeight, 0); - position(panelContainer, this.titlebarHeight, sidebarSize.width + activityBarSize.width, this.statusbarHeight, editorSize.width); - } - } - - // Activity Bar Part - const activitybarContainer = this.parts.activitybar.getContainer(); - size(activitybarContainer, null, activityBarSize.height); - if (sidebarPosition === Position.LEFT) { - this.parts.activitybar.getContainer().style.right = ''; - position(activitybarContainer, this.titlebarHeight, undefined, 0, 0); - } else { - this.parts.activitybar.getContainer().style.left = ''; - position(activitybarContainer, this.titlebarHeight, 0, 0, undefined); - } - if (isActivityBarHidden) { - hide(activitybarContainer); - } else { - show(activitybarContainer); - } - - // Sidebar Part - const sidebarContainer = this.parts.sidebar.getContainer(); - size(sidebarContainer, sidebarSize.width, sidebarSize.height); - const editorAndPanelWidth = editorSize.width + (panelPosition === Position.RIGHT ? panelWidth : 0); - if (sidebarPosition === Position.LEFT) { - position(sidebarContainer, this.titlebarHeight, editorAndPanelWidth, this.statusbarHeight, activityBarSize.width); - } else { - position(sidebarContainer, this.titlebarHeight, activityBarSize.width, this.statusbarHeight, editorAndPanelWidth); - } - - // Statusbar Part - const statusbarContainer = this.parts.statusbar.getContainer(); - position(statusbarContainer, this.workbenchSize.height - this.statusbarHeight); - if (isStatusbarHidden) { - hide(statusbarContainer); - } else { - show(statusbarContainer); - } - - // Sashes - this.sashXOne.layout(); - if (panelPosition === Position.BOTTOM) { - this.sashXTwo.hide(); - this.sashY.layout(); - this.sashY.show(); - } else { - this.sashY.hide(); - this.sashXTwo.layout(); - this.sashXTwo.show(); - } - - // Propagate to Part Layouts - this.parts.titlebar.layout(this.workbenchSize.width, this.titlebarHeight); - this.parts.editor.layout(editorSize.width, editorSize.height); - this.parts.sidebar.layout(sidebarSize.width, sidebarSize.height); - this.parts.panel.layout(panelDimension.width, panelDimension.height); - this.parts.activitybar.layout(activityBarSize.width, activityBarSize.height); - - // Propagate to Context View - this.contextViewService.layout(); - } - - getVerticalSashTop(sash: Sash): number { - return this.titlebarHeight; - } - - getVerticalSashLeft(sash: Sash): number { - let sidebarPosition = this.layoutService.getSideBarPosition(); - if (sash === this.sashXOne) { - - if (sidebarPosition === Position.LEFT) { - return this.sidebarWidth + this.activitybarWidth; - } - - return this.workbenchSize.width - this.sidebarWidth - this.activitybarWidth; - } - - return this.workbenchSize.width - this.panelWidth - (sidebarPosition === Position.RIGHT ? this.sidebarWidth + this.activitybarWidth : 0); - } - - getVerticalSashHeight(sash: Sash): number { - if (sash === this.sashXTwo && !this.layoutService.isVisible(Parts.PANEL_PART)) { - return 0; - } - - return this.sidebarHeight; - } - - getHorizontalSashTop(sash: Sash): number { - const offset = 2; // Horizontal sash should be a bit lower than the editor area, thus add 2px #5524 - return offset + (this.layoutService.isVisible(Parts.PANEL_PART) ? this.sidebarHeight - this.panelHeight + this.titlebarHeight : this.sidebarHeight + this.titlebarHeight); - } - - getHorizontalSashLeft(sash: Sash): number { - if (this.layoutService.getSideBarPosition() === Position.RIGHT) { - return 0; - } - - return this.sidebarWidth + this.activitybarWidth; - } - - getHorizontalSashWidth(sash: Sash): number { - return this.panelWidth; - } - - isPanelMaximized(): boolean { - return this.panelMaximized; - } - - resizePart(part: Parts, sizeChange: number): void { - const panelPosition = this.layoutService.getPanelPosition(); - const sizeChangePxWidth = this.workbenchSize.width * (sizeChange / 100); - const sizeChangePxHeight = this.workbenchSize.height * (sizeChange / 100); - - let doLayout = false; - switch (part) { - case Parts.SIDEBAR_PART: - this.sidebarWidth = this.sidebarWidth + sizeChangePxWidth; // Sidebar can not become smaller than MIN_PART_WIDTH - - if (this.workbenchSize.width - this.sidebarWidth < this.parts.editor.minimumWidth) { - this.sidebarWidth = this.workbenchSize.width - this.parts.editor.minimumWidth; - } - - doLayout = true; - break; - case Parts.PANEL_PART: - if (panelPosition === Position.BOTTOM) { - this.panelHeight = this.panelHeight + sizeChangePxHeight; - } else if (panelPosition === Position.RIGHT) { - this.panelWidth = this.panelWidth + sizeChangePxWidth; - } - - doLayout = true; - break; - case Parts.EDITOR_PART: - // If we have one editor we can cheat and resize sidebar with the negative delta - // If the sidebar is not visible and panel is, resize panel main axis with negative Delta - if (this.editorGroupService.count === 1) { - if (this.layoutService.isVisible(Parts.SIDEBAR_PART)) { - this.sidebarWidth = this.sidebarWidth - sizeChangePxWidth; - doLayout = true; - } else if (this.layoutService.isVisible(Parts.PANEL_PART)) { - if (panelPosition === Position.BOTTOM) { - this.panelHeight = this.panelHeight - sizeChangePxHeight; - } else if (panelPosition === Position.RIGHT) { - this.panelWidth = this.panelWidth - sizeChangePxWidth; - } - doLayout = true; - } - } else { - const activeGroup = this.editorGroupService.activeGroup; - - const { width, height } = this.editorGroupService.getSize(activeGroup); - this.editorGroupService.setSize(activeGroup, { width: width + sizeChangePxWidth, height: height + sizeChangePxHeight }); - } - } - - if (doLayout) { - this.layout(); - } - } -} diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index 6b4f2c371c..a1fbb92ece 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -3,10 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench > .part { - position: absolute; /* only position absolute when grid is not used (TODO@sbatten TODO@ben remove eventually) */ -} - .monaco-workbench .part { box-sizing: border-box; overflow: hidden; diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index e27b603e54..fb96c105eb 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -51,10 +51,10 @@ export interface ICompositeBar { export class ActivityAction extends Action { - private _onDidChangeActivity = new Emitter(); + private readonly _onDidChangeActivity = new Emitter(); readonly onDidChangeActivity: Event = this._onDidChangeActivity.event; - private _onDidChangeBadge = new Emitter(); + private readonly _onDidChangeBadge = new Emitter(); readonly onDidChangeBadge: Event = this._onDidChangeBadge.event; private badge?: IBadge; diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 9805fc1952..fd1bec4610 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -45,7 +45,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { onDidChangeZoomLevel } from 'vs/base/browser/browser'; -import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; +import { withNullAsUndefined } from 'vs/base/common/types'; import { ILabelService } from 'vs/platform/label/common/label'; class Item extends BreadcrumbsItem { @@ -485,7 +485,7 @@ export class BreadcrumbsControl { selection: Range.collapseToStart(element.symbol.selectionRange), revealInCenterIfOutsideViewport: true } - }, withUndefinedAsNull(this._getActiveCodeEditor()), group === SIDE_GROUP); + }, this._getActiveCodeEditor(), group === SIDE_GROUP); } } } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 37910829ad..da6c6d1f46 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -45,7 +45,7 @@ export class EditorBreadcrumbsModel { private _outlineElements: Array = []; private _outlineDisposables = new DisposableStore(); - private _onDidUpdate = new Emitter(); + private readonly _onDidUpdate = new Emitter(); readonly onDidUpdate: Event = this._onDidUpdate.event; constructor( diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 70663a48a9..bd85031d9b 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -418,12 +418,12 @@ editorCommands.setup(); // Touch Bar if (isMacintosh) { MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { - command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconLocation: { dark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/back-tb.png')) } }, + command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')) } }, group: 'navigation' }); MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { - command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconLocation: { dark: URI.parse(registerAndGetAmdImageURL('vs/workbench/browser/parts/editor/media/forward-tb.png')) } }, + command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')) } }, group: 'navigation' }); } diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index a871f54679..0df568230e 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -467,7 +467,7 @@ export class CloseEditorAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'close-editor-action'); + super(id, label, 'codicon-close'); } run(context?: IEditorCommandsContext): Promise { @@ -485,7 +485,7 @@ export class CloseOneEditorAction extends Action { label: string, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService ) { - super(id, label, 'close-editor-action'); + super(id, label, 'codicon-close'); } run(context?: IEditorCommandsContext): Promise { @@ -669,7 +669,7 @@ export class CloseAllEditorsAction extends BaseCloseAllAction { @ITextFileService textFileService: ITextFileService, @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(id, label, 'action-close-all-files', textFileService, editorGroupService); + super(id, label, 'codicon-close-all', textFileService, editorGroupService); } protected doCloseAll(): Promise { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 5b88807bd3..5b582505f2 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -206,8 +206,6 @@ class State { private _metadata: string | undefined; get metadata(): string | undefined { return this._metadata; } - constructor() { } - update(update: StateDelta): StateChange { const change = new StateChange(); diff --git a/src/vs/workbench/browser/parts/editor/media/close-dark.svg b/src/vs/workbench/browser/parts/editor/media/close-dark.svg deleted file mode 100644 index 44ece771f4..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-dirty-alt.svg b/src/vs/workbench/browser/parts/editor/media/close-dirty-alt.svg deleted file mode 100644 index 409e5fa539..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-dirty-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/media/close-dirty-dark.svg b/src/vs/workbench/browser/parts/editor/media/close-dirty-dark.svg deleted file mode 100644 index 51946be5bb..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-dirty-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-dirty-light.svg b/src/vs/workbench/browser/parts/editor/media/close-dirty-light.svg deleted file mode 100644 index fb91225b96..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-dirty-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-dirty.svg b/src/vs/workbench/browser/parts/editor/media/close-dirty.svg deleted file mode 100644 index 409e5fa539..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-dirty.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/media/close-hc.svg b/src/vs/workbench/browser/parts/editor/media/close-hc.svg deleted file mode 100644 index fa205f4ee1..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/close-light.svg b/src/vs/workbench/browser/parts/editor/media/close-light.svg deleted file mode 100644 index 742fcae4ae..0000000000 --- a/src/vs/workbench/browser/parts/editor/media/close-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css index 580272b194..59677ad2f2 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css +++ b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css @@ -89,15 +89,6 @@ background-repeat: no-repeat; } -.vs .monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .close-editor-group { - background-image: url('close-light.svg'); -} - -.vs-dark .monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .close-editor-group, -.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .editor-group-container-toolbar .close-editor-group { - background-image: url('close-dark.svg'); -} - /* Editor */ .monaco-workbench .part.editor > .content .editor-group-container.empty > .editor-container { diff --git a/src/vs/workbench/browser/parts/editor/media/editorstatus.css b/src/vs/workbench/browser/parts/editor/media/editorstatus.css index 75cd59dc98..e792c81da1 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorstatus.css +++ b/src/vs/workbench/browser/parts/editor/media/editorstatus.css @@ -49,15 +49,3 @@ margin-right: 5px; max-width: fit-content; } - -.vs .monaco-workbench .screen-reader-detected-explanation .cancel { - background: url('close-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .screen-reader-detected-explanation .cancel { - background: url('close-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .screen-reader-detected-explanation .cancel { - background: url('close-hc.svg') center center no-repeat; -} \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index afa43434d6..adaaa2453a 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -198,6 +198,12 @@ opacity: 1; } +/* change close icon to dirty state icon */ +.monaco-workbench .part.editor > .content .editor-group-container.active > .title .tabs-container > .tab.dirty > .tab-close .action-label:not(:hover)::before, +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label:not(:hover)::before { + content: "\ea71"; /* use `circle-filled` icon unicode */ +} + .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active > .tab-close .action-label, /* show dimmed for inactive group */ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* show dimmed for inactive group */ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty > .tab-close .action-label, /* show dimmed for inactive group */ @@ -216,27 +222,6 @@ margin-right: 0.5em; } -.vs .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action { - background: url('close-dirty-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action, -.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action { - background: url('close-dirty-dark.svg') center center no-repeat; -} - -.vs .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action:hover { - background: url('close-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action:hover { - background: url('close-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty .close-editor-action:hover { - background: url('close-hc.svg') center center no-repeat; -} - /* No Tab Close Button */ .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off { @@ -254,15 +239,6 @@ padding-right: 28px; /* make room for dirty indication when we are running without close button */ } -.vs .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty:not(.dirty-border-top) { - background-image: url('close-dirty-light.svg'); -} - -.vs-dark .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty:not(.dirty-border-top), -.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off.dirty { - background-image: url('close-dirty-dark.svg'); -} - /* Editor Actions */ .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions { diff --git a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css index 35c8b42a0a..aa3bca6d25 100644 --- a/src/vs/workbench/browser/parts/editor/media/titlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/titlecontrol.css @@ -54,20 +54,6 @@ cursor: grab; } -/* Actions */ - -.monaco-workbench .part.editor > .content .editor-group-container > .title .close-editor-action { - background: url('close-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .part.editor > .content .editor-group-container > .title .close-editor-action { - background: url('close-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .part.editor > .content .editor-group-container > .title .close-editor-action { - background: url('close-hc.svg') center center no-repeat; -} - /* Drag and Drop Feedback */ .monaco-editor-group-drag-image { @@ -76,4 +62,4 @@ border-radius: 10px; font-size: 12px; position: absolute; -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/notifications/media/close-all-dark.svg b/src/vs/workbench/browser/parts/notifications/media/close-all-dark.svg deleted file mode 100644 index 35e5fb4422..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/close-all-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/close-all-light.svg b/src/vs/workbench/browser/parts/notifications/media/close-all-light.svg deleted file mode 100644 index 6c7cec7461..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/close-all-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/close-dark.svg b/src/vs/workbench/browser/parts/notifications/media/close-dark.svg deleted file mode 100644 index 080eb9f042..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/close-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/close-light.svg b/src/vs/workbench/browser/parts/notifications/media/close-light.svg deleted file mode 100644 index 2223c53fb1..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/close-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/configure-dark.svg b/src/vs/workbench/browser/parts/notifications/media/configure-dark.svg deleted file mode 100644 index ace01a5ddf..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/configure-dark.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/configure-light.svg b/src/vs/workbench/browser/parts/notifications/media/configure-light.svg deleted file mode 100644 index 4194780bba..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/configure-light.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/error-dark.svg b/src/vs/workbench/browser/parts/notifications/media/error-dark.svg deleted file mode 100644 index efdc5f2ae2..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/error-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/error-light.svg b/src/vs/workbench/browser/parts/notifications/media/error-light.svg deleted file mode 100644 index d646c72c74..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/error-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/info-dark.svg b/src/vs/workbench/browser/parts/notifications/media/info-dark.svg deleted file mode 100644 index bb851afdfe..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/info-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/info-light.svg b/src/vs/workbench/browser/parts/notifications/media/info-light.svg deleted file mode 100644 index 6faf670ccc..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/info-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css b/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css index 48c3111061..2760483447 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsActions.css @@ -5,7 +5,8 @@ .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .action-label, .monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-toolbar .action-label { - display: block; + display: flex; + align-items: center; width: 16px; height: 22px; margin-right: 4px; @@ -13,57 +14,3 @@ background-position: center; background-repeat: no-repeat; } - -.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .clear-notification-action { - background-image: url('close-light.svg'); -} - -.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .clear-notification-action, -.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .clear-notification-action { - background-image: url('close-dark.svg'); -} - -.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .expand-notification-action { - background-image: url('tree-collapsed-light.svg'); -} - -.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .expand-notification-action, -.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .expand-notification-action { - background-image: url('tree-collapsed-dark.svg'); -} - -.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .collapse-notification-action { - background-image: url('tree-expanded-light.svg'); -} - -.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .collapse-notification-action, -.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .collapse-notification-action { - background-image: url('tree-expanded-dark.svg'); -} - -.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .configure-notification-action { - background-image: url('configure-light.svg'); -} - -.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .configure-notification-action, -.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .configure-notification-action { - background-image: url('configure-dark.svg'); -} - -.vs-dark .monaco-workbench > .notifications-center > .notifications-center-header .clear-all-notifications-action, -.hc-black .monaco-workbench > .notifications-center > .notifications-center-header .clear-all-notifications-action { - background-image: url('close-all-dark.svg'); -} - -.vs .monaco-workbench > .notifications-center > .notifications-center-header .clear-all-notifications-action { - background-image: url('close-all-light.svg'); -} - -.vs-dark .monaco-workbench > .notifications-center > .notifications-center-header .hide-all-notifications-action, -.hc-black .monaco-workbench > .notifications-center > .notifications-center-header .hide-all-notifications-action { - background-image: url('tree-expanded-dark.svg'); -} - -.vs .monaco-workbench > .notifications-center > .notifications-center-header .hide-all-notifications-action { - background-image: url('tree-expanded-light.svg'); -} diff --git a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css index cbcf1787b0..702b1b0f6f 100644 --- a/src/vs/workbench/browser/parts/notifications/media/notificationsList.css +++ b/src/vs/workbench/browser/parts/notifications/media/notificationsList.css @@ -32,6 +32,8 @@ /** Notification: Icon */ .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon { + display: flex; + align-items: center; flex: 0 0 16px; height: 22px; margin-right: 4px; @@ -40,33 +42,6 @@ background-repeat: no-repeat; } -.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info { - background-image: url('info-light.svg'); -} - -.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-warning { - background-image: url('warning-light.svg'); -} - -.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-error { - background-image: url('error-light.svg'); -} - -.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info, -.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info { - background-image: url('info-dark.svg'); -} - -.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-warning, -.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-warning { - background-image: url('warning-dark.svg'); -} - -.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-error, -.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-error { - background-image: url('error-dark.svg'); -} - /** Notification: Message */ .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-message { diff --git a/src/vs/workbench/browser/parts/notifications/media/tree-collapsed-dark.svg b/src/vs/workbench/browser/parts/notifications/media/tree-collapsed-dark.svg deleted file mode 100644 index 25150f44af..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/tree-collapsed-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/tree-collapsed-light.svg b/src/vs/workbench/browser/parts/notifications/media/tree-collapsed-light.svg deleted file mode 100644 index 17fe3be98b..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/tree-collapsed-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/tree-expanded-dark.svg b/src/vs/workbench/browser/parts/notifications/media/tree-expanded-dark.svg deleted file mode 100644 index a1df6a8d44..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/tree-expanded-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/tree-expanded-light.svg b/src/vs/workbench/browser/parts/notifications/media/tree-expanded-light.svg deleted file mode 100644 index e60e357f57..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/tree-expanded-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/warning-dark.svg b/src/vs/workbench/browser/parts/notifications/media/warning-dark.svg deleted file mode 100644 index a267963e58..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/warning-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/notifications/media/warning-light.svg b/src/vs/workbench/browser/parts/notifications/media/warning-light.svg deleted file mode 100644 index f2e2aa741e..0000000000 --- a/src/vs/workbench/browser/parts/notifications/media/warning-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts index ce0738a178..bae33812c5 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsActions.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsActions.ts @@ -23,7 +23,7 @@ export class ClearNotificationAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'clear-notification-action'); + super(id, label, 'codicon-close'); } run(notification: INotificationViewItem): Promise { @@ -43,7 +43,7 @@ export class ClearAllNotificationsAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'clear-all-notifications-action'); + super(id, label, 'codicon-close-all'); } run(notification: INotificationViewItem): Promise { @@ -63,7 +63,7 @@ export class HideNotificationsCenterAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'hide-all-notifications-action'); + super(id, label, 'codicon-chevron-down'); } run(notification: INotificationViewItem): Promise { @@ -83,7 +83,7 @@ export class ExpandNotificationAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'expand-notification-action'); + super(id, label, 'codicon-chevron-up'); } run(notification: INotificationViewItem): Promise { @@ -103,7 +103,7 @@ export class CollapseNotificationAction extends Action { label: string, @ICommandService private readonly commandService: ICommandService ) { - super(id, label, 'collapse-notification-action'); + super(id, label, 'codicon-chevron-down'); } run(notification: INotificationViewItem): Promise { @@ -123,7 +123,7 @@ export class ConfigureNotificationAction extends Action { label: string, private readonly _configurationActions: ReadonlyArray ) { - super(id, label, 'configure-notification-action'); + super(id, label, 'codicon-gear'); } get configurationActions(): ReadonlyArray { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index 90198784a5..d357cd46c2 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -8,7 +8,7 @@ import { addClass, isAncestor, trackFocus } from 'vs/base/browser/dom'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IListOptions } from 'vs/base/browser/ui/list/listWidget'; -import { Themable, NOTIFICATIONS_LINKS, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND } from 'vs/workbench/common/theme'; +import { Themable, NOTIFICATIONS_LINKS, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND, NOTIFICATIONS_ERROR_ICON_FOREGROUND, NOTIFICATIONS_WARNING_ICON_FOREGROUND, NOTIFICATIONS_INFO_ICON_FOREGROUND } from 'vs/workbench/common/theme'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { INotificationViewItem } from 'vs/workbench/common/notifications'; @@ -256,4 +256,34 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { outline-color: ${focusOutline}; }`); } + + // Notification Error Icon + const notificationErrorIconForegroundColor = theme.getColor(NOTIFICATIONS_ERROR_ICON_FOREGROUND); + if (notificationErrorIconForegroundColor) { + collector.addRule(` + .monaco-workbench .notifications-center .codicon-error, + .monaco-workbench .notifications-toasts .codicon-error { + color: ${notificationErrorIconForegroundColor}; + }`); + } + + // Notification Warning Icon + const notificationWarningIconForegroundColor = theme.getColor(NOTIFICATIONS_WARNING_ICON_FOREGROUND); + if (notificationWarningIconForegroundColor) { + collector.addRule(` + .monaco-workbench .notifications-center .codicon-warning, + .monaco-workbench .notifications-toasts .codicon-warning { + color: ${notificationWarningIconForegroundColor}; + }`); + } + + // Notification Info Icon + const notificationInfoIconForegroundColor = theme.getColor(NOTIFICATIONS_INFO_ICON_FOREGROUND); + if (notificationInfoIconForegroundColor) { + collector.addRule(` + .monaco-workbench .notifications-center .codicon-info, + .monaco-workbench .notifications-toasts .codicon-info { + color: ${notificationInfoIconForegroundColor}; + }`); + } }); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 7df648d147..9c328e3b7e 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -207,6 +207,7 @@ export class NotificationRenderer implements IListRenderer { const domAction = notification.severity === this.toSeverity(severity) ? addClass : removeClass; - domAction(this.template.icon, `icon-${severity}`); + domAction(this.template.icon, `codicon-${severity}`); }); } diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 67f564ba2a..1e58dbc039 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -123,3 +123,8 @@ min-width: 110px; margin-right: 10px; } + +.monaco-workbench .part.panel.right .title-actions .codicon-chevron-up, +.monaco-workbench .part.panel.right .title-actions .codicon-chevron-down { + transform: rotate(-90deg); +} diff --git a/src/vs/workbench/browser/parts/quickinput/media/quickInput.css b/src/vs/workbench/browser/parts/quickinput/media/quickInput.css index 8eccef05cd..ee494b1d8b 100644 --- a/src/vs/workbench/browser/parts/quickinput/media/quickInput.css +++ b/src/vs/workbench/browser/parts/quickinput/media/quickInput.css @@ -37,7 +37,7 @@ flex: 1; } -.quick-input-titlebar .monaco-action-bar .action-label.icon { +.quick-input-titlebar .monaco-action-bar .action-label.codicon { margin: 0; width: 19px; height: 100%; @@ -212,7 +212,7 @@ overflow: visible; } -.quick-input-list .quick-input-list-entry-action-bar .action-label.icon { +.quick-input-list .quick-input-list-entry-action-bar .action-label.codicon { margin: 0; width: 19px; height: 100%; @@ -224,11 +224,11 @@ margin-top: 1px; } -.quick-input-list .quick-input-list-entry-action-bar ul:first-child .action-label.icon { +.quick-input-list .quick-input-list-entry-action-bar ul:first-child .action-label.codicon { margin-left: 2px; } -.quick-input-list .quick-input-list-entry-action-bar ul:last-child .action-label.icon { +.quick-input-list .quick-input-list-entry-action-bar ul:last-child .action-label.codicon { margin-right: 8px; } diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index cc2189f738..50855dc88c 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -1123,7 +1123,7 @@ export class QuickInputService extends Component implements IQuickInputService { break; case KeyCode.Tab: if (!event.altKey && !event.ctrlKey && !event.metaKey) { - const selectors = ['.action-label.icon']; + const selectors = ['.action-label.codicon']; if (container.classList.contains('show-checkboxes')) { selectors.push('input'); } else { diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index f47055bdf4..91124709b5 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -50,7 +50,7 @@ class ListElement implements IListElement { saneDescription?: string; saneDetail?: string; hidden = false; - private _onChecked = new Emitter(); + private readonly _onChecked = new Emitter(); onChecked = this._onChecked.event; _checked?: boolean; get checked() { @@ -222,17 +222,17 @@ export class QuickInputList { matchOnDescription = false; matchOnDetail = false; matchOnLabel = true; - private _onChangedAllVisibleChecked = new Emitter(); + private readonly _onChangedAllVisibleChecked = new Emitter(); onChangedAllVisibleChecked: Event = this._onChangedAllVisibleChecked.event; - private _onChangedCheckedCount = new Emitter(); + private readonly _onChangedCheckedCount = new Emitter(); onChangedCheckedCount: Event = this._onChangedCheckedCount.event; - private _onChangedVisibleCount = new Emitter(); + private readonly _onChangedVisibleCount = new Emitter(); onChangedVisibleCount: Event = this._onChangedVisibleCount.event; - private _onChangedCheckedElements = new Emitter(); + private readonly _onChangedCheckedElements = new Emitter(); onChangedCheckedElements: Event = this._onChangedCheckedElements.event; - private _onButtonTriggered = new Emitter>(); + private readonly _onButtonTriggered = new Emitter>(); onButtonTriggered = this._onButtonTriggered.event; - private _onLeave = new Emitter(); + private readonly _onLeave = new Emitter(); onLeave: Event = this._onLeave.event; private _fireCheckedEvents = true; private elementDisposables: IDisposable[] = []; diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index ce82f73376..e358b665fd 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -27,7 +27,7 @@ import { Color } from 'vs/base/common/color'; 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 { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; import { template } from 'vs/base/common/labels'; import { ILabelService } from 'vs/platform/label/common/label'; import { Event, Emitter } from 'vs/base/common/event'; @@ -35,11 +35,14 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { RunOnceScheduler } from 'vs/base/common/async'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { Schemas } from 'vs/base/common/network'; 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'; +// TODO@sbatten https://github.com/microsoft/vscode/issues/81360 +// tslint:disable-next-line: import-patterns layering +import { IElectronService } from 'vs/platform/electron/node/electron'; + export class TitlebarPart extends Part implements ITitleService { private static readonly NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]"); @@ -96,7 +99,8 @@ export class TitlebarPart extends Part implements ITitleService { @IStorageService storageService: IStorageService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IMenuService menuService: IMenuService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @optional(IElectronService) private electronService: IElectronService ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); @@ -176,17 +180,6 @@ export class TitlebarPart extends Part implements ITitleService { this.activeEditorListeners.add(activeEditor.onDidChangeDirty(() => this.titleUpdater.schedule())); this.activeEditorListeners.add(activeEditor.onDidChangeLabel(() => this.titleUpdater.schedule())); } - - // Represented File Name - this.updateRepresentedFilename(); - } - - private updateRepresentedFilename(): void { - const file = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file }); - const path = file ? file.fsPath : ''; - - // Apply to window - this.windowService.setRepresentedFilename(path); } private doUpdateTitle(): void { @@ -334,7 +327,7 @@ export class TitlebarPart extends Part implements ITitleService { this.onUpdateAppIconDragBehavior(); this._register(addDisposableListener(this.appIcon, EventType.DBLCLICK, (e => { - this.windowService.closeWindow(); + this.electronService.closeWindow(); }))); } @@ -358,15 +351,6 @@ export class TitlebarPart extends Part implements ITitleService { this.titleUpdater.schedule(); } - // Maximize/Restore on doubleclick - if (isMacintosh && !isWeb) { - this._register(addDisposableListener(this.element, EventType.DBLCLICK, e => { - EventHelper.stop(e); - - this.onTitleDoubleclick(); - })); - } - // Context menu on title [EventType.CONTEXT_MENU, EventType.MOUSE_DOWN].forEach(event => { this._register(addDisposableListener(this.title, event, e => { @@ -387,7 +371,7 @@ export class TitlebarPart extends Part implements ITitleService { const minimizeIcon = append(minimizeIconContainer, $('div.window-icon')); addClass(minimizeIcon, 'window-minimize'); this._register(addDisposableListener(minimizeIcon, EventType.CLICK, e => { - this.windowService.minimizeWindow(); + this.electronService.minimizeWindow(); })); // Restore @@ -395,12 +379,12 @@ export class TitlebarPart extends Part implements ITitleService { this.maxRestoreControl = append(restoreIconContainer, $('div.window-icon')); addClass(this.maxRestoreControl, 'window-max-restore'); this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async e => { - const maximized = await this.windowService.isMaximized(); + const maximized = await this.electronService.isMaximized(); if (maximized) { - return this.windowService.unmaximizeWindow(); + return this.electronService.unmaximizeWindow(); } - return this.windowService.maximizeWindow(); + return this.electronService.maximizeWindow(); })); // Close @@ -409,7 +393,7 @@ export class TitlebarPart extends Part implements ITitleService { const closeIcon = append(closeIconContainer, $('div.window-icon')); addClass(closeIcon, 'window-close'); this._register(addDisposableListener(closeIcon, EventType.CLICK, e => { - this.windowService.closeWindow(); + this.electronService.closeWindow(); })); // Resizer @@ -417,7 +401,7 @@ export class TitlebarPart extends Part implements ITitleService { const isMaximized = this.environmentService.configuration.maximized ? true : false; this.onDidChangeMaximized(isMaximized); - this.windowService.onDidChangeMaximize(this.onDidChangeMaximized, this); + this._register(this.windowService.onDidChangeMaximize(this.onDidChangeMaximized, this)); } // Since the title area is used to drag the window, we do not want to steal focus from the @@ -489,10 +473,6 @@ export class TitlebarPart extends Part implements ITitleService { } } - private onTitleDoubleclick(): void { - this.windowService.onWindowTitleDoubleClick(); - } - private onUpdateAppIconDragBehavior() { const setting = this.configurationService.getValue('window.doubleClickIconToClose'); if (setting) { diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index b716a9afac..2629f6af73 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -308,7 +308,7 @@ export class CustomTreeView extends Disposable implements ITreeView { getPrimaryActions(): IAction[] { if (this.showCollapseAllAction) { - const collapseAllAction = new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); + const collapseAllAction = new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action codicon-collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); return [...this.menus.getTitleActions(), collapseAllAction]; } else { return this.menus.getTitleActions(); @@ -608,7 +608,7 @@ export class CustomTreeView extends Disposable implements ITreeView { private refreshing: boolean = false; private async doRefresh(elements: ITreeItem[]): Promise { const tree = this.tree; - if (tree) { + if (tree && this.visible) { this.refreshing = true; await Promise.all(elements.map(element => tree.updateChildren(element, true))); elements.map(element => tree.rerender(element)); @@ -921,4 +921,3 @@ class TreeMenus extends Disposable implements IDisposable { return result; } } - diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index 40214d63fb..cbf9e1a868 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -52,9 +52,9 @@ /* File icons in trees */ -.file-icon-themable-tree.align-icons-and-twisties .monaco-tl-twistie:not(.collapsible), -.file-icon-themable-tree .align-icon-with-twisty .monaco-tl-twistie:not(.collapsible), -.file-icon-themable-tree.hide-arrows .monaco-tl-twistie { +.file-icon-themable-tree.align-icons-and-twisties .monaco-tl-twistie:not(.force-twistie):not(.collapsible), +.file-icon-themable-tree .align-icon-with-twisty .monaco-tl-twistie:not(.force-twistie):not(.collapsible), +.file-icon-themable-tree.hide-arrows .monaco-tl-twistie:not(.force-twistie) { background-image: none !important; width: 0 !important; margin-right: 0 !important; diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 7648dacbe1..1716a19464 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -132,7 +132,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView if (!view) { this.toggleViewVisibility(id); } - view = this.getView(id); + view = this.getView(id)!; view.setExpanded(true); if (focus) { view.focus(); @@ -185,7 +185,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView return (this.instantiationService as any).createInstance(viewDescriptor.ctorDescriptor.ctor, ...(viewDescriptor.ctorDescriptor.arguments || []), options) as ViewletPanel; } - protected getView(id: string): ViewletPanel { + protected getView(id: string): ViewletPanel | undefined { return this.panels.filter(view => view.id === id)[0]; } diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index 3a88612de4..81c43aa58f 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -10,18 +10,15 @@ import { Event } from 'vs/base/common/event'; 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 { IWindowService, IURIToOpen, IWindowsService, IOpenSettings, IWindowSettings } from 'vs/platform/windows/common/windows'; 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'; -import { addDisposableListener, EventType, windowOpenNoOpener } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType } from 'vs/base/browser/dom'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { pathsToEditors } from 'vs/workbench/common/editor'; import { IFileService } from 'vs/platform/files/common/files'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; -import { IProcessEnvironment } from 'vs/base/common/platform'; import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/common/historyStorage'; //#region Window @@ -87,89 +84,6 @@ export class SimpleWindowService extends Disposable implements IWindowService { return Promise.resolve(this.hasFocus); } - isMaximized(): Promise { - return Promise.resolve(false); - } - - pickFileFolderAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - pickFileAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - pickFolderAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - pickWorkspaceAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - reloadWindow(): Promise { - window.location.reload(); - - return Promise.resolve(); - } - - openDevTools(): Promise { - return Promise.resolve(); - } - - toggleDevTools(): Promise { - return Promise.resolve(); - } - - closeWorkspace(): Promise { - return Promise.resolve(); - } - - enterWorkspace(_path: URI): Promise { - return Promise.resolve(undefined); - } - - toggleFullScreen(target?: HTMLElement): Promise { - if (!target) { - return Promise.resolve(); - } - - // Chromium - if ((document).fullscreen !== undefined) { - if (!(document).fullscreen) { - - return (target).requestFullscreen().catch(() => { - // if it fails, chromium throws an exception with error undefined. - // re https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen - console.warn('Toggle Full Screen failed'); - }); - } else { - return document.exitFullscreen().catch(() => { - console.warn('Exit Full Screen failed'); - }); - } - } - - // Safari and Edge 14 are all using webkit prefix - if ((document).webkitIsFullScreen !== undefined) { - try { - if (!(document).webkitIsFullScreen) { - (target).webkitRequestFullscreen(); // it's async, but doesn't return a real promise. - } else { - (document).webkitExitFullscreen(); // it's async, but doesn't return a real promise. - } - } catch { - console.warn('Enter/Exit Full Screen failed'); - } - } - - return Promise.resolve(); - } - - setRepresentedFilename(_fileName: string): Promise { - return Promise.resolve(); - } - async getRecentlyOpened(): Promise { const recentlyOpenedRaw = this.storageService.get(SimpleWindowService.RECENTLY_OPENED_KEY, StorageScope.GLOBAL); if (recentlyOpenedRaw) { @@ -224,18 +138,6 @@ export class SimpleWindowService extends Disposable implements IWindowService { return Promise.resolve(); } - maximizeWindow(): Promise { - return Promise.resolve(); - } - - unmaximizeWindow(): Promise { - return Promise.resolve(); - } - - minimizeWindow(): Promise { - return Promise.resolve(); - } - async openWindow(_uris: IURIToOpen[], _options?: IOpenSettings): Promise { const { openFolderInNewWindow } = this.shouldOpenNewWindow(_options); for (let i = 0; i < _uris.length; i++) { @@ -273,40 +175,6 @@ export class SimpleWindowService extends Disposable implements IWindowService { } return { openFolderInNewWindow }; } - - closeWindow(): Promise { - window.close(); - - return Promise.resolve(); - } - - setDocumentEdited(_flag: boolean): Promise { - return Promise.resolve(); - } - - onWindowTitleDoubleClick(): Promise { - return Promise.resolve(); - } - - showMessageBox(_options: Electron.MessageBoxOptions): Promise { - return Promise.resolve({ button: 0 }); - } - - showSaveDialog(_options: Electron.SaveDialogOptions): Promise { - throw new Error('not implemented'); - } - - showOpenDialog(_options: Electron.OpenDialogOptions): Promise { - throw new Error('not implemented'); - } - - updateTouchBar(_items: ISerializableCommandAction[][]): Promise { - return Promise.resolve(); - } - - resolveProxy(url: string): Promise { - return Promise.resolve(undefined); - } } registerSingleton(IWindowService, SimpleWindowService); @@ -329,50 +197,6 @@ export class SimpleWindowsService implements IWindowsService { return Promise.resolve(true); } - pickFileFolderAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - pickFileAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - pickFolderAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - pickWorkspaceAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - reloadWindow(_windowId: number): Promise { - return Promise.resolve(); - } - - openDevTools(_windowId: number): Promise { - return Promise.resolve(); - } - - toggleDevTools(_windowId: number): Promise { - return Promise.resolve(); - } - - closeWorkspace(_windowId: number): Promise { - return Promise.resolve(); - } - - enterWorkspace(_windowId: number, _path: URI): Promise { - return Promise.resolve(undefined); - } - - toggleFullScreen(_windowId: number): Promise { - return Promise.resolve(); - } - - setRepresentedFilename(_windowId: number, _fileName: string): Promise { - return Promise.resolve(); - } - addRecentlyOpened(recents: IRecent[]): Promise { return Promise.resolve(); } @@ -396,129 +220,18 @@ export class SimpleWindowsService implements IWindowsService { return Promise.resolve(); } - closeWindow(_windowId: number): Promise { - return Promise.resolve(); - } - - isMaximized(_windowId: number): Promise { - return Promise.resolve(false); - } - - maximizeWindow(_windowId: number): Promise { - return Promise.resolve(); - } - - minimizeWindow(_windowId: number): Promise { - return Promise.resolve(); - } - - unmaximizeWindow(_windowId: number): Promise { - return Promise.resolve(); - } - - onWindowTitleDoubleClick(_windowId: number): Promise { - return Promise.resolve(); - } - - setDocumentEdited(_windowId: number, _flag: boolean): Promise { - return Promise.resolve(); - } - - quit(): Promise { - return Promise.resolve(); - } - - relaunch(_options: { addArgs?: string[], removeArgs?: string[] }): Promise { - window.location.reload(); - - return Promise.resolve(); - } - - whenSharedProcessReady(): Promise { - return Promise.resolve(); - } - - toggleSharedProcess(): Promise { - return Promise.resolve(); - } - // Global methods openWindow(_windowId: number, _uris: IURIToOpen[], _options: IOpenSettings): Promise { return Promise.resolve(); } - openNewWindow(): Promise { - return Promise.resolve(); - } - - openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { - return Promise.resolve(); - } - getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { return Promise.resolve([]); } - newWindowTab(): Promise { - return Promise.resolve(); - } - - showPreviousWindowTab(): Promise { - return Promise.resolve(); - } - - showNextWindowTab(): Promise { - return Promise.resolve(); - } - - moveWindowTabToNewWindow(): Promise { - return Promise.resolve(); - } - - mergeAllWindowTabs(): Promise { - return Promise.resolve(); - } - - toggleWindowTabsBar(): Promise { - return Promise.resolve(); - } - - updateTouchBar(_windowId: number, _items: ISerializableCommandAction[][]): Promise { - return Promise.resolve(); - } - getActiveWindowId(): Promise { return Promise.resolve(0); } - - // This needs to be handled from browser process to prevent - // foreground ordering issues on Windows - openExternal(_url: string): Promise { - windowOpenNoOpener(_url); - - return Promise.resolve(true); - } - - // TODO: this is a bit backwards - startCrashReporter(_config: Electron.CrashReporterStartOptions): Promise { - return Promise.resolve(); - } - - showMessageBox(_windowId: number, _options: Electron.MessageBoxOptions): Promise { - throw new Error('not implemented'); - } - - showSaveDialog(_windowId: number, _options: Electron.SaveDialogOptions): Promise { - throw new Error('not implemented'); - } - - showOpenDialog(_windowId: number, _options: Electron.OpenDialogOptions): Promise { - throw new Error('not implemented'); - } - - resolveProxy(windowId: number, url: string): Promise { - return Promise.resolve(undefined); - } } registerSingleton(IWindowsService, SimpleWindowsService); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 423fa2dcf3..24551d32b2 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -234,12 +234,6 @@ import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common 'default': true, 'tags': ['usesOnlineServices'] }, - 'workbench.useExperimentalGridLayout': { - 'type': 'boolean', - 'description': nls.localize('workbench.useExperimentalGridLayout', "Enables the grid layout for the workbench. This setting may enable additional layout options for workbench components."), - 'default': true, - 'scope': ConfigurationScope.APPLICATION - }, 'workbench.octiconsUpdate.enabled': { 'type': 'boolean', 'default': true, diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 5152445ec7..9dae518c85 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -325,13 +325,6 @@ export class Workbench extends Layout { ].forEach(({ id, role, classes, options }) => { const partContainer = this.createPart(id, role, classes); - if (!configurationService.getValue('workbench.useExperimentalGridLayout')) { - // TODO@Ben cleanup once moved to grid - // Insert all workbench parts at the beginning. Issue #52531 - // This is primarily for the title bar to allow overriding -webkit-app-region - this.container.insertBefore(partContainer, this.container.lastChild); - } - this.getPart(id).create(partContainer, options); }); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index f744db0eea..758e138bc4 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -19,7 +19,7 @@ import { ICompositeControl } from 'vs/workbench/common/composite'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { IPathData } from 'vs/platform/windows/common/windows'; -import { coalesce } from 'vs/base/common/arrays'; +import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; export const ActiveEditorContext = new RawContextKey('activeEditor', null); export const ActiveEditorIsSaveableContext = new RawContextKey('activeEditorIsSaveable', false); @@ -384,11 +384,7 @@ export abstract class EditorInput extends Disposable implements IEditorInput { * for the input. This allows subclasses to decide late which editor to use for the input on a case by case basis. */ getPreferredEditorId(candidates: string[]): string | undefined { - if (candidates.length > 0) { - return candidates[0]; - } - - return undefined; + return firstOrDefault(candidates); } /** diff --git a/src/vs/workbench/common/editor/dataUriEditorInput.ts b/src/vs/workbench/common/editor/dataUriEditorInput.ts index 5ee0e17820..35195c3074 100644 --- a/src/vs/workbench/common/editor/dataUriEditorInput.ts +++ b/src/vs/workbench/common/editor/dataUriEditorInput.ts @@ -25,10 +25,6 @@ export class DataUriEditorInput extends EditorInput { ) { super(); - this.name = name; - this.description = description; - this.resource = resource; - if (!this.name || !this.description) { const metadata = DataUri.parseMetaData(this.resource); diff --git a/src/vs/workbench/common/editor/resourceEditorModel.ts b/src/vs/workbench/common/editor/resourceEditorModel.ts index d9a383d536..32bec24ef6 100644 --- a/src/vs/workbench/common/editor/resourceEditorModel.ts +++ b/src/vs/workbench/common/editor/resourceEditorModel.ts @@ -9,7 +9,7 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; /** - * An editor model whith an in-memory, readonly content that is backed by an existing editor model. + * An editor model for in-memory, readonly content that is backed by an existing editor model. */ export class ResourceEditorModel extends BaseTextEditorModel { @@ -34,4 +34,4 @@ export class ResourceEditorModel extends BaseTextEditorModel { super.dispose(); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/common/markdownDocumentRenderer.ts b/src/vs/workbench/common/markdownDocumentRenderer.ts new file mode 100644 index 0000000000..170bd1d8b8 --- /dev/null +++ b/src/vs/workbench/common/markdownDocumentRenderer.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as marked from 'vs/base/common/marked/marked'; +import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; +import { ITokenizationSupport, TokenizationRegistry } from 'vs/editor/common/modes'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IModeService } from 'vs/editor/common/services/modeService'; + +/** + * Renders a string of markdown as a document. + * + * Uses VS Code's syntax highlighting code blocks. + */ +export async function renderMarkdownDocument( + text: string, + extensionService: IExtensionService, + modeService: IModeService, +): Promise { + const renderer = await getRenderer(text, extensionService, modeService); + return marked(text, { renderer }); +} + +async function getRenderer( + text: string, + extensionService: IExtensionService, + modeService: IModeService, +): Promise { + let result: Promise[] = []; + const renderer = new marked.Renderer(); + renderer.code = (_code, lang) => { + const modeId = modeService.getModeIdForLanguageName(lang); + if (modeId) { + result.push(extensionService.whenInstalledExtensionsRegistered().then(() => { + modeService.triggerMode(modeId); + return TokenizationRegistry.getPromise(modeId); + })); + } + return ''; + }; + + marked(text, { renderer }); + await Promise.all(result); + + renderer.code = (code, lang) => { + const modeId = modeService.getModeIdForLanguageName(lang); + return `${tokenizeToString(code, modeId ? TokenizationRegistry.get(modeId)! : undefined)}`; + }; + return renderer; +} diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 9d2f234903..093aea3ae8 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -559,6 +559,24 @@ export const NOTIFICATIONS_BORDER = registerColor('notifications.border', { hc: NOTIFICATIONS_CENTER_HEADER_BACKGROUND }, nls.localize('notificationsBorder', "Notifications border color separating from other notifications in the notifications center. Notifications slide in from the bottom right of the window.")); +export const NOTIFICATIONS_ERROR_ICON_FOREGROUND = registerColor('notificationsErrorIcon.foreground', { + dark: '#F48771', + light: '#A1260D', + hc: '#F48771' +}, nls.localize('notificationsErrorIconForeground', "The color used for the notification error icon.")); + +export const NOTIFICATIONS_WARNING_ICON_FOREGROUND = registerColor('notificationsWarningIcon.foreground', { + dark: '#FFCC00', + light: '#DDB100', + hc: '#FFCC00' +}, nls.localize('notificationsWarningIconForeground', "The color used for the notification warning icon.")); + +export const NOTIFICATIONS_INFO_ICON_FOREGROUND = registerColor('notificationsInfoIcon.foreground', { + dark: '#75BEFF', + light: '#007ACC', + hc: '#75BEFF' +}, nls.localize('notificationsInfoIconForeground', "The color used for the notification info icon.")); + /** * Base class for all themable workbench components. */ diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index c4b1fdc87f..a37c171da9 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -130,8 +130,6 @@ export interface IViewDescriptor { readonly when?: ContextKeyExpr; - readonly group?: string; - readonly order?: number; readonly weight?: number; @@ -146,6 +144,11 @@ export interface IViewDescriptor { readonly workspace?: boolean; readonly focusCommand?: { id: string, keybindings?: IKeybindings }; + + // For contributed remote explorer views + readonly group?: string; + + readonly remoteAuthority?: string; } export interface IViewDescriptorCollection extends IDisposable { diff --git a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts index 323b28cc2b..dbe2ae2d5b 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts @@ -359,7 +359,7 @@ export class LanguageConfigurationFileHandler { const schemaId = 'vscode://schemas/language-configuration'; const schema: IJSONSchema = { allowComments: true, - allowsTrailingCommas: true, + allowTrailingCommas: true, default: { comments: { blockComment: ['/*', '*/'], diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index 91c09e06e3..f4349db67c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -94,13 +94,13 @@ export function attachSuggestEnabledInputBoxStyler(widget: IThemable, themeServi export class SuggestEnabledInput extends Widget implements IThemable { - private _onShouldFocusResults = new Emitter(); + private readonly _onShouldFocusResults = new Emitter(); readonly onShouldFocusResults: Event = this._onShouldFocusResults.event; - private _onEnter = new Emitter(); + private readonly _onEnter = new Emitter(); readonly onEnter: Event = this._onEnter.event; - private _onInputDidChange = new Emitter(); + private readonly _onInputDidChange = new Emitter(); readonly onInputDidChange: Event = this._onInputDidChange.event; private readonly inputWidget: CodeEditorWidget; diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index a00d3812ba..b942a6a1b8 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -57,7 +57,7 @@ export class CommentNode extends Disposable { protected toolbar: ToolBar | undefined; private _commentFormActions: CommentFormActions | null = null; - private _onDidDelete = new Emitter(); + private readonly _onDidDelete = new Emitter(); public get domNode(): HTMLElement { return this._domNode; diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 8687b5a6b5..b53e072236 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -65,8 +65,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _commentForm: HTMLElement; private _reviewThreadReplyButton: HTMLElement; private _resizeObserver: any; - private _onDidClose = new Emitter(); - private _onDidCreateThread = new Emitter(); + private readonly _onDidClose = new Emitter(); + private readonly _onDidCreateThread = new Emitter(); private _isExpanded?: boolean; private _collapseAction: Action; private _commentGlyph?: CommentGlyphWidget; diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 950bfdc03e..814f68a88f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -10,7 +10,7 @@ import { coalesce, findFirstInSorted } from 'vs/base/common/arrays'; import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./media/review'; import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { IActiveCodeEditor, ICodeEditor, IEditorMouseEvent, isCodeEditor, isDiffEditor, IViewZone, MouseTargetType } from 'vs/editor/browser/editorBrowser'; @@ -103,7 +103,6 @@ class CommentingRangeDecorator { private decorationOptions: ModelDecorationOptions; private commentingRangeDecorations: CommentingRangeDecoration[] = []; - private disposables: IDisposable[] = []; constructor() { const decorationOptions: IModelDecorationOptions = { @@ -146,14 +145,13 @@ class CommentingRangeDecorator { } public dispose(): void { - this.disposables = dispose(this.disposables); this.commentingRangeDecorations = []; } } export class ReviewController implements IEditorContribution { - private globalToDispose: IDisposable[]; - private localToDispose: IDisposable[]; + private readonly globalToDispose = new DisposableStore(); + private readonly localToDispose = new DisposableStore(); private editor: ICodeEditor; private _commentWidgets: ReviewZoneWidget[]; private _commentInfos: ICommentInfo[]; @@ -175,8 +173,6 @@ export class ReviewController implements IEditorContribution { @IContextMenuService readonly contextMenuService: IContextMenuService, @IQuickInputService private readonly quickInputService: IQuickInputService ) { - this.globalToDispose = []; - this.localToDispose = []; this._commentInfos = []; this._commentWidgets = []; this._pendingCommentCache = {}; @@ -190,20 +186,20 @@ export class ReviewController implements IEditorContribution { this._commentingRangeDecorator = new CommentingRangeDecorator(); - this.globalToDispose.push(this.commentService.onDidDeleteDataProvider(ownerId => { + this.globalToDispose.add(this.commentService.onDidDeleteDataProvider(ownerId => { delete this._pendingCommentCache[ownerId]; this.beginCompute(); })); - this.globalToDispose.push(this.commentService.onDidSetDataProvider(_ => this.beginCompute())); + this.globalToDispose.add(this.commentService.onDidSetDataProvider(_ => this.beginCompute())); - this.globalToDispose.push(this.commentService.onDidSetResourceCommentInfos(e => { + this.globalToDispose.add(this.commentService.onDidSetResourceCommentInfos(e => { const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri; if (editorURI && editorURI.toString() === e.resource.toString()) { this.setComments(e.commentInfos.filter(commentInfo => commentInfo !== null)); } })); - this.globalToDispose.push(this.editor.onDidChangeModel(e => this.onModelChanged(e))); + this.globalToDispose.add(this.editor.onDidChangeModel(e => this.onModelChanged(e))); this.codeEditorService.registerDecorationType(COMMENTEDITOR_DECORATION_KEY, {}); this.beginCompute(); } @@ -326,8 +322,8 @@ export class ReviewController implements IEditorContribution { } public dispose(): void { - this.globalToDispose = dispose(this.globalToDispose); - this.localToDispose = dispose(this.localToDispose); + this.globalToDispose.dispose(); + this.localToDispose.dispose(); this._commentWidgets.forEach(widget => widget.dispose()); @@ -335,15 +331,15 @@ export class ReviewController implements IEditorContribution { } public onModelChanged(e: IModelChangedEvent): void { - this.localToDispose = dispose(this.localToDispose); + this.localToDispose.clear(); this.removeCommentWidgetsAndStoreCache(); - this.localToDispose.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); - this.localToDispose.push(this.editor.onMouseUp(e => this.onEditorMouseUp(e))); + this.localToDispose.add(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); + this.localToDispose.add(this.editor.onMouseUp(e => this.onEditorMouseUp(e))); this._computeCommentingRangeScheduler = new Delayer(200); - this.localToDispose.push({ + this.localToDispose.add({ dispose: () => { if (this._computeCommentingRangeScheduler) { this._computeCommentingRangeScheduler.cancel(); @@ -351,10 +347,10 @@ export class ReviewController implements IEditorContribution { this._computeCommentingRangeScheduler = null; } }); - this.localToDispose.push(this.editor.onDidChangeModelContent(async () => { + this.localToDispose.add(this.editor.onDidChangeModelContent(async () => { this.beginComputeCommentingRanges(); })); - this.localToDispose.push(this.commentService.onDidUpdateCommentThreads(async e => { + this.localToDispose.add(this.commentService.onDidUpdateCommentThreads(async e => { const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri; if (!editorURI) { return; diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index 9af91c0648..bd0188436a 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -144,16 +144,16 @@ margin-right: 4px; } -.monaco-editor .review-widget .head .review-actions > .monaco-action-bar .icon.expand-review-action { +.monaco-editor .review-widget .head .review-actions > .monaco-action-bar .codicon.expand-review-action { background-image: url("./close-light.svg"); background-size: 16px; } -.monaco-editor.vs-dark .review-widget .head .review-actions > .monaco-action-bar .icon.expand-review-action { +.monaco-editor.vs-dark .review-widget .head .review-actions > .monaco-action-bar .codicon.expand-review-action { background-image: url("./close-dark.svg"); } -.monaco-editor.hc-black .review-widget .head .review-actions > .monaco-action-bar .icon.expand-review-action { +.monaco-editor.hc-black .review-widget .head .review-actions > .monaco-action-bar .codicon.expand-review-action { background-image: url("./close-hc.svg"); } @@ -417,7 +417,7 @@ margin: 0; } -.monaco-editor .review-widget .head .review-actions .action-label.icon.close-review-action { +.monaco-editor .review-widget .head .review-actions .action-label.codicon.close-review-action { background: url("./close.svg") center center no-repeat; } diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index a5f1aab44c..35fa4dfc91 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -17,6 +17,7 @@ import { ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/c import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { firstOrDefault } from 'vs/base/common/arrays'; const viewCategory = nls.localize('viewCategory', "View"); @@ -32,7 +33,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: async (accessor: ServicesAccessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService); - const targetResource = resources[0]; + const targetResource = firstOrDefault(resources); if (!targetResource) { return undefined; // {{SQL CARBON EDIT}} strict-null-check } @@ -89,14 +90,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { - order: 40, - command: { - id: REOPEN_WITH_COMMAND_ID, - title: REOPEN_WITH_TITLE, - } -}); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: REOPEN_WITH_COMMAND_ID, diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 013edf7a16..5f6fe629a4 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -135,7 +135,7 @@ export interface IExpressionTemplateData { name: HTMLSpanElement; value: HTMLSpanElement; inputBoxContainer: HTMLElement; - enableInputBox(expression: IExpression, options: IInputBoxOptions): void; + enableInputBox(options: IInputBoxOptions): void; toDispose: IDisposable[]; label: HighlightedLabel; } @@ -159,15 +159,12 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { + const enableInputBox = (options: IInputBoxOptions) => { name.style.display = 'none'; value.style.display = 'none'; inputBoxContainer.style.display = 'initial'; - const inputBox = new InputBox(inputBoxContainer, this.contextViewService, { - placeholder: options.placeholder, - ariaLabel: options.ariaLabel - }); + const inputBox = new InputBox(inputBoxContainer, this.contextViewService, options); const styler = attachInputBoxStyler(inputBox, this.themeService); inputBox.value = replaceWhitespace(options.initialValue); @@ -217,10 +214,10 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer, index: number, data: IExpressionTemplateData): void { const { element } = node; - if (element === this.debugService.getViewModel().getSelectedExpression()) { + if (element === this.debugService.getViewModel().getSelectedExpression() || (element instanceof Variable && element.errorMessage)) { const options = this.getInputBoxOptions(element); if (options) { - data.enableInputBox(element, options); + data.enableInputBox(options); return; } } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index ffe6edfd96..81c0f44202 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -6,7 +6,7 @@ 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 { 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'; @@ -166,11 +166,13 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { const anchor = { x: e.event.posx, y: e.event.posy }; const breakpoints = this.debugService.getModel().getBreakpoints({ lineNumber, uri }); + const actions = this.getContextMenuActions(breakpoints, uri, lineNumber); this.contextMenuService.showContextMenu({ getAnchor: () => anchor, - getActions: () => this.getContextMenuActions(breakpoints, uri, lineNumber), - getActionsContext: () => breakpoints.length ? breakpoints[0] : undefined + getActions: () => actions, + getActionsContext: () => breakpoints.length ? breakpoints[0] : undefined, + onHide: () => dispose(actions) }); } else { const breakpoints = this.debugService.getModel().getBreakpoints({ uri, lineNumber }); @@ -243,7 +245,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.onModelDecorationsChanged())); } - private getContextMenuActions(breakpoints: ReadonlyArray, uri: uri, lineNumber: number, column?: number): Array { + 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"); @@ -371,7 +373,8 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { 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)); + const contextMenuActions = () => this.getContextMenuActions([breakpoint], activeCodeEditor.getModel().uri, breakpoint.lineNumber, breakpoint.column); + inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, desiredBreakpointDecorations[index].options.glyphMarginClassName, breakpoint, this.debugService, this.contextMenuService, contextMenuActions); } return { @@ -394,8 +397,12 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { }); 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)); + // Candidate decoration has a breakpoint attached when a breakpoint is already at that location and we did not yet set a decoration there + // In practice this happens for the first breakpoint that was set on a line + // We could have also rendered this first decoration as part of desiredBreakpointDecorations however at that moment we have no location information + const cssClass = candidate.breakpoint ? getBreakpointMessageAndClassName(this.debugService, candidate.breakpoint).className : 'debug-breakpoint-disabled'; + const contextMenuActions = () => this.getContextMenuActions(candidate.breakpoint ? [candidate.breakpoint] : [], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn); + const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, cssClass, candidate.breakpoint, this.debugService, this.contextMenuService, contextMenuActions); return { decorationId, @@ -524,14 +531,15 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { 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 => { + this.toDispose.push(dom.addDisposableListener(this.domNode, dom.EventType.CONTEXT_MENU, e => { const event = new StandardMouseEvent(e); const anchor = { x: event.posx, y: event.posy }; - + const actions = this.getContextMenuActions(); this.contextMenuService.showContextMenu({ getAnchor: () => anchor, - getActions: () => this.getContextMenuActions(), - getActionsContext: () => this.breakpoint + getActions: () => actions, + getActionsContext: () => this.breakpoint, + onHide: () => dispose(actions) }); })); } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index b79c78c460..9249e04a1b 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -152,6 +152,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi const value = this.getInputValue(this.breakpoint); this.input.getModel().setValue(value); + this.input.focus(); }); this.createBreakpointInput(dom.append(container, $('.inputContainer'))); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 67b08c6236..50e03fb6fd 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -196,7 +196,8 @@ export class BreakpointsView extends ViewletPanel { this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, - getActionsContext: () => element + getActionsContext: () => element, + onHide: () => dispose(actions) }); } @@ -654,9 +655,8 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, br }; } - const session = debugService.getViewModel().focusedSession; if (breakpoint instanceof FunctionBreakpoint) { - if (session && !session.capabilities.supportsFunctionBreakpoints) { + if (!breakpoint.supported) { return { className: 'debug-function-breakpoint-unverified', message: nls.localize('functionBreakpointUnsupported', "Function breakpoints not supported by this debug type"), @@ -670,7 +670,7 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, br } if (breakpoint instanceof DataBreakpoint) { - if (session && !session.capabilities.supportsDataBreakpoints) { + if (!breakpoint.supported) { return { className: 'debug-data-breakpoint-unverified', message: nls.localize('dataBreakpointUnsupported', "Data breakpoints not supported by this debug type"), @@ -685,30 +685,17 @@ export function getBreakpointMessageAndClassName(debugService: IDebugService, br if (breakpoint.logMessage || breakpoint.condition || breakpoint.hitCondition) { const messages: string[] = []; - if (breakpoint.logMessage) { - if (session && !session.capabilities.supportsLogPoints) { - return { - className: 'debug-breakpoint-unsupported', - message: nls.localize('logBreakpointUnsupported', "Logpoints not supported by this debug type"), - }; - } + if (!breakpoint.supported) { + return { + className: 'debug-breakpoint-unsupported', + message: nls.localize('breakpointUnsupported', "Breakpoints of this type are not supported by the debugger"), + }; + } + + if (breakpoint.logMessage) { messages.push(nls.localize('logMessage', "Log Message: {0}", breakpoint.logMessage)); } - - if (session && breakpoint.condition && !session.capabilities.supportsConditionalBreakpoints) { - return { - className: 'debug-breakpoint-unsupported', - message: nls.localize('conditionalBreakpointUnsupported', "Conditional breakpoints not supported by this debug type"), - }; - } - if (session && breakpoint.hitCondition && !session.capabilities.supportsHitConditionalBreakpoints) { - return { - className: 'debug-breakpoint-unsupported', - message: nls.localize('hitBreakpointUnsupported', "Hit conditional breakpoints not supported by this debug type"), - }; - } - if (breakpoint.condition) { messages.push(nls.localize('expression', "Expression: {0}", breakpoint.condition)); } diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index e8f97508b9..1aa03d5b76 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -312,7 +312,7 @@ export class CallStackView extends ViewletPanel { } const actions: IAction[] = []; - const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg: this.getContextForContributedActions(element), shouldForwardArgs: true }, actions, this.contextMenuService); + const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg: this.getContextForContributedActions(element), shouldForwardArgs: false }, actions, this.contextMenuService); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index a8337ee6a7..1c3844aa85 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -128,11 +128,11 @@ Registry.as(WorkbenchExtensions.Workbench).regi const debugCategory = nls.localize('debugCategory', "Debug"); -registry.registerWorkbenchAction(new SyncActionDescriptor(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE.toNegated()), 'Debug: Start Debugging', debugCategory); +registry.registerWorkbenchAction(new SyncActionDescriptor(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }), 'Debug: Start Debugging', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL), 'Debug: Open launch.json', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL), 'Debug: Add Function Breakpoint', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL), 'Debug: Reapply All Breakpoints', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }, CONTEXT_IN_DEBUG_MODE.toNegated()), 'Debug: Start Without Debugging', debugCategory); +registry.registerWorkbenchAction(new SyncActionDescriptor(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }), 'Debug: Start Without Debugging', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL), 'Debug: Remove All Breakpoints', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL), 'Debug: Enable All Breakpoints', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL), 'Debug: Disable All Breakpoints', debugCategory); @@ -556,13 +556,13 @@ if (isMacintosh) { }); }; - registerTouchBarEntry(StartAction.ID, StartAction.LABEL, 0, CONTEXT_IN_DEBUG_MODE.toNegated(), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/continue-tb.png'))); - registerTouchBarEntry(RunAction.ID, RunAction.LABEL, 1, CONTEXT_IN_DEBUG_MODE.toNegated(), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/continue-without-debugging-tb.png'))); - registerTouchBarEntry(CONTINUE_ID, CONTINUE_LABEL, 0, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/continue-tb.png'))); - registerTouchBarEntry(PAUSE_ID, PAUSE_LABEL, 1, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.notEquals('debugState', 'stopped')), URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/pause-tb.png'))); - registerTouchBarEntry(STEP_OVER_ID, STEP_OVER_LABEL, 2, CONTEXT_IN_DEBUG_MODE, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/stepover-tb.png'))); - registerTouchBarEntry(STEP_INTO_ID, STEP_INTO_LABEL, 3, CONTEXT_IN_DEBUG_MODE, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/stepinto-tb.png'))); - registerTouchBarEntry(STEP_OUT_ID, STEP_OUT_LABEL, 4, CONTEXT_IN_DEBUG_MODE, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/stepout-tb.png'))); - registerTouchBarEntry(RESTART_SESSION_ID, RESTART_LABEL, 5, CONTEXT_IN_DEBUG_MODE, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/restart-tb.png'))); - registerTouchBarEntry(STOP_ID, STOP_LABEL, 6, CONTEXT_IN_DEBUG_MODE, URI.parse(registerAndGetAmdImageURL('vs/workbench/contrib/debug/browser/media/stop-tb.png'))); + registerTouchBarEntry(StartAction.ID, StartAction.LABEL, 0, CONTEXT_IN_DEBUG_MODE.toNegated(), URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/continue-tb.png'))); + registerTouchBarEntry(RunAction.ID, RunAction.LABEL, 1, CONTEXT_IN_DEBUG_MODE.toNegated(), URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/continue-without-debugging-tb.png'))); + registerTouchBarEntry(CONTINUE_ID, CONTINUE_LABEL, 0, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/continue-tb.png'))); + registerTouchBarEntry(PAUSE_ID, PAUSE_LABEL, 1, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.notEquals('debugState', 'stopped')), URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/pause-tb.png'))); + registerTouchBarEntry(STEP_OVER_ID, STEP_OVER_LABEL, 2, CONTEXT_IN_DEBUG_MODE, URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/stepover-tb.png'))); + registerTouchBarEntry(STEP_INTO_ID, STEP_INTO_LABEL, 3, CONTEXT_IN_DEBUG_MODE, URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/stepinto-tb.png'))); + registerTouchBarEntry(STEP_OUT_ID, STEP_OUT_LABEL, 4, CONTEXT_IN_DEBUG_MODE, URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/stepout-tb.png'))); + registerTouchBarEntry(RESTART_SESSION_ID, RESTART_LABEL, 5, CONTEXT_IN_DEBUG_MODE, URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/restart-tb.png'))); + registerTouchBarEntry(STOP_ID, STOP_LABEL, 6, CONTEXT_IN_DEBUG_MODE, URI.parse(require.toUrl('vs/workbench/contrib/debug/browser/media/stop-tb.png'))); } diff --git a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts index 0d1752cade..fcc6469ecd 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts @@ -202,7 +202,7 @@ export class FocusSessionActionViewItem extends SelectActionViewItem { this._register(attachSelectBoxStyler(this.selectBox, themeService)); this._register(this.debugService.getViewModel().onDidFocusSession(() => { - const session = this.debugService.getViewModel().focusedSession; + const session = this.getSelectedSession(); if (session) { const index = this.getSessions().indexOf(session); this.select(index); @@ -222,11 +222,11 @@ export class FocusSessionActionViewItem extends SelectActionViewItem { } protected getActionContext(_: string, index: number): any { - return this.debugService.getModel().getSessions()[index]; + return this.getSessions()[index]; } private update() { - const session = this.debugService.getViewModel().focusedSession; + const session = this.getSelectedSession(); const sessions = this.getSessions(); const names = sessions.map(s => { const label = s.getLabel(); @@ -240,10 +240,23 @@ export class FocusSessionActionViewItem extends SelectActionViewItem { this.setOptions(names.map(data => { text: data }), session ? sessions.indexOf(session) : undefined); } + private getSelectedSession(): IDebugSession | undefined { + const session = this.debugService.getViewModel().focusedSession; + return session ? this.mapFocusedSessionToSelected(session) : undefined; + } + protected getSessions(): ReadonlyArray { const showSubSessions = this.configurationService.getValue('debug').showSubSessionsInToolBar; const sessions = this.debugService.getModel().getSessions(); return showSubSessions ? sessions : sessions.filter(s => !s.parentSession); } + + protected mapFocusedSessionToSelected(focusedSession: IDebugSession): IDebugSession { + const showSubSessions = this.configurationService.getValue('debug').showSubSessionsInToolBar; + while (focusedSession.parentSession && !showSubSessions) { + focusedSession = focusedSession.parentSession; + } + return focusedSession; + } } diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index b223636c23..19ea42083a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -5,7 +5,6 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import * as lifecycle from 'vs/base/common/lifecycle'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDebugService, State, IEnablement, IBreakpoint, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; @@ -16,16 +15,16 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { startDebugging } from 'vs/workbench/contrib/debug/common/debugUtils'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; export abstract class AbstractDebugAction extends Action { - protected toDispose: lifecycle.IDisposable[]; + protected toDispose: IDisposable[]; constructor( id: string, label: string, cssClass: string, @IDebugService protected debugService: IDebugService, @IKeybindingService protected keybindingService: IKeybindingService, - public weight?: number ) { super(id, label, cssClass, false); this.toDispose = []; @@ -35,11 +34,11 @@ export abstract class AbstractDebugAction extends Action { this.updateEnablement(); } - public run(e?: any): Promise { + run(_: any): Promise { throw new Error('implement me'); } - public get tooltip(): string { + get tooltip(): string { const keybinding = this.keybindingService.lookupKeybinding(this.id); const keybindingLabel = keybinding && keybinding.getLabel(); @@ -54,13 +53,13 @@ export abstract class AbstractDebugAction extends Action { this.enabled = this.isEnabled(state); } - protected isEnabled(state: State): boolean { + protected isEnabled(_: State): boolean { return true; } - public dispose(): void { + dispose(): void { super.dispose(); - this.toDispose = lifecycle.dispose(this.toDispose); + this.toDispose = dispose(this.toDispose); } } @@ -79,7 +78,7 @@ export class ConfigureAction extends AbstractDebugAction { this.updateClass(); } - public get tooltip(): string { + get tooltip(): string { if (this.debugService.getConfigurationManager().selectedConfiguration.name) { return ConfigureAction.LABEL; } @@ -93,7 +92,7 @@ export class ConfigureAction extends AbstractDebugAction { this.class = configurationCount > 0 ? 'debug-action configure' : 'debug-action configure notification'; } - public run(event?: any): Promise { + run(event?: any): Promise { if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.notificationService.info(nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration.")); return Promise.resolve(); @@ -127,7 +126,7 @@ export class StartAction extends AbstractDebugAction { this.toDispose.push(this.contextService.onDidChangeWorkbenchState(() => this.updateEnablement())); } - public run(): Promise { + run(): Promise { return startDebugging(this.debugService, this.historyService, this.isNoDebug()); } @@ -135,7 +134,7 @@ export class StartAction extends AbstractDebugAction { return false; } - public static isEnabled(debugService: IDebugService) { + static isEnabled(debugService: IDebugService) { const sessions = debugService.getModel().getSessions(); if (debugService.state === State.Initializing) { @@ -176,7 +175,7 @@ export class SelectAndStartAction extends AbstractDebugAction { super(id, label, '', debugService, keybindingService); } - public run(): Promise { + run(): Promise { return this.quickOpenService.show('debug '); } } @@ -189,7 +188,7 @@ export class RemoveBreakpointAction extends Action { super(id, label, 'debug-action remove'); } - public run(breakpoint: IBreakpoint): Promise { + run(breakpoint: IBreakpoint): Promise { return breakpoint instanceof Breakpoint ? this.debugService.removeBreakpoints(breakpoint.getId()) : breakpoint instanceof FunctionBreakpoint ? this.debugService.removeFunctionBreakpoints(breakpoint.getId()) : this.debugService.removeDataBreakpoints(breakpoint.getId()); } @@ -204,13 +203,13 @@ export class RemoveAllBreakpointsAction extends AbstractDebugAction { this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } - public run(): Promise { + run(): Promise { return Promise.all([this.debugService.removeBreakpoints(), this.debugService.removeFunctionBreakpoints(), this.debugService.removeDataBreakpoints()]); } - protected isEnabled(state: State): boolean { + protected isEnabled(_: State): boolean { const model = this.debugService.getModel(); - return super.isEnabled(state) && (model.getBreakpoints().length > 0 || model.getFunctionBreakpoints().length > 0 || model.getDataBreakpoints().length > 0); + return (model.getBreakpoints().length > 0 || model.getFunctionBreakpoints().length > 0 || model.getDataBreakpoints().length > 0); } } @@ -223,13 +222,13 @@ export class EnableAllBreakpointsAction extends AbstractDebugAction { this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } - public run(): Promise { + run(): Promise { return this.debugService.enableOrDisableBreakpoints(true); } - protected isEnabled(state: State): boolean { + protected isEnabled(_: State): boolean { const model = this.debugService.getModel(); - return super.isEnabled(state) && (>model.getBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getExceptionBreakpoints()).some(bp => !bp.enabled); + return (>model.getBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getExceptionBreakpoints()).some(bp => !bp.enabled); } } @@ -242,13 +241,13 @@ export class DisableAllBreakpointsAction extends AbstractDebugAction { this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } - public run(): Promise { + run(): Promise { return this.debugService.enableOrDisableBreakpoints(false); } - protected isEnabled(state: State): boolean { + protected isEnabled(_: State): boolean { const model = this.debugService.getModel(); - return super.isEnabled(state) && (>model.getBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getExceptionBreakpoints()).some(bp => bp.enabled); + return (>model.getBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getExceptionBreakpoints()).some(bp => bp.enabled); } } @@ -267,11 +266,11 @@ export class ToggleBreakpointsActivatedAction extends AbstractDebugAction { })); } - public run(): Promise { + run(): Promise { return this.debugService.setBreakpointsActivated(!this.debugService.getModel().areBreakpointsActivated()); } - protected isEnabled(state: State): boolean { + protected isEnabled(_: State): boolean { return !!(this.debugService.getModel().getFunctionBreakpoints().length || this.debugService.getModel().getBreakpoints().length || this.debugService.getModel().getDataBreakpoints().length); } } @@ -285,13 +284,13 @@ export class ReapplyBreakpointsAction extends AbstractDebugAction { this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } - public run(): Promise { + run(): Promise { return this.debugService.setBreakpointsActivated(true); } protected isEnabled(state: State): boolean { const model = this.debugService.getModel(); - return super.isEnabled(state) && (state === State.Running || state === State.Stopped) && + return (state === State.Running || state === State.Stopped) && ((model.getFunctionBreakpoints().length + model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getDataBreakpoints().length) > 0); } } @@ -305,12 +304,12 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction { this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } - public run(): Promise { + run(): Promise { this.debugService.addFunctionBreakpoint(); return Promise.resolve(); } - protected isEnabled(state: State): boolean { + protected isEnabled(_: State): boolean { return !this.debugService.getViewModel().getSelectedFunctionBreakpoint() && this.debugService.getModel().getFunctionBreakpoints().every(fbp => !!fbp.name); } @@ -326,14 +325,14 @@ export class AddWatchExpressionAction extends AbstractDebugAction { this.toDispose.push(this.debugService.getViewModel().onDidSelectExpression(() => this.updateEnablement())); } - public run(): Promise { + run(): Promise { this.debugService.addWatchExpression(); return Promise.resolve(undefined); } - protected isEnabled(state: State): boolean { + protected isEnabled(_: State): boolean { const focusedExpression = this.debugService.getViewModel().getSelectedExpression(); - return super.isEnabled(state) && this.debugService.getModel().getWatchExpressions().every(we => !!we.name && we !== focusedExpression); + return this.debugService.getModel().getWatchExpressions().every(we => !!we.name && we !== focusedExpression); } } @@ -346,13 +345,13 @@ export class RemoveAllWatchExpressionsAction extends AbstractDebugAction { this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement())); } - public run(): Promise { + run(): Promise { this.debugService.removeWatchExpressions(); return Promise.resolve(); } - protected isEnabled(state: State): boolean { - return super.isEnabled(state) && this.debugService.getModel().getWatchExpressions().length > 0; + protected isEnabled(_: State): boolean { + return this.debugService.getModel().getWatchExpressions().length > 0; } } @@ -365,10 +364,10 @@ export class FocusSessionAction extends AbstractDebugAction { @IKeybindingService keybindingService: IKeybindingService, @IEditorService private readonly editorService: IEditorService ) { - super(id, label, '', debugService, keybindingService, 100); + super(id, label, '', debugService, keybindingService); } - public run(session: IDebugSession): Promise { + run(session: IDebugSession): Promise { this.debugService.focusStackFrame(undefined, undefined, session, true); const stackFrame = this.debugService.getViewModel().focusedStackFrame; if (stackFrame) { @@ -392,17 +391,19 @@ export class CopyValueAction extends Action { this._enabled = typeof this.value === 'string' || (this.value instanceof Variable && !!this.value.evaluateName); } - public run(): Promise { + async run(): Promise { const stackFrame = this.debugService.getViewModel().focusedStackFrame; const session = this.debugService.getViewModel().focusedSession; if (this.value instanceof Variable && stackFrame && session && this.value.evaluateName) { - return session.evaluate(this.value.evaluateName, stackFrame.frameId, this.context).then(result => { - return this.clipboardService.writeText(result.body.result); - }, err => this.clipboardService.writeText(this.value.value)); + try { + const evaluation = await session.evaluate(this.value.evaluateName, stackFrame.frameId, this.context); + this.clipboardService.writeText(evaluation.body.result); + } catch (e) { + this.clipboardService.writeText(this.value.value); + } } - return this.clipboardService.writeText(this.value); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 66004f31e2..1a1a8c3461 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -50,7 +50,7 @@ export class ConfigurationManager implements IConfigurationManager { private selectedName: string | undefined; private selectedLaunch: ILaunch | undefined; private toDispose: IDisposable[]; - private _onDidSelectConfigurationName = new Emitter(); + private readonly _onDidSelectConfigurationName = new Emitter(); private configProviders: IDebugConfigurationProvider[]; private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[]; private debugAdapterFactories = new Map(); diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 5492749b70..a292fee6f4 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -137,7 +137,7 @@ export class DebugHoverWidget implements IContentWidget { return this.domNode; } - showAt(range: Range, focus: boolean): Promise { + async showAt(range: Range, focus: boolean): Promise { const pos = range.getStartPosition(); const session = this.debugService.getViewModel().focusedSession; @@ -153,63 +153,64 @@ export class DebugHoverWidget implements IContentWidget { return Promise.resolve(this.hide()); } - let promise: Promise; + let expression; if (session.capabilities.supportsEvaluateForHovers) { - const result = new Expression(matchingExpression); - promise = result.evaluate(session, this.debugService.getViewModel().focusedStackFrame, 'hover').then(() => result); + expression = new Expression(matchingExpression); + await expression.evaluate(session, this.debugService.getViewModel().focusedStackFrame, 'hover'); } else { - promise = this.findExpressionInStackFrame(coalesce(matchingExpression.split('.').map(word => word.trim()))); + expression = await this.findExpressionInStackFrame(coalesce(matchingExpression.split('.').map(word => word.trim()))); } - return promise.then(expression => { - if (!expression || (expression instanceof Expression && !expression.available)) { - this.hide(); - return undefined; - } + if (!expression || (expression instanceof Expression && !expression.available)) { + this.hide(); + return undefined; + } - this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{ - range: new Range(pos.lineNumber, start, pos.lineNumber, start + matchingExpression.length), - options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS - }]); + this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{ + range: new Range(pos.lineNumber, start, pos.lineNumber, start + matchingExpression.length), + options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS + }]); - return this.doShow(pos, expression, focus); - }); + return this.doShow(pos, expression, focus); } private static _HOVER_HIGHLIGHT_DECORATION_OPTIONS = ModelDecorationOptions.register({ className: 'hoverHighlight' }); - private doFindExpression(container: IExpressionContainer, namesToFind: string[]): Promise { + private async doFindExpression(container: IExpressionContainer, namesToFind: string[]): Promise { if (!container) { return Promise.resolve(null); } - return container.getChildren().then(children => { - // look for our variable in the list. First find the parents of the hovered variable if there are any. - const filtered = children.filter(v => namesToFind[0] === v.name); - if (filtered.length !== 1) { - return null; - } + const children = await container.getChildren(); + // look for our variable in the list. First find the parents of the hovered variable if there are any. + const filtered = children.filter(v => namesToFind[0] === v.name); + if (filtered.length !== 1) { + return null; + } - if (namesToFind.length === 1) { - return filtered[0]; - } else { - return this.doFindExpression(filtered[0], namesToFind.slice(1)); - } - }); + if (namesToFind.length === 1) { + return filtered[0]; + } else { + return this.doFindExpression(filtered[0], namesToFind.slice(1)); + } } - private findExpressionInStackFrame(namesToFind: string[]): Promise { - return this.debugService.getViewModel().focusedStackFrame!.getScopes() - .then(scopes => scopes.filter(s => !s.expensive)) - .then(scopes => Promise.all(scopes.map(scope => this.doFindExpression(scope, namesToFind)))) - .then(coalesce) - // only show if all expressions found have the same value - .then(expressions => (expressions.length > 0 && expressions.every(e => e.value === expressions[0].value)) ? expressions[0] : undefined); + private async findExpressionInStackFrame(namesToFind: string[]): Promise { + const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; + if (!focusedStackFrame) { + return undefined; + } + + const scopes = await focusedStackFrame.getScopes(); + const nonExpensive = scopes.filter(s => !s.expensive); + const expressions = coalesce(await Promise.all(nonExpensive.map(scope => this.doFindExpression(scope, namesToFind)))); + // only show if all expressions found have the same value + return expressions.length > 0 && expressions.every(e => e.value === expressions[0].value) ? expressions[0] : undefined; } - private doShow(position: Position, expression: IExpression, focus: boolean, forceValueHover = false): Promise { + private async doShow(position: Position, expression: IExpression, focus: boolean, forceValueHover = false): Promise { if (!this.domNode) { this.create(); } @@ -239,20 +240,19 @@ export class DebugHoverWidget implements IContentWidget { this.valueContainer.hidden = true; this.complexValueContainer.hidden = false; - return this.tree.setInput(expression).then(() => { - this.complexValueTitle.textContent = replaceWhitespace(expression.value); - this.complexValueTitle.title = expression.value; - this.layoutTreeAndContainer(); - this.editor.layoutContentWidget(this); - this.scrollbar.scanDomNode(); - this.tree.scrollTop = 0; - this.tree.scrollLeft = 0; + await this.tree.setInput(expression); + this.complexValueTitle.textContent = replaceWhitespace(expression.value); + this.complexValueTitle.title = expression.value; + this.layoutTreeAndContainer(); + this.editor.layoutContentWidget(this); + this.scrollbar.scanDomNode(); + this.tree.scrollTop = 0; + this.tree.scrollLeft = 0; - if (focus) { - this.editor.render(); - this.tree.domFocus(); - } - }); + if (focus) { + this.editor.render(); + this.tree.domFocus(); + } } private layoutTreeAndContainer(): void { diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts index 4e37b988a6..030944a046 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts @@ -21,19 +21,19 @@ class AddConfigEntry extends QuickOpenEntry { super(highlights); } - public getLabel(): string { + getLabel(): string { return this.label; } - public getDescription(): string { + getDescription(): string { return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.name : ''; } - public getAriaLabel(): string { + getAriaLabel(): string { return nls.localize('entryAriaLabel', "{0}, debug", this.getLabel()); } - public run(mode: Mode): boolean { + run(mode: Mode): boolean { if (mode === Mode.PREVIEW) { return false; } @@ -49,19 +49,19 @@ class StartDebugEntry extends QuickOpenEntry { super(highlights); } - public getLabel(): string { + getLabel(): string { return this.configurationName; } - public getDescription(): string { + getDescription(): string { return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.name : ''; } - public getAriaLabel(): string { + getAriaLabel(): string { return nls.localize('entryAriaLabel', "{0}, debug", this.getLabel()); } - public run(mode: Mode): boolean { + run(mode: Mode): boolean { if (mode === Mode.PREVIEW || !StartAction.isEnabled(this.debugService)) { return false; } @@ -75,7 +75,7 @@ class StartDebugEntry extends QuickOpenEntry { export class DebugQuickOpenHandler extends QuickOpenHandler { - public static readonly ID = 'workbench.picker.launch'; + static readonly ID = 'workbench.picker.launch'; private autoFocusIndex: number | undefined; @@ -88,11 +88,11 @@ export class DebugQuickOpenHandler extends QuickOpenHandler { super(); } - public getAriaLabel(): string { + getAriaLabel(): string { return nls.localize('debugAriaLabel', "Type a name of a launch configuration to run."); } - public getResults(input: string, token: CancellationToken): Promise { + getResults(input: string, token: CancellationToken): Promise { const configurations: QuickOpenEntry[] = []; const configManager = this.debugService.getConfigurationManager(); @@ -122,14 +122,14 @@ export class DebugQuickOpenHandler extends QuickOpenHandler { return Promise.resolve(new QuickOpenModel(configurations)); } - public getAutoFocus(input: string): IAutoFocus { + getAutoFocus(input: string): IAutoFocus { return { autoFocusFirstEntry: !!input, autoFocusIndex: this.autoFocusIndex }; } - public getEmptyLabel(searchString: string): string { + getEmptyLabel(searchString: string): string { if (searchString.length > 0) { return nls.localize('noConfigurationsMatching', "No debug configurations matching"); } diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index ddc7f44415..e15bfd6662 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -41,7 +41,7 @@ import { IAction } from 'vs/base/common/actions'; import { deepClone, equals } from 'vs/base/common/objects'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug'; import { isExtensionHostDebugging } from 'vs/workbench/contrib/debug/common/debugUtils'; import { isErrorWithActions, createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -167,9 +167,7 @@ export class DebugService implements IDebugService { this.toDispose.push(this.viewModel.onDidFocusStackFrame(() => { this.onStateChange(); })); - this.toDispose.push(this.viewModel.onDidFocusSession(session => { - const id = session ? session.getId() : undefined; - this.model.setBreakpointsSessionId(id); + this.toDispose.push(this.viewModel.onDidFocusSession(() => { this.onStateChange(); })); } @@ -255,7 +253,7 @@ export class DebugService implements IDebugService { * main entry point * properly manages compounds, checks for errors and handles the initializing state. */ - startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, noDebug = false, parentSession?: IDebugSession): Promise { + startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise { this.startInitializingState(); // make sure to save all files and that the configuration is up to date @@ -318,7 +316,7 @@ export class DebugService implements IDebugService { } } - return this.createSession(launchForName, launchForName!.getConfiguration(name), noDebug, parentSession); + return this.createSession(launchForName, launchForName!.getConfiguration(name), options); })).then(values => values.every(success => !!success)); // Compound launch is a success only if each configuration launched successfully } @@ -328,7 +326,7 @@ export class DebugService implements IDebugService { return Promise.reject(new Error(message)); } - return this.createSession(launch, config, noDebug, parentSession); + return this.createSession(launch, config, options); }); })); }).then(success => { @@ -344,7 +342,7 @@ export class DebugService implements IDebugService { /** * gets the debugger for the type, resolves configurations by providers, substitutes variables and runs prelaunch tasks */ - private createSession(launch: ILaunch | undefined, config: IConfig | undefined, noDebug: boolean, parentSession?: IDebugSession): Promise { + private createSession(launch: ILaunch | undefined, config: IConfig | undefined, options?: IDebugSessionOptions): Promise { // We keep the debug type in a separate variable 'type' so that a no-folder config has no attributes. // Storing the type in the config would break extensions that assume that the no-folder case is indicated by an empty config. let type: string | undefined; @@ -356,7 +354,7 @@ export class DebugService implements IDebugService { } const unresolvedConfig = deepClone(config); - if (noDebug) { + if (options && options.noDebug) { config!.noDebug = true; } @@ -390,7 +388,7 @@ export class DebugService implements IDebugService { const workspace = launch ? launch.workspace : undefined; return this.runTaskAndCheckErrors(workspace, resolvedConfig.preLaunchTask).then(result => { if (result === TaskRunResult.Success) { - return this.doCreateSession(workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, parentSession); + return this.doCreateSession(workspace, { resolved: resolvedConfig, unresolved: unresolvedConfig }, options); } return false; }); @@ -419,9 +417,9 @@ export class DebugService implements IDebugService { /** * instantiates the new session, initializes the session, registers session listeners and reports telemetry */ - private doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, parentSession?: IDebugSession): Promise { + private doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }, options?: IDebugSessionOptions): Promise { - const session = this.instantiationService.createInstance(DebugSession, configuration, root, this.model, parentSession); + const session = this.instantiationService.createInstance(DebugSession, configuration, root, this.model, options); this.model.addSession(session); // register listeners as the very first thing! this.registerSessionListeners(session); diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index ebdac647cc..9f56965b16 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -12,7 +12,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { CompletionItem, completionKindFromString } from 'vs/editor/common/modes'; 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 { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions } 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'; @@ -43,6 +43,7 @@ export class DebugSession implements IDebugSession { private _subId: string | undefined; private raw: RawDebugSession | undefined; private initialized = false; + private _options: IDebugSessionOptions; private sources = new Map(); private threads = new Map(); @@ -66,7 +67,7 @@ export class DebugSession implements IDebugSession { private _configuration: { resolved: IConfig, unresolved: IConfig | undefined }, public root: IWorkspaceFolder, private model: DebugModel, - private _parentSession: IDebugSession | undefined, + options: IDebugSessionOptions | undefined, @IDebugService private readonly debugService: IDebugService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IWindowService private readonly windowService: IWindowService, @@ -79,7 +80,12 @@ export class DebugSession implements IDebugSession { @IOpenerService private readonly openerService: IOpenerService ) { this.id = generateUuid(); - this.repl = new ReplModel(this); + this._options = options || {}; + if (this.hasSeparateRepl()) { + this.repl = new ReplModel(); + } else { + this.repl = (this.parentSession as DebugSession).repl; + } } getId(): string { @@ -103,7 +109,7 @@ export class DebugSession implements IDebugSession { } get parentSession(): IDebugSession | undefined { - return this._parentSession; + return this._options.parentSession; } setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig | undefined }) { @@ -307,7 +313,7 @@ export class DebugSession implements IDebugSession { data.set(breakpointsToSend[i].getId(), response.body.breakpoints[i]); } - this.model.setBreakpointSessionData(this.getId(), data); + this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } }); } @@ -321,7 +327,7 @@ export class DebugSession implements IDebugSession { for (let i = 0; i < fbpts.length; i++) { data.set(fbpts[i].getId(), response.body.breakpoints[i]); } - this.model.setBreakpointSessionData(this.getId(), data); + this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } }); } @@ -361,7 +367,7 @@ export class DebugSession implements IDebugSession { for (let i = 0; i < dataBreakpoints.length; i++) { data.set(dataBreakpoints[i].getId(), response.body.breakpoints[i]); } - this.model.setBreakpointSessionData(this.getId(), data); + this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } }); } @@ -838,7 +844,7 @@ export class DebugSession implements IDebugSession { }], false); if (bps.length === 1) { const data = new Map([[bps[0].getId(), event.body.breakpoint]]); - this.model.setBreakpointSessionData(this.getId(), data); + this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } } @@ -857,11 +863,11 @@ export class DebugSession implements IDebugSession { event.body.breakpoint.column = undefined; } const data = new Map([[breakpoint.getId(), event.body.breakpoint]]); - this.model.setBreakpointSessionData(this.getId(), data); + this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } if (functionBreakpoint) { const data = new Map([[functionBreakpoint.getId(), event.body.breakpoint]]); - this.model.setBreakpointSessionData(this.getId(), data); + this.model.setBreakpointSessionData(this.getId(), this.capabilities, data); } } })); @@ -879,6 +885,7 @@ export class DebugSession implements IDebugSession { this.rawListeners.push(this.raw.onDidExitAdapter(event => { this.initialized = true; + this.model.setBreakpointSessionData(this.getId(), this.capabilities, undefined); this._onDidEndAdapter.fire(event); })); } @@ -954,13 +961,17 @@ export class DebugSession implements IDebugSession { return this.repl.getReplElements(); } + hasSeparateRepl(): boolean { + return !this.parentSession || this._options.repl !== 'mergeWithParent'; + } + removeReplExpressions(): void { this.repl.removeReplExpressions(); this._onDidChangeREPLElements.fire(); } async addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise { - const expressionEvaluated = this.repl.addReplExpression(stackFrame, name); + const expressionEvaluated = this.repl.addReplExpression(this, stackFrame, name); this._onDidChangeREPLElements.fire(); await expressionEvaluated; this._onDidChangeREPLElements.fire(); @@ -974,7 +985,7 @@ export class DebugSession implements IDebugSession { } logToRepl(sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) { - this.repl.logToRepl(sev, args, frame); + this.repl.logToRepl(this, sev, args, frame); this._onDidChangeREPLElements.fire(); } } diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-disabled.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-disabled.svg index faf015508c..ea246058e0 100644 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-disabled.svg +++ b/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-disabled.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-unverified.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-unverified.svg index f83544babf..ae8ed0ba7b 100644 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-unverified.svg +++ b/src/vs/workbench/contrib/debug/browser/media/breakpoint-log-unverified.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-log.svg index 509e255b91..fc72afc7e2 100644 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-log.svg +++ b/src/vs/workbench/contrib/debug/browser/media/breakpoint-log.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/debug/browser/media/breakpoint-unsupported.svg b/src/vs/workbench/contrib/debug/browser/media/breakpoint-unsupported.svg index 4a40c0fd1e..624b9f60c8 100644 --- a/src/vs/workbench/contrib/debug/browser/media/breakpoint-unsupported.svg +++ b/src/vs/workbench/contrib/debug/browser/media/breakpoint-unsupported.svg @@ -1,3 +1,3 @@ - + diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 3d5dc43499..f36708fb79 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -356,7 +356,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati getActions(): IAction[] { const result: IAction[] = []; - if (this.debugService.getModel().getSessions(true).filter(s => !sessionsToIgnore.has(s)).length > 1) { + if (this.debugService.getModel().getSessions(true).filter(s => s.hasSeparateRepl() && !sessionsToIgnore.has(s)).length > 1) { result.push(this.selectReplAction); } result.push(this.clearReplAction); @@ -504,7 +504,8 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, - getActionsContext: () => e.element + getActionsContext: () => e.element, + onHide: () => dispose(actions) }); } @@ -968,12 +969,15 @@ registerEditorAction(FilterReplAction); class SelectReplActionViewItem extends FocusSessionActionViewItem { - protected getActionContext(_: string, index: number): any { - return this.debugService.getModel().getSessions(true)[index]; + protected getSessions(): ReadonlyArray { + return this.debugService.getModel().getSessions(true).filter(s => s.hasSeparateRepl() && !sessionsToIgnore.has(s)); } - protected getSessions(): ReadonlyArray { - return this.debugService.getModel().getSessions(true).filter(s => !sessionsToIgnore.has(s)); + protected mapFocusedSessionToSelected(focusedSession: IDebugSession): IDebugSession { + while (focusedSession.parentSession && !focusedSession.hasSeparateRepl()) { + focusedSession = focusedSession.parentSession; + } + return focusedSession; } } diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index de88d3a723..bcf0a80083 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -30,6 +30,7 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { dispose } from 'vs/base/common/lifecycle'; const $ = dom.$; let forgetScopes = true; @@ -188,7 +189,8 @@ export class VariablesView extends ViewletPanel { this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, - getActionsContext: () => variable + getActionsContext: () => variable, + onHide: () => dispose(actions) }); } } diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 9d8e37aaba..803fe02cb1 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -30,6 +30,7 @@ import { FuzzyScore } from 'vs/base/common/filters'; import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { variableSetEmitter, VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { dispose } from 'vs/base/common/lifecycle'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; @@ -175,7 +176,8 @@ export class WatchExpressionsView extends ViewletPanel { this.contextMenuService.showContextMenu({ getAnchor: () => anchor, getActions: () => actions, - getActionsContext: () => element + getActionsContext: () => element, + onHide: () => dispose(actions) }); } } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index cfdd333b43..4ec6d97c11 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -147,6 +147,14 @@ export interface LoadedSourceEvent { source: Source; } +export type IDebugSessionReplMode = 'separate' | 'mergeWithParent'; + +export interface IDebugSessionOptions { + noDebug?: boolean; + parentSession?: IDebugSession; + repl?: IDebugSessionReplMode; +} + export interface IDebugSession extends ITreeElement { readonly configuration: IConfig; @@ -173,7 +181,7 @@ export interface IDebugSession extends ITreeElement { clearThreads(removeThreads: boolean, reference?: number): void; getReplElements(): IReplElement[]; - + hasSeparateRepl(): boolean; removeReplExpressions(): void; addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise; appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void; @@ -311,7 +319,7 @@ export interface IStackFrame extends ITreeElement { forgetScopes(): void; restart(): Promise; toString(): string; - openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean): Promise; + openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean): Promise; equals(other: IStackFrame): boolean; } @@ -342,6 +350,7 @@ export interface IBaseBreakpoint extends IEnablement { readonly hitCondition?: string; readonly logMessage?: string; readonly verified: boolean; + readonly supported: boolean; getIdFromAdapter(sessionId: string): number | undefined; } @@ -815,7 +824,7 @@ export interface IDebugService { * Returns true if the start debugging was successfull. For compound launches, all configurations have to start successfuly for it to return success. * On errors the startDebugging will throw an error, however some error and cancelations are handled and in that case will simply return false. */ - startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, noDebug?: boolean, parentSession?: IDebugSession): Promise; + startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise; /** * Restarts a session or creates a new one if there is no active session. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 0598be46a8..34403a50d1 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -23,6 +23,7 @@ import { posix } from 'vs/base/common/path'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextEditor } from 'vs/workbench/common/editor'; +import { mixin } from 'vs/base/common/objects'; export class ExpressionContainer implements IExpressionContainer { @@ -337,8 +338,8 @@ export class StackFrame implements IStackFrame { return sourceToString === UNKNOWN_SOURCE_LABEL ? this.name : `${this.name} (${sourceToString})`; } - openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { - return !this.source.available ? Promise.resolve(null) : + openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { + return !this.source.available ? Promise.resolve(undefined) : this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned); } @@ -498,10 +499,28 @@ export class Enablement implements IEnablement { } } -export class BaseBreakpoint extends Enablement implements IBaseBreakpoint { +interface IBreakpointSessionData extends DebugProtocol.Breakpoint { + supportsConditionalBreakpoints: boolean; + supportsHitConditionalBreakpoints: boolean; + supportsLogPoints: boolean; + supportsFunctionBreakpoints: boolean; + supportsDataBreakpoints: boolean; +} - private sessionData = new Map(); - private sessionId: string | undefined; +function toBreakpointSessionData(data: DebugProtocol.Breakpoint, capabilities: DebugProtocol.Capabilities): IBreakpointSessionData { + return mixin({ + supportsConditionalBreakpoints: !!capabilities.supportsConditionalBreakpoints, + supportsHitConditionalBreakpoints: !!capabilities.supportsHitConditionalBreakpoints, + supportsLogPoints: !!capabilities.supportsLogPoints, + supportsFunctionBreakpoints: !!capabilities.supportsFunctionBreakpoints, + supportsDataBreakpoints: !!capabilities.supportsDataBreakpoints + }, data); +} + +export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoint { + + private sessionData = new Map(); + protected data: IBreakpointSessionData | undefined; constructor( enabled: boolean, @@ -516,32 +535,38 @@ export class BaseBreakpoint extends Enablement implements IBaseBreakpoint { } } - protected getSessionData(): DebugProtocol.Breakpoint | undefined { - return this.sessionId ? this.sessionData.get(this.sessionId) : undefined; - } + setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void { + if (!data) { + this.sessionData.delete(sessionId); + } else { + this.sessionData.set(sessionId, data); + } - setSessionData(sessionId: string, data: DebugProtocol.Breakpoint): void { - this.sessionData.set(sessionId, data); - } - - setSessionId(sessionId: string | undefined): void { - this.sessionId = sessionId; + const allData = Array.from(this.sessionData.values()); + const verifiedData = distinct(allData.filter(d => d.verified), d => `${d.line}:${d.column}`); + if (verifiedData.length) { + // In case multiple session verified the breakpoint and they provide different data show the intial data that the user set (corner case) + this.data = verifiedData.length === 1 ? verifiedData[0] : undefined; + } else { + // No session verified the breakpoint + this.data = allData.length ? allData[0] : undefined; + } } get message(): string | undefined { - const data = this.getSessionData(); - if (!data) { + if (!this.data) { return undefined; } - return data.message; + return this.data.message; } get verified(): boolean { - const data = this.getSessionData(); - return data ? data.verified : true; + return this.data ? this.data.verified : true; } + abstract get supported(): boolean; + getIdFromAdapter(sessionId: string): number | undefined { const data = this.sessionData.get(sessionId); return data ? data.id : undefined; @@ -576,23 +601,20 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { } get lineNumber(): number { - const data = this.getSessionData(); - return this.verified && data && typeof data.line === 'number' ? data.line : this._lineNumber; + return this.verified && this.data && typeof this.data.line === 'number' ? this.data.line : this._lineNumber; } get verified(): boolean { - const data = this.getSessionData(); - if (data) { - return data.verified && !this.textFileService.isDirty(this.uri); + if (this.data) { + return this.data.verified && !this.textFileService.isDirty(this.uri); } return true; } get column(): number | undefined { - const data = this.getSessionData(); // Only respect the column if the user explictly set the column to have an inline breakpoint - return data && typeof data.column === 'number' && typeof this._column === 'number' ? data.column : this._column; + return this.verified && this.data && typeof this.data.column === 'number' && typeof this._column === 'number' ? this.data.column : this._column; } get message(): string | undefined { @@ -604,18 +626,15 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { } get adapterData(): any { - const data = this.getSessionData(); - return data && data.source && data.source.adapterData ? data.source.adapterData : this._adapterData; + return this.data && this.data.source && this.data.source.adapterData ? this.data.source.adapterData : this._adapterData; } get endLineNumber(): number | undefined { - const data = this.getSessionData(); - return data ? data.endLine : undefined; + return this.verified && this.data ? this.data.endLine : undefined; } get endColumn(): number | undefined { - const data = this.getSessionData(); - return data ? data.endColumn : undefined; + return this.verified && this.data ? this.data.endColumn : undefined; } get sessionAgnosticData(): { lineNumber: number, column: number | undefined } { @@ -625,7 +644,25 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { }; } - setSessionData(sessionId: string, data: DebugProtocol.Breakpoint): void { + get supported(): boolean { + if (!this.data) { + return true; + } + if (this.logMessage && !this.data.supportsLogPoints) { + return false; + } + if (this.condition && !this.data.supportsConditionalBreakpoints) { + return false; + } + if (this.hitCondition && !this.data.supportsHitConditionalBreakpoints) { + return false; + } + + return true; + } + + + setSessionData(sessionId: string, data: IBreakpointSessionData | undefined): void { super.setSessionData(sessionId, data); if (!this._adapterData) { this._adapterData = this.adapterData; @@ -685,6 +722,14 @@ export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreak return result; } + get supported(): boolean { + if (!this.data) { + return true; + } + + return this.data.supportsFunctionBreakpoints; + } + toString(): string { return this.name; } @@ -713,6 +758,14 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { return result; } + get supported(): boolean { + if (!this.data) { + return true; + } + + return this.data.supportsDataBreakpoints; + } + toString(): string { return this.label; } @@ -751,7 +804,6 @@ export class DebugModel implements IDebugModel { private sessions: IDebugSession[]; private toDispose: lifecycle.IDisposable[]; private schedulers = new Map(); - private breakpointsSessionId: string | undefined; private breakpointsActivated = true; private readonly _onDidChangeBreakpoints: Emitter; private readonly _onDidChangeCallStack: Emitter; @@ -945,7 +997,6 @@ export class DebugModel implements IDebugModel { addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): IBreakpoint[] { const newBreakpoints = rawData.map(rawBp => new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled === false ? false : true, rawBp.condition, rawBp.hitCondition, rawBp.logMessage, undefined, this.textFileService, rawBp.id)); - newBreakpoints.forEach(bp => bp.setSessionId(this.breakpointsSessionId)); this.breakpoints = this.breakpoints.concat(newBreakpoints); this.breakpointsActivated = true; this.sortAndDeDup(); @@ -975,23 +1026,35 @@ export class DebugModel implements IDebugModel { this._onDidChangeBreakpoints.fire({ changed: updated }); } - setBreakpointSessionData(sessionId: string, data: Map): void { + setBreakpointSessionData(sessionId: string, capabilites: DebugProtocol.Capabilities, data: Map | undefined): void { this.breakpoints.forEach(bp => { - const bpData = data.get(bp.getId()); - if (bpData) { - bp.setSessionData(sessionId, bpData); + if (!data) { + bp.setSessionData(sessionId, undefined); + } else { + const bpData = data.get(bp.getId()); + if (bpData) { + bp.setSessionData(sessionId, toBreakpointSessionData(bpData, capabilites)); + } } }); this.functionBreakpoints.forEach(fbp => { - const fbpData = data.get(fbp.getId()); - if (fbpData) { - fbp.setSessionData(sessionId, fbpData); + if (!data) { + fbp.setSessionData(sessionId, undefined); + } else { + const fbpData = data.get(fbp.getId()); + if (fbpData) { + fbp.setSessionData(sessionId, toBreakpointSessionData(fbpData, capabilites)); + } } }); this.dataBreakopints.forEach(dbp => { - const dbpData = data.get(dbp.getId()); - if (dbpData) { - dbp.setSessionData(sessionId, dbpData); + if (!data) { + dbp.setSessionData(sessionId, undefined); + } else { + const dbpData = data.get(dbp.getId()); + if (dbpData) { + dbp.setSessionData(sessionId, toBreakpointSessionData(dbpData, capabilites)); + } } }); @@ -1000,17 +1063,6 @@ export class DebugModel implements IDebugModel { }); } - setBreakpointsSessionId(sessionId: string | undefined): void { - this.breakpointsSessionId = sessionId; - this.breakpoints.forEach(bp => bp.setSessionId(sessionId)); - this.functionBreakpoints.forEach(fbp => fbp.setSessionId(sessionId)); - this.dataBreakopints.forEach(dbp => dbp.setSessionId(sessionId)); - - this._onDidChangeBreakpoints.fire({ - sessionOnly: true - }); - } - private sortAndDeDup(): void { this.breakpoints = this.breakpoints.sort((first, second) => { if (first.uri.toString() !== second.uri.toString()) { diff --git a/src/vs/workbench/contrib/debug/common/debugSchemas.ts b/src/vs/workbench/contrib/debug/common/debugSchemas.ts index 2e5cf3a16b..3ec967bcb0 100644 --- a/src/vs/workbench/contrib/debug/common/debugSchemas.ts +++ b/src/vs/workbench/contrib/debug/common/debugSchemas.ts @@ -137,7 +137,7 @@ export const launchSchema: IJSONSchema = { id: launchSchemaId, type: 'object', title: nls.localize('app.launch.json.title', "Launch"), - allowsTrailingCommas: true, + allowTrailingCommas: true, allowComments: true, required: [], default: { version: '0.2.0', configurations: [], compounds: [] }, diff --git a/src/vs/workbench/contrib/debug/common/debugSource.ts b/src/vs/workbench/contrib/debug/common/debugSource.ts index 66588efa42..7bf4a4fc2b 100644 --- a/src/vs/workbench/contrib/debug/common/debugSource.ts +++ b/src/vs/workbench/contrib/debug/common/debugSource.ts @@ -13,7 +13,6 @@ import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/ import { Schemas } from 'vs/base/common/network'; import { isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; import { ITextEditor } from 'vs/workbench/common/editor'; -import { withUndefinedAsNull } from 'vs/base/common/types'; export const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source"); @@ -32,9 +31,9 @@ export const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Sourc export class Source { - public readonly uri: uri; - public available: boolean; - public raw: DebugProtocol.Source; + readonly uri: uri; + available: boolean; + raw: DebugProtocol.Source; constructor(raw_: DebugProtocol.Source | undefined, sessionId: string) { let path: string; @@ -94,8 +93,8 @@ export class Source { return this.uri.scheme === DEBUG_SCHEME; } - openInEditor(editorService: IEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { - return !this.available ? Promise.resolve(null) : editorService.openEditor({ + openInEditor(editorService: IEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { + return !this.available ? Promise.resolve(undefined) : editorService.openEditor({ resource: this.uri, description: this.origin, options: { @@ -105,7 +104,7 @@ export class Source { revealInCenterIfOutsideViewport: true, pinned: pinned || (!preserveFocus && !this.inMemory) } - }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(withUndefinedAsNull); + }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } static getEncodedDebugData(modelUri: uri): { name: string, path: string, sessionId?: string, sourceReference?: number } { diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index 6ff93d15e9..c24e62c78a 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -27,7 +27,7 @@ export function startDebugging(debugService: IDebugService, historyService: IHis configurationManager.selectConfiguration(launch); } - return debugService.startDebugging(launch, undefined, noDebug); + return debugService.startDebugging(launch, undefined, { noDebug }); } export function formatPII(value: string, excludePII: boolean, args: { [key: string]: string }): string { diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index 6fe9a77264..0d71e6b43b 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -108,16 +108,14 @@ export class ReplEvaluationResult extends ExpressionContainer implements IReplEl export class ReplModel { private replElements: IReplElement[] = []; - constructor(private session: IDebugSession) { } - getReplElements(): IReplElement[] { return this.replElements; } - async addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise { + async addReplExpression(session: IDebugSession, stackFrame: IStackFrame | undefined, name: string): Promise { this.addReplElement(new ReplEvaluationInput(name)); const result = new ReplEvaluationResult(); - await result.evaluateExpression(name, this.session, stackFrame, 'repl'); + await result.evaluateExpression(name, session, stackFrame, 'repl'); this.addReplElement(result); } @@ -153,14 +151,14 @@ export class ReplModel { } } - logToRepl(sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) { + logToRepl(session: IDebugSession, sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) { let source: IReplElementSource | undefined; if (frame) { source = { column: frame.column, lineNumber: frame.line, - source: this.session.getSource({ + source: session.getSource({ name: basenameOrAuthority(frame.uri), path: frame.uri.fsPath }) diff --git a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts index a357078862..15dad5a3e2 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts @@ -10,6 +10,7 @@ import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } f import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient { @@ -22,7 +23,7 @@ export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient { openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { // TODO@Isidor use debug IPC channel - return this.windowsService.openExtensionDevelopmentHostWindow(args, env); + return (this.windowsService as WindowsService).openExtensionDevelopmentHostWindow(args, env); } } diff --git a/src/vs/workbench/contrib/debug/node/debugHelperService.ts b/src/vs/workbench/contrib/debug/node/debugHelperService.ts index 81e5e6f93e..d09bcf44df 100644 --- a/src/vs/workbench/contrib/debug/node/debugHelperService.ts +++ b/src/vs/workbench/contrib/debug/node/debugHelperService.ts @@ -14,10 +14,6 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class NodeDebugHelperService implements IDebugHelperService { _serviceBrand: undefined; - constructor( - ) { - } - createTelemetryService(configurationService: IConfigurationService, args: string[]): TelemetryService | undefined { const client = new TelemetryClient( @@ -41,4 +37,4 @@ export class NodeDebugHelperService implements IDebugHelperService { } } -registerSingleton(IDebugHelperService, NodeDebugHelperService); \ No newline at end of file +registerSingleton(IDebugHelperService, NodeDebugHelperService); diff --git a/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts index 09338dc504..5f6b0589da 100644 --- a/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/debugModel.test.ts @@ -12,11 +12,11 @@ import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel } from 'vs/workbench/contrib/debug/common/replModel'; -import { IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug'; +import { IBreakpointUpdateData, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug'; import { NullOpenerService } from 'vs/platform/opener/common/opener'; -function createMockSession(model: DebugModel, name = 'mockSession', parentSession?: DebugSession | undefined): DebugSession { - return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, parentSession, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService); +function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession { + return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService); } suite('Debug - Model', () => { @@ -110,6 +110,44 @@ suite('Debug - Model', () => { assert.equal(model.getFunctionBreakpoints().length, 0); }); + test('breakpoints multiple sessions', () => { + const modelUri = uri.file('/myfolder/myfile.js'); + const breakpoints = model.addBreakpoints(modelUri, [{ lineNumber: 5, enabled: true, condition: 'x > 5' }, { lineNumber: 10, enabled: false }]); + const session = createMockSession(model); + const data = new Map(); + + assert.equal(breakpoints[0].lineNumber, 5); + assert.equal(breakpoints[1].lineNumber, 10); + + data.set(breakpoints[0].getId(), { verified: false, line: 10 }); + data.set(breakpoints[1].getId(), { verified: true, line: 50 }); + model.setBreakpointSessionData(session.getId(), {}, data); + assert.equal(breakpoints[0].lineNumber, 5); + assert.equal(breakpoints[1].lineNumber, 50); + + const session2 = createMockSession(model); + const data2 = new Map(); + data2.set(breakpoints[0].getId(), { verified: true, line: 100 }); + data2.set(breakpoints[1].getId(), { verified: true, line: 500 }); + model.setBreakpointSessionData(session2.getId(), {}, data2); + + // Breakpoint is verified only once, show that line + assert.equal(breakpoints[0].lineNumber, 100); + // Breakpoint is verified two times, show the original line + assert.equal(breakpoints[1].lineNumber, 10); + + model.setBreakpointSessionData(session.getId(), {}, undefined); + // No more double session verification + assert.equal(breakpoints[0].lineNumber, 100); + assert.equal(breakpoints[1].lineNumber, 500); + + assert.equal(breakpoints[0].supported, false); + const data3 = new Map(); + data3.set(breakpoints[0].getId(), { verified: true, line: 500 }); + model.setBreakpointSessionData(session2.getId(), { supportsConditionalBreakpoints: true }, data2); + assert.equal(breakpoints[0].supported, true); + }); + // Threads test('threads simple', () => { @@ -341,10 +379,10 @@ suite('Debug - Model', () => { session['raw'] = rawSession; const thread = new Thread(session, 'mockthread', 1); const stackFrame = new StackFrame(thread, 1, undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1); - const replModel = new ReplModel(session); - replModel.addReplExpression(stackFrame, 'myVariable').then(); - replModel.addReplExpression(stackFrame, 'myVariable').then(); - replModel.addReplExpression(stackFrame, 'myVariable').then(); + const replModel = new ReplModel(); + replModel.addReplExpression(session, stackFrame, 'myVariable').then(); + replModel.addReplExpression(session, stackFrame, 'myVariable').then(); + replModel.addReplExpression(session, stackFrame, 'myVariable').then(); assert.equal(replModel.getReplElements().length, 3); replModel.getReplElements().forEach(re => { @@ -405,13 +443,13 @@ suite('Debug - Model', () => { model.addSession(session); const secondSession = createMockSession(model, 'mockSession2'); model.addSession(secondSession); - const firstChild = createMockSession(model, 'firstChild', session); + const firstChild = createMockSession(model, 'firstChild', { parentSession: session }); model.addSession(firstChild); - const secondChild = createMockSession(model, 'secondChild', session); + const secondChild = createMockSession(model, 'secondChild', { parentSession: session }); model.addSession(secondChild); const thirdSession = createMockSession(model, 'mockSession3'); model.addSession(thirdSession); - const anotherChild = createMockSession(model, 'secondChild', secondSession); + const anotherChild = createMockSession(model, 'secondChild', { parentSession: secondSession }); model.addSession(anotherChild); const sessions = model.getSessions(); @@ -426,8 +464,7 @@ suite('Debug - Model', () => { // Repl output test('repl output', () => { - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService); - const repl = new ReplModel(session); + const repl = new ReplModel(); repl.appendToRepl('first line\n', severity.Error); repl.appendToRepl('second line ', severity.Error); repl.appendToRepl('third line ', severity.Error); @@ -466,4 +503,41 @@ suite('Debug - Model', () => { assert.equal(elements[1], '23\n45\n'); assert.equal(elements[2], '6'); }); + + test('repl merging', () => { + // 'mergeWithParent' should be ignored when there is no parent. + const parent = createMockSession(model, 'parent', { repl: 'mergeWithParent' }); + const child1 = createMockSession(model, 'child1', { parentSession: parent, repl: 'separate' }); + const child2 = createMockSession(model, 'child2', { parentSession: parent, repl: 'mergeWithParent' }); + const grandChild = createMockSession(model, 'grandChild', { parentSession: child2, repl: 'mergeWithParent' }); + const child3 = createMockSession(model, 'child3', { parentSession: parent }); + + parent.appendToRepl('1\n', severity.Info); + assert.equal(parent.getReplElements().length, 1); + assert.equal(child1.getReplElements().length, 0); + assert.equal(child2.getReplElements().length, 1); + assert.equal(grandChild.getReplElements().length, 1); + assert.equal(child3.getReplElements().length, 0); + + grandChild.appendToRepl('1\n', severity.Info); + assert.equal(parent.getReplElements().length, 2); + assert.equal(child1.getReplElements().length, 0); + assert.equal(child2.getReplElements().length, 2); + assert.equal(grandChild.getReplElements().length, 2); + assert.equal(child3.getReplElements().length, 0); + + child3.appendToRepl('1\n', severity.Info); + assert.equal(parent.getReplElements().length, 2); + assert.equal(child1.getReplElements().length, 0); + assert.equal(child2.getReplElements().length, 2); + assert.equal(grandChild.getReplElements().length, 2); + assert.equal(child3.getReplElements().length, 1); + + child1.appendToRepl('1\n', severity.Info); + assert.equal(parent.getReplElements().length, 2); + assert.equal(child1.getReplElements().length, 1); + assert.equal(child2.getReplElements().length, 2); + assert.equal(grandChild.getReplElements().length, 2); + assert.equal(child3.getReplElements().length, 1); + }); }); diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 5f0633812a..35973a4697 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -7,7 +7,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, 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 { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource, IDataBreakpoint, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { CompletionItem } from 'vs/editor/common/modes'; import Severity from 'vs/base/common/severity'; @@ -102,7 +102,7 @@ export class MockDebugService implements IDebugService { public removeWatchExpressions(id?: string): void { } - public startDebugging(launch: ILaunch, configOrName?: IConfig | string, noDebug?: boolean): Promise { + public startDebugging(launch: ILaunch, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise { return Promise.resolve(true); } @@ -159,6 +159,10 @@ export class MockSession implements IDebugSession { return []; } + hasSeparateRepl(): boolean { + return true; + } + removeReplExpressions(): void { } get onDidChangeReplElements(): Event { throw new Error('not implemented'); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index c969eb50b8..969a6a6744 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -5,7 +5,6 @@ import 'vs/css!./media/extensionEditor'; import { localize } from 'vs/nls'; -import * as marked from 'vs/base/common/marked/marked'; import { createCancelablePromise } from 'vs/base/common/async'; import * as arrays from 'vs/base/common/arrays'; import { OS } from 'vs/base/common/platform'; @@ -56,6 +55,11 @@ import { renderDashboardContributions } from 'sql/workbench/parts/extensions/bro import { generateUuid } from 'vs/base/common/uuid'; import { platform } from 'vs/base/common/process'; import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import { renderMarkdownDocument } from 'vs/workbench/common/markdownDocumentRenderer'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { TokenizationRegistry } from 'vs/editor/common/modes'; +import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; function removeEmbeddedSVGs(documentContent: string): string { const newDocument = new DOMParser().parseFromString(documentContent, 'text/html'); @@ -187,7 +191,8 @@ export class ExtensionEditor extends BaseEditor { @IStorageService storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, - @IWebviewService private readonly webviewService: IWebviewService + @IWebviewService private readonly webviewService: IWebviewService, + @IModeService private readonly modeService: IModeService, ) { super(ExtensionEditor.ID, telemetryService, themeService, storageService); this.extensionReadme = null; @@ -580,44 +585,68 @@ export class ExtensionEditor extends BaseEditor { return Promise.resolve(null); } - private openMarkdown(cacheResult: CacheResult, noContentCopy: string, template: IExtensionEditorTemplate): Promise { - return this.loadContents(() => cacheResult, template) - .then(marked.parse) - .then(content => this.renderBody(content)) - .then(removeEmbeddedSVGs) - .then(body => { - const webviewElement = this.webviewService.createWebview('extensionEditor', - { - enableFindWidget: true, - }, - {}); - webviewElement.mountTo(template.content); - this.contentDisposables.add(webviewElement.onDidFocus(() => this.fireOnDidFocus())); - const removeLayoutParticipant = arrays.insert(this.layoutParticipants, webviewElement); - this.contentDisposables.add(toDisposable(removeLayoutParticipant)); - webviewElement.html = body; + private async openMarkdown(cacheResult: CacheResult, noContentCopy: string, template: IExtensionEditorTemplate): Promise { + try { + const body = await this.renderMarkdown(cacheResult, template); - this.contentDisposables.add(webviewElement.onDidClickLink(link => { - if (!link) { - return; - } - // Whitelist supported schemes for links - if (['http', 'https', 'mailto'].indexOf(link.scheme) >= 0 || (link.scheme === 'command' && link.path === ShowCurrentReleaseNotesActionId)) { - this.openerService.open(link); - } - }, null, this.contentDisposables)); - this.contentDisposables.add(webviewElement); - return webviewElement; - }) - .then(undefined, () => { - const p = append(template.content, $('p.nocontent')); - p.textContent = noContentCopy; - return p; + const webviewElement = this.contentDisposables.add(this.webviewService.createWebviewEditorOverlay('extensionEditor', { + enableFindWidget: true, + }, {})); + + webviewElement.claim(this); + webviewElement.layoutWebviewOverElement(template.content); + webviewElement.html = body; + + this.contentDisposables.add(webviewElement.onDidFocus(() => this.fireOnDidFocus())); + const removeLayoutParticipant = arrays.insert(this.layoutParticipants, { + layout: () => { + webviewElement.layout(); + webviewElement.layoutWebviewOverElement(template.content); + } }); + this.contentDisposables.add(toDisposable(removeLayoutParticipant)); + + let isDisposed = false; + this.contentDisposables.add(toDisposable(() => { isDisposed = true; })); + + this.contentDisposables.add(this.themeService.onThemeChange(async () => { + // Render again since syntax highlighting of code blocks may have changed + const body = await this.renderMarkdown(cacheResult, template); + if (!isDisposed) { // Make sure we weren't disposed of in the meantime + webviewElement.html = body; + } + })); + + this.contentDisposables.add(webviewElement.onDidClickLink(link => { + if (!link) { + return; + } + + // Whitelist supported schemes for links + if ([Schemas.http, Schemas.https, Schemas.mailto].indexOf(link.scheme) >= 0 || (link.scheme === 'command' && link.path === ShowCurrentReleaseNotesActionId)) { + this.openerService.open(link); + } + }, null, this.contentDisposables)); + + return webviewElement; + } catch (e) { + const p = append(template.content, $('p.nocontent')); + p.textContent = noContentCopy; + return p; + } + } + + private async renderMarkdown(cacheResult: CacheResult, template: IExtensionEditorTemplate) { + const contents = await this.loadContents(() => cacheResult, template); + const content = await renderMarkdownDocument(contents, this.extensionService, this.modeService); + const documentContent = await this.renderBody(content); + return removeEmbeddedSVGs(documentContent); } private async renderBody(body: string): Promise { const nonce = generateUuid(); + const colorMap = TokenizationRegistry.getColorMap(); + const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; return ` @@ -695,14 +724,9 @@ export class ExtensionEditor extends BaseEditor { } code { - font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; - font-size: 14px; - line-height: 19px; - } - - .mac code { - font-size: 12px; - line-height: 18px; + font-family: var(--vscode-editor-font-family); + font-weight: var(--vscode-editor-font-weight); + font-size: var(--vscode-editor-font-size); } code > div { @@ -711,6 +735,10 @@ export class ExtensionEditor extends BaseEditor { overflow: auto; } + .monaco-tokenized-source { + white-space: pre; + } + #scroll-to-top { position: fixed; width: 40px; @@ -795,6 +823,7 @@ export class ExtensionEditor extends BaseEditor { border-color: rgba(255, 255, 255, 0.18); } + ${css} diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 4d02373245..3b42a0fef6 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 } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; 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'; @@ -1178,7 +1178,7 @@ export class ReloadAction extends ExtensionAction { constructor( @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @IExtensionService private readonly extensionService: IExtensionService, @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService @@ -1230,7 +1230,7 @@ export class ReloadAction extends ExtensionAction { if (this.extension.local) { const isEnabled = this.extensionEnablementService.isEnabled(this.extension.local); - // Extension is runningÎ + // Extension is running if (runningExtension) { if (isEnabled) { if (!this.extensionService.canAddExtension(toExtensionDescription(this.extension.local))) { @@ -1241,11 +1241,6 @@ export class ReloadAction extends ExtensionAction { // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio this.tooltip = localize('postUpdateTooltip', "Please reload Azure Data Studio to enable the updated extension."); } - } else { - this.enabled = true; - this.label = localize('reloadRequired', "Reload Required"); - // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio - this.tooltip = localize('postEnableTooltip', "Please reload Azure Data Studio to enable this extension."); } } } else { @@ -1285,7 +1280,7 @@ export class ReloadAction extends ExtensionAction { } run(): Promise { - return Promise.resolve(this.windowService.reloadWindow()); + return Promise.resolve(this.hostService.reload()); } } @@ -1634,6 +1629,29 @@ export class ShowRecommendedExtensionsAction extends Action { } } +export class ShowSyncedExtensionsAction extends Action { + + static readonly ID = 'workbench.extensions.action.listSyncedExtensions'; + static LABEL = localize('showSyncedExtensions', "Show My Accoount Extensions"); + + constructor( + id: string, + label: string, + @IViewletService private readonly viewletService: IViewletService + ) { + super(id, label, undefined, true); + } + + run(): Promise { + return this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => { + viewlet.search('@myaccount '); + viewlet.focus(); + }); + } +} + export class InstallWorkspaceRecommendedExtensionsAction extends Action { static readonly ID = 'workbench.extensions.action.installWorkspaceRecommendedExtensions'; @@ -2622,11 +2640,10 @@ export class SystemDisabledWarningAction extends ExtensionAction { constructor( @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IProductService private readonly productService: IProductService, @ILabelService private readonly labelService: ILabelService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionService private readonly extensionService: IExtensionService, + @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, ) { super('extensions.install', '', `${SystemDisabledWarningAction.CLASS} hide`, false); this._register(this.labelService.onDidChangeFormatters(() => this.update(), this)); @@ -2662,32 +2679,24 @@ export class SystemDisabledWarningAction extends ExtensionAction { return; } const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))[0]; - const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation) : null; - const localExtension = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier))[0]; - const localExtensionServer = localExtension ? localExtension.server : null; - if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && !isUIExtension(this.extension.local.manifest, this.productService, this.configurationService)) { - if (runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { + if (!runningExtension && this.extension.enablementState === EnablementState.DisabledByExtensionKind) { + this.class = `${SystemDisabledWarningAction.WARNING_CLASS}`; + const server = this.extensionManagementServerService.localExtensionManagementServer === this.extension.server ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer; + this.tooltip = localize('Install in other server to enable', "Install the extension on '{0}' to enable.", server.label); + return; + } + if (this.extensionEnablementService.isEnabled(this.extension.local)) { + const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation) : null; + if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; this.tooltip = localize('disabled locally', "Extension is enabled on '{0}' and disabled locally.", this.extensionManagementServerService.remoteExtensionManagementServer.label); return; } - if (localExtensionServer !== this.extensionManagementServerService.remoteExtensionManagementServer) { - this.class = `${SystemDisabledWarningAction.WARNING_CLASS}`; - this.tooltip = localize('Install in remote server', "Install the extension on '{0}' to enable.", this.extensionManagementServerService.remoteExtensionManagementServer.label); - return; - } - } - if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && isUIExtension(this.extension.local.manifest, this.productService, this.configurationService)) { - if (runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { + if (this.extension.server === this.extensionManagementServerService.remoteExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.localExtensionManagementServer) { this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; this.tooltip = localize('disabled remotely', "Extension is enabled locally and disabled on '{0}'.", this.extensionManagementServerService.remoteExtensionManagementServer.label); return; } - if (localExtensionServer !== this.extensionManagementServerService.localExtensionManagementServer) { - this.class = `${SystemDisabledWarningAction.WARNING_CLASS}`; - this.tooltip = localize('Install in local server', "Install the extension locally to enable."); - return; - } } } @@ -2810,10 +2819,9 @@ export class InstallVSIXAction extends Action { label = InstallVSIXAction.LABEL, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @INotificationService private readonly notificationService: INotificationService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IExtensionService private readonly extensionService: IExtensionService, - @IInstantiationService private readonly instantiationService: IInstantiationService, // {{SQL CARBON EDIT}} @IConfigurationService private configurationService: IConfigurationService, @IStorageService private storageService: IStorageService @@ -2825,17 +2833,17 @@ export class InstallVSIXAction extends Action { // {{SQL CARBON EDIT}} - Replace run body let extensionPolicy = this.configurationService.getValue(ExtensionsPolicyKey); if (extensionPolicy === ExtensionsPolicy.allowAll) { - return Promise.resolve(this.windowService.showOpenDialog({ + return Promise.resolve(this.fileDialogService.showOpenDialog({ title: localize('installFromVSIX', "Install from VSIX"), filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }], - properties: ['openFile'], - buttonLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install")) + canSelectFiles: true, + openLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install")) }).then(result => { if (!result) { return Promise.resolve(); } return Promise.all(result.map(vsix => { - if (!this.storageService.getBoolean(vsix, StorageScope.GLOBAL)) { + if (!this.storageService.getBoolean(vsix.fsPath, StorageScope.GLOBAL)) { this.notificationService.prompt( Severity.Warning, localize('thirdPartyExtension.vsix', 'This is a third party extension and might involve security risks. Are you sure you want to install this extension?'), @@ -2843,13 +2851,13 @@ export class InstallVSIXAction extends Action { { label: localize('thirdPartExt.yes', 'Yes'), run: () => { - this.extensionsWorkbenchService.install(URI.file(vsix)).then(extension => { + this.extensionsWorkbenchService.install(vsix).then(extension => { const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local))); const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Azure Data Studio to complete installing the extension {0}.", extension.identifier.id) : localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.identifier.id); const actions = requireReload ? [{ label: localize('InstallVSIXAction.reloadNow', "Reload Now"), - run: () => this.windowService.reloadWindow() + run: () => this.hostService.reload() }] : []; this.notificationService.prompt( Severity.Info, @@ -2868,7 +2876,7 @@ export class InstallVSIXAction extends Action { label: localize('thirdPartyExt.dontShowAgain', 'Don\'t Show Again'), isSecondary: true, run: () => { - this.storageService.store(vsix, true, StorageScope.GLOBAL); + this.storageService.store(vsix.fsPath, true, StorageScope.GLOBAL); return Promise.resolve(); } } @@ -2876,13 +2884,13 @@ export class InstallVSIXAction extends Action { { sticky: true } ); } else { - this.extensionsWorkbenchService.install(URI.file(vsix)).then(extension => { + this.extensionsWorkbenchService.install(vsix).then(extension => { const requireReload = !(extension.local && this.extensionService.canAddExtension(toExtensionDescription(extension.local))); const message = requireReload ? localize('InstallVSIXAction.successReload', "Please reload Azure Data Studio to complete installing the extension {0}.", extension.identifier.id) : localize('InstallVSIXAction.success', "Completed installing the extension {0}.", extension.identifier.id); const actions = requireReload ? [{ label: localize('InstallVSIXAction.reloadNow', "Reload Now"), - run: () => this.windowService.reloadWindow() + run: () => this.hostService.reload() }] : []; this.notificationService.prompt( Severity.Info, @@ -2912,7 +2920,7 @@ export class ReinstallAction extends Action { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IQuickInputService private readonly quickInputService: IQuickInputService, @INotificationService private readonly notificationService: INotificationService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IExtensionService private readonly extensionService: IExtensionService ) { @@ -2956,7 +2964,7 @@ export class ReinstallAction extends Action { : localize('ReinstallAction.success', "Reinstalling the extension {0} is completed.", extension.identifier.id); const actions = requireReload ? [{ label: localize('InstallVSIXAction.reloadNow', "Reload Now"), - run: () => this.windowService.reloadWindow() + run: () => this.hostService.reload() }] : []; this.notificationService.prompt( Severity.Info, @@ -2980,7 +2988,7 @@ export class InstallSpecificVersionOfExtensionAction extends Action { @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IQuickInputService private readonly quickInputService: IQuickInputService, @INotificationService private readonly notificationService: INotificationService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IExtensionService private readonly extensionService: IExtensionService, @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, @@ -3042,7 +3050,7 @@ export class InstallSpecificVersionOfExtensionAction extends Action { : localize('InstallAnotherVersionExtensionAction.success', "Installing the extension {0} is completed.", extension.identifier.id); const actions = requireReload ? [{ label: localize('InstallAnotherVersionExtensionAction.reloadNow', "Reload Now"), - run: () => this.windowService.reloadWindow() + run: () => this.hostService.reload() }] : []; this.notificationService.prompt( Severity.Info, @@ -3069,7 +3077,7 @@ export class InstallLocalExtensionsInRemoteAction extends Action { @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IQuickInputService private readonly quickInputService: IQuickInputService, @INotificationService private readonly notificationService: INotificationService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @IProgressService private readonly progressService: IProgressService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { @@ -3182,7 +3190,7 @@ export class InstallLocalExtensionsInRemoteAction extends Action { message: localize('finished installing', "Successfully installed extensions in {0}. Please reload the window to enable them.", this.extensionManagementServerService.remoteExtensionManagementServer!.label), actions: { primary: [new Action('realod', localize('reload', "Reload Window"), '', true, - () => this.windowService.reloadWindow())] + () => this.hostService.reload())] } }); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts b/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts index 87afbb5054..102eb8ee39 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsDependencyChecker.ts @@ -13,7 +13,7 @@ import { values } from 'vs/base/common/map'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { Action } from 'vs/base/common/actions'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { Disposable } from 'vs/base/common/lifecycle'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -23,7 +23,7 @@ export class ExtensionDependencyChecker extends Disposable implements IWorkbench @IExtensionService private readonly extensionService: IExtensionService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @INotificationService private readonly notificationService: INotificationService, - @IWindowService private readonly windowService: IWindowService + @IHostService private readonly hostService: IHostService ) { super(); CommandsRegistry.registerCommand('workbench.extensions.installMissingDepenencies', () => this.installMissingDependencies()); @@ -69,7 +69,7 @@ export class ExtensionDependencyChecker extends Disposable implements IWorkbench message: localize('finished installing missing deps', "Finished installing missing dependencies. Please reload the window now."), actions: { primary: [new Action('realod', localize('reload', "Reload Window"), '', true, - () => this.windowService.reloadWindow())] + () => this.hostService.reload())] } }); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index a1836b86ea..d1b60c67dd 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -22,12 +22,12 @@ import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, AutoUpdate import { ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction, ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction, - EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction + EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction, ShowSyncedExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; -import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; +import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, ServerExtensionsView, DefaultRecommendedExtensionsView, SyncedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -43,7 +43,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; @@ -58,6 +58,7 @@ import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsView import { RemoteNameContext } from 'vs/workbench/browser/contextkeys'; import { ILabelService } from 'vs/platform/label/common/label'; import { MementoObject } from 'vs/workbench/common/memento'; +import { SyncStatus, CONTEXT_SYNC_STATE } from 'vs/platform/userDataSync/common/userDataSync'; const NonEmptyWorkspaceContext = new RawContextKey('nonEmptyWorkspace', false); const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true); @@ -70,6 +71,7 @@ const HasInstalledExtensionsContext = new RawContextKey('hasInstalledEx const SearchBuiltInExtensionsContext = new RawContextKey('searchBuiltInExtensions', false); const RecommendedExtensionsContext = new RawContextKey('recommendedExtensions', false); const DefaultRecommendedExtensionsContext = new RawContextKey('defaultRecommendedExtensions', false); +const SyncedExtensionsContext = new RawContextKey('syncedExtensions', false); const viewIdNameMappings: { [id: string]: string } = { 'extensions.listView': localize('marketPlace', "Marketplace"), 'extensions.enabledExtensionList': localize('enabledExtensions', "Enabled"), @@ -84,6 +86,7 @@ const viewIdNameMappings: { [id: string]: string } = { 'extensions.builtInExtensionsList': localize('builtInExtensions', "Features"), 'extensions.builtInThemesExtensionsList': localize('builtInThemesExtensions', "Themes"), 'extensions.builtInBasicsExtensionsList': localize('builtInBasicsExtensions', "Programming Languages"), + 'extensions.syncedExtensionsList': localize('syncedExtensions', "My Account"), }; export class ExtensionsViewletViewsContribution implements IWorkbenchContribution { @@ -110,6 +113,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push(this.createDefaultRecommendedExtensionsListViewDescriptor()); viewDescriptors.push(this.createOtherRecommendedExtensionsListViewDescriptor()); viewDescriptors.push(this.createWorkspaceRecommendedExtensionsListViewDescriptor()); + viewDescriptors.push(this.createSyncedExtensionsViewDescriptor()); if (this.extensionManagementServerService.localExtensionManagementServer) { viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.localExtensionManagementServer)); @@ -314,6 +318,17 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio weight: 100 }; } + + private createSyncedExtensionsViewDescriptor(): IViewDescriptor { + const id = 'extensions.syncedExtensionsList'; + return { + id, + name: viewIdNameMappings[id], + ctorDescriptor: { ctor: SyncedExtensionsView }, + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has('config.userConfiguration.enableSync'), ContextKeyExpr.has('syncedExtensions')), + weight: 100 + }; + } } export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensionsViewlet { @@ -330,6 +345,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio private hasInstalledExtensionsContextKey: IContextKey; private searchBuiltInExtensionsContextKey: IContextKey; private recommendedExtensionsContextKey: IContextKey; + private syncedExtensionsContextKey: IContextKey; private defaultRecommendedExtensionsContextKey: IContextKey; private searchDelayer: Delayer; @@ -371,6 +387,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService); this.defaultRecommendedExtensionsContextKey = DefaultRecommendedExtensionsContext.bindTo(contextKeyService); this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)); + this.syncedExtensionsContextKey = SyncedExtensionsContext.bindTo(contextKeyService); this._register(this.viewletService.onDidViewletOpen(this.onViewletOpen, this)); this.searchViewletState = this.getMemento(StorageScope.WORKSPACE); @@ -469,8 +486,8 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL), this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL), this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL), - // {{SQL CARBON EDIT}} - // this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL), + // this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL), // {{SQL CARBON EDIT}} + this.instantiationService.createInstance(ShowSyncedExtensionsAction, ShowSyncedExtensionsAction.ID, ShowSyncedExtensionsAction.LABEL), new Separator(), // {{SQL CARBON EDIT}} //this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Sort By: Install Count"), this.onSearchChange, 'installs'), @@ -519,6 +536,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio private doSearch(): Promise { const value = this.normalizedQuery(); + const isSyncedExtensionsQuery = ExtensionsListView.isSyncedExtensionsQuery(value); const isRecommendedExtensionsQuery = ExtensionsListView.isRecommendedExtensionsQuery(value); this.searchInstalledExtensionsContextKey.set(ExtensionsListView.isInstalledExtensionsQuery(value)); this.searchOutdatedExtensionsContextKey.set(ExtensionsListView.isOutdatedExtensionsQuery(value)); @@ -526,7 +544,8 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.searchDisabledExtensionsContextKey.set(ExtensionsListView.isDisabledExtensionsQuery(value)); this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value)); this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery); - this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery); + this.syncedExtensionsContextKey.set(isSyncedExtensionsQuery); + this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery && !isSyncedExtensionsQuery); this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY); this.defaultViewsContextKey.set(!value); @@ -647,7 +666,7 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution { constructor( @IExtensionManagementService private readonly extensionsManagementService: IExtensionManagementService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @ILogService private readonly logService: ILogService, @INotificationService private readonly notificationService: INotificationService, @IEnvironmentService private readonly environmentService: IEnvironmentService @@ -678,7 +697,7 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution { localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", e.identifier.id), [{ label: localize('reloadNow', "Reload Now"), - run: () => this.windowService.reloadWindow() + run: () => this.hostService.reload() }], { sticky: true } ); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 3538d6c0ec..2a6126bc9d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -47,6 +47,7 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async import { IProductService } from 'vs/platform/product/common/productService'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync'; class ExtensionsViewState extends Disposable implements IExtensionsViewState { @@ -104,6 +105,7 @@ export class ExtensionsListView extends ViewletPanel { @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, @IProductService protected readonly productService: IProductService, @IContextKeyService contextKeyService: IContextKeyService, + @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService); this.server = options.server; @@ -456,8 +458,10 @@ export class ExtensionsListView extends ViewletPanel { } else if (ExtensionsListView.isRecommendedExtensionsQuery(query.value)) { return this.getRecommendationsModel(query, options, token); // {{SQL CARBON EDIT}} - } else if (ExtensionsListView.isAllMarketplaceExtensionsQuery(query.value)) { + } else if (ExtensionsListView.isAllMarketplaceExtensionsQuery(query.value)) { // {{SQL CARBON EDIT}} add if return this.getAllMarketplaceModel(query, options, token); + } else if (ExtensionsListView.isSyncedExtensionsQuery(query.value)) { + return this.getSyncedExtensionsModel(query, options, token); } if (/\bcurated:([^\s]+)\b/.test(query.value)) { @@ -765,6 +769,23 @@ export class ExtensionsListView extends ViewletPanel { .then(result => this.getPagedModel(result)); } + private async getSyncedExtensionsModel(query: Query, options: IQueryOptions, token: CancellationToken): Promise> { + const syncedExtensions = await this.userDataSyncService.getRemoteExtensions(); + if (!syncedExtensions.length) { + return this.showEmptyModel(); + } + const ids: string[] = [], names: string[] = []; + for (const installed of syncedExtensions) { + if (installed.identifier.uuid) { + ids.push(installed.identifier.uuid); + } else { + names.push(installed.identifier.id); + } + } + const pager = await this.extensionsWorkbenchService.queryGallery({ ids, names, pageSize: ids.length }, token); + return this.getPagedModel(pager || []); + } + // Sorts the firstPage of the pager in the same order as given array of extension ids private sortFirstPage(pager: IPager, ids: string[]) { ids = ids.map(x => x.toLowerCase()); @@ -904,6 +925,10 @@ export class ExtensionsListView extends ViewletPanel { return /@recommended:keymaps/i.test(query); } + static isSyncedExtensionsQuery(query: string): boolean { + return /@myaccount/i.test(query); + } + focus(): void { super.focus(); if (!this.list) { @@ -944,10 +969,11 @@ export class ServerExtensionsView extends ExtensionsListView { @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, @IProductService productService: IProductService, - @IContextKeyService contextKeyService: IContextKeyService + @IContextKeyService contextKeyService: IContextKeyService, + @IUserDataSyncService userDataSyncService: IUserDataSyncService ) { options.server = server; - super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService); + super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService, userDataSyncService); this._register(onDidChangeTitle(title => this.updateTitle(title))); } @@ -1003,6 +1029,14 @@ export class BuiltInBasicsExtensionsView extends ExtensionsListView { } } +export class SyncedExtensionsView extends ExtensionsListView { + + async show(query: string): Promise> { + query = query || '@myaccount'; + return ExtensionsListView.isSyncedExtensionsQuery(query) ? super.show(query) : this.showEmptyModel(); + } +} + export class DefaultRecommendedExtensionsView extends ExtensionsListView { // {{SQL CARBON EDIT}} private readonly recommendedExtensionsQuery = '@allmarketplace'; diff --git a/src/vs/workbench/contrib/extensions/common/extensionsFileTemplate.ts b/src/vs/workbench/contrib/extensions/common/extensionsFileTemplate.ts index 3b7a598268..231b699af2 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionsFileTemplate.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsFileTemplate.ts @@ -11,7 +11,7 @@ export const ExtensionsConfigurationSchemaId = 'vscode://schemas/extensions'; export const ExtensionsConfigurationSchema: IJSONSchema = { id: ExtensionsConfigurationSchemaId, allowComments: true, - allowsTrailingCommas: true, + allowTrailingCommas: true, type: 'object', title: localize('app.extensions.json.title', "Extensions"), additionalProperties: false, @@ -39,7 +39,7 @@ export const ExtensionsConfigurationSchema: IJSONSchema = { export const ExtensionsConfigurationInitialContent: string = [ '{', - '\t// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.', + '\t// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.', '\t// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp', '', '\t// List of extensions which should be recommended for users of this workspace.', diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index df80b52e6b..efcbc5009e 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -12,7 +12,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; import { IExtensionHostProfileService, ProfileSessionState } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IElectronService } from 'vs/platform/electron/node/electron'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { randomPort } from 'vs/base/node/ports'; import product from 'vs/platform/product/common/product'; @@ -46,7 +46,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio @IExtensionService private readonly _extensionService: IExtensionService, @IEditorService private readonly _editorService: IEditorService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IWindowsService private readonly _windowsService: IWindowsService, + @IElectronService private readonly _electronService: IElectronService, @IDialogService private readonly _dialogService: IDialogService, @IStatusbarService private readonly _statusbarService: IStatusbarService, ) { @@ -122,7 +122,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio secondaryButton: nls.localize('cancel', "Cancel") }).then(res => { if (res.confirmed) { - this._windowsService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] }); + this._electronService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] }); } }); } diff --git a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 449fee92d9..e5d5aa5cfd 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -24,7 +24,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { clipboard } from 'electron'; import { EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IElectronService } from 'vs/platform/electron/node/electron'; import { writeFile } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { memoize } from 'vs/base/common/decorators'; @@ -307,7 +307,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { const activationTimes = element.status.activationTimes!; let syncTime = activationTimes.codeLoadingTime + activationTimes.activateCallTime; - data.activationTime.textContent = activationTimes.startup ? `Startup Activation: ${syncTime}ms` : `Activation: ${syncTime}ms`; + data.activationTime.textContent = activationTimes.activationReason.startup ? `Startup Activation: ${syncTime}ms` : `Activation: ${syncTime}ms`; data.actionbar.clear(); if (element.unresponsiveProfile) { @@ -318,43 +318,45 @@ export class RuntimeExtensionsEditor extends BaseEditor { } let title: string; - if (activationTimes.activationEvent === '*') { - title = nls.localize('starActivation', "Activated on start-up"); - } else if (/^workspaceContains:/.test(activationTimes.activationEvent)) { - let fileNameOrGlob = activationTimes.activationEvent.substr('workspaceContains:'.length); + const activationId = activationTimes.activationReason.extensionId.value; + const activationEvent = activationTimes.activationReason.activationEvent; + if (activationEvent === '*') { + title = nls.localize('starActivation', "Activated by {0} on start-up", activationId); + } else if (/^workspaceContains:/.test(activationEvent)) { + let fileNameOrGlob = activationEvent.substr('workspaceContains:'.length); if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) { title = nls.localize({ key: 'workspaceContainsGlobActivation', comment: [ '{0} will be a glob pattern' ] - }, "Activated because a file matching {0} exists in your workspace", fileNameOrGlob); + }, "Activated by {1} because a file matching {1} exists in your workspace", fileNameOrGlob, activationId); } else { title = nls.localize({ key: 'workspaceContainsFileActivation', comment: [ '{0} will be a file name' ] - }, "Activated because file {0} exists in your workspace", fileNameOrGlob); + }, "Activated by {1} because file {0} exists in your workspace", fileNameOrGlob, activationId); } - } else if (/^workspaceContainsTimeout:/.test(activationTimes.activationEvent)) { - const glob = activationTimes.activationEvent.substr('workspaceContainsTimeout:'.length); + } else if (/^workspaceContainsTimeout:/.test(activationEvent)) { + const glob = activationEvent.substr('workspaceContainsTimeout:'.length); title = nls.localize({ key: 'workspaceContainsTimeout', comment: [ '{0} will be a glob pattern' ] - }, "Activated because searching for {0} took too long", glob); - } else if (/^onLanguage:/.test(activationTimes.activationEvent)) { - let language = activationTimes.activationEvent.substr('onLanguage:'.length); - title = nls.localize('languageActivation', "Activated because you opened a {0} file", language); + }, "Activated by {1} because searching for {0} took too long", glob, activationId); + } else if (/^onLanguage:/.test(activationEvent)) { + let language = activationEvent.substr('onLanguage:'.length); + title = nls.localize('languageActivation', "Activated by {1} because you opened a {0} file", language, activationId); } else { title = nls.localize({ key: 'workspaceGenericActivation', comment: [ 'The {0} placeholder will be an activation event, like e.g. \'language:typescript\', \'debug\', etc.' ] - }, "Activated on {0}", activationTimes.activationEvent); + }, "Activated by {1} on {0}", activationEvent, activationId); } data.activationTime.title = title; @@ -536,7 +538,7 @@ export class DebugExtensionHostAction extends Action { constructor( @IDebugService private readonly _debugService: IDebugService, - @IWindowsService private readonly _windowsService: IWindowsService, + @IElectronService private readonly _electronService: IElectronService, @IDialogService private readonly _dialogService: IDialogService, @IExtensionService private readonly _extensionService: IExtensionService, ) { @@ -555,7 +557,7 @@ export class DebugExtensionHostAction extends Action { secondaryButton: nls.localize('cancel', "Cancel") }); if (res.confirmed) { - this._windowsService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] }); + this._electronService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] }); } } @@ -609,7 +611,7 @@ export class SaveExtensionHostProfileAction extends Action { constructor( id: string = SaveExtensionHostProfileAction.ID, label: string = SaveExtensionHostProfileAction.LABEL, - @IWindowService private readonly _windowService: IWindowService, + @IElectronService private readonly _electronService: IElectronService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService, ) { @@ -624,7 +626,7 @@ export class SaveExtensionHostProfileAction extends Action { } private async _asyncRun(): Promise { - let picked = await this._windowService.showSaveDialog({ + let picked = await this._electronService.showSaveDialog({ title: 'Save Extension Host Profile', buttonLabel: 'Save', defaultPath: `CPU-${new Date().toISOString().replace(/[\-:]/g, '')}.cpuprofile`, @@ -634,13 +636,15 @@ export class SaveExtensionHostProfileAction extends Action { }] }); - if (!picked) { + if (!picked || !picked.filePath || picked.canceled) { return; } const profileInfo = this._extensionHostProfileService.lastProfile; let dataToWrite: object = profileInfo ? profileInfo.data : {}; + let savePath = picked.filePath; + if (this._environmentService.isBuilt) { const profiler = await import('v8-inspect-profiler'); // when running from a not-development-build we remove @@ -651,9 +655,9 @@ export class SaveExtensionHostProfileAction extends Action { let tmp = profiler.rewriteAbsolutePaths({ profile: dataToWrite as any }, 'piiRemoved'); dataToWrite = tmp.profile; - picked = picked + '.txt'; + savePath = savePath + '.txt'; } - return writeFile(picked, JSON.stringify(profileInfo ? profileInfo.data : {}, null, '\t')); + return writeFile(savePath, JSON.stringify(profileInfo ? profileInfo.data : {}, null, '\t')); } } 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 84716874c6..6394df76f1 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 @@ -1465,7 +1465,7 @@ suite('ExtensionsActions Test', () => { assert.equal(testObject.tooltip, 'Please reload Azure Data Studio to enable this extension.'); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio }); - test('Test ReloadAction when ui extension is disabled on remote server and installed in local server', async () => { + test('Test ReloadAction is disabled when remote ui extension is installed in local server', async () => { // multi server setup const gallery = aGalleryExtension('a'); const localExtensionManagementService = createExtensionManagementService([]); @@ -1480,7 +1480,7 @@ suite('ExtensionsActions Test', () => { const onDidChangeExtensionsEmitter: Emitter = new Emitter(); instantiationService.stub(IExtensionService, >{ - getExtensions: () => Promise.resolve([]), + getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(remoteExtension)]), onDidChangeExtensions: onDidChangeExtensionsEmitter.event, canAddExtension: (extension) => false }); @@ -1497,8 +1497,69 @@ suite('ExtensionsActions Test', () => { const localExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a') }); onDidInstallEvent.fire({ identifier: localExtension.identifier, local: localExtension, operation: InstallOperation.Install }); - assert.ok(testObject.enabled); - assert.equal(testObject.tooltip, 'Please reload Azure Data Studio to enable this extension.'); // {{SQL CARBON EDIT}} - replace Visual Studio Code with Azure Data Studio + assert.ok(!testObject.enabled); + }); + + test('Test ReloadAction for remote ui extension is disabled when it is installed and enabled in local server', async () => { + // multi server setup + const gallery = aGalleryExtension('a'); + const localExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a') }); + const localExtensionManagementService = createExtensionManagementService([localExtension]); + const onDidInstallEvent = new Emitter(); + localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; + const remoteExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + const onDidChangeExtensionsEmitter: Emitter = new Emitter(); + instantiationService.stub(IExtensionService, >{ + getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(localExtension)]), + onDidChangeExtensions: onDidChangeExtensionsEmitter.event, + canAddExtension: (extension) => false + }); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + await workbenchService.queryGallery(CancellationToken.None); + const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); + }); + + test('Test ReloadAction for local ui extension is disabled when it is installed and enabled in remote server', async () => { + // multi server setup + const gallery = aGalleryExtension('a'); + const localExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a') }); + const localExtensionManagementService = createExtensionManagementService([localExtension]); + const onDidInstallEvent = new Emitter(); + localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; + const remoteExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file('pub.a').with({ scheme: Schemas.vscodeRemote }) }); + const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteExtension])); + instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.set(IExtensionsWorkbenchService, workbenchService); + + const onDidChangeExtensionsEmitter: Emitter = new Emitter(); + instantiationService.stub(IExtensionService, >{ + getExtensions: () => Promise.resolve([ExtensionsActions.toExtensionDescription(remoteExtension)]), + onDidChangeExtensions: onDidChangeExtensionsEmitter.event, + canAddExtension: (extension) => false + }); + const testObject: ExtensionsActions.ReloadAction = instantiationService.createInstance(ExtensionsActions.ReloadAction); + instantiationService.createInstance(ExtensionContainers, [testObject]); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); + + await workbenchService.queryGallery(CancellationToken.None); + const extensions = await workbenchService.queryLocal(extensionManagementServerService.localExtensionManagementServer!); + testObject.extension = extensions[0]; + assert.ok(testObject.extension); + assert.ok(!testObject.enabled); }); test('Test remote install action is enabled for local workspace extension', async () => { @@ -1854,7 +1915,7 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); }); - test('Test local install action is enabled for remote ui extension', async () => { + test('Test local install action is disabled for remote ui extension', async () => { // multi server setup const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); @@ -1870,9 +1931,7 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; - assert.ok(testObject.enabled); - assert.equal('Install Locally', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.ok(!testObject.enabled); }); test('Test local install action when installing remote ui extension', async () => { @@ -1896,54 +1955,10 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; - assert.ok(testObject.enabled); - assert.equal('Install Locally', testObject.label); - assert.equal('extension-action prominent install', testObject.class); - - onInstallExtension.fire({ identifier: remoteUIExtension.identifier, gallery }); - assert.ok(testObject.enabled); - assert.equal('Installing', testObject.label); - assert.equal('extension-action install installing', testObject.class); - }); - - test('Test local install action when installing remote ui extension is finished', async () => { - // multi server setup - const localExtensionManagementService: IExtensionManagementService = createExtensionManagementService(); - const onInstallExtension = new Emitter(); - localExtensionManagementService.onInstallExtension = onInstallExtension.event; - const onDidInstallEvent = new Emitter(); - localExtensionManagementService.onDidInstallExtension = onDidInstallEvent.event; - const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); - const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, localExtensionManagementService, createExtensionManagementService([remoteUIExtension])); - instantiationService.stub(IExtensionManagementServerService, extensionManagementServerService); - instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); - const workbenchService: IExtensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); - instantiationService.stub(IExtensionsWorkbenchService, workbenchService, 'open', undefined); - instantiationService.set(IExtensionsWorkbenchService, workbenchService); - - const gallery = aGalleryExtension('a', { identifier: remoteUIExtension.identifier }); - instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(gallery)); - const testObject: ExtensionsActions.InstallAction = instantiationService.createInstance(ExtensionsActions.LocalInstallAction); - instantiationService.createInstance(ExtensionContainers, [testObject]); - - const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); - await workbenchService.queryGallery(CancellationToken.None); - testObject.extension = extensions[0]; - assert.ok(testObject.enabled); - assert.equal('Install Locally', testObject.label); - assert.equal('extension-action prominent install', testObject.class); - - onInstallExtension.fire({ identifier: remoteUIExtension.identifier, gallery }); - assert.ok(testObject.enabled); - assert.equal('Installing', testObject.label); - assert.equal('extension-action install installing', testObject.class); - - const installedExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`) }); - onDidInstallEvent.fire({ identifier: installedExtension.identifier, local: installedExtension, operation: InstallOperation.Install }); assert.ok(!testObject.enabled); }); - test('Test local install action is enabled for disabled remote ui extension', async () => { + test('Test local install action is disabled for disabled remote ui extension', async () => { // multi server setup const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); @@ -1960,9 +1975,7 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; - assert.ok(testObject.enabled); - assert.equal('Install Locally', testObject.label); - assert.equal('extension-action prominent install', testObject.class); + assert.ok(!testObject.enabled); }); test('Test local install action is disabled when extension is not set', async () => { @@ -2062,7 +2075,7 @@ suite('ExtensionsActions Test', () => { assert.ok(!testObject.enabled); }); - test('Test local install action is disabled for remoteUI extension if it is uninstalled locally', async () => { + test('Test local install action is disabled for remote UI extension if it uninstalled locally', async () => { // multi server setup const extensionManagementService = instantiationService.get(IExtensionManagementService); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), extensionManagementService); @@ -2080,14 +2093,13 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); await workbenchService.queryGallery(CancellationToken.None); testObject.extension = extensions[0]; - assert.ok(testObject.enabled); - assert.equal('Install Locally', testObject.label); + assert.ok(!testObject.enabled); uninstallEvent.fire(remoteUIExtension.identifier); assert.ok(!testObject.enabled); }); - test('Test local install action is enabled for remote UI extension if it has gallery', async () => { + test('Test local install action is disabled for remote UI extension if it has gallery', async () => { // multi server setup const remoteUIExtension = aLocalExtension('a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); const extensionManagementServerService = aMultiExtensionManagementServerService(instantiationService, createExtensionManagementService(), createExtensionManagementService([remoteUIExtension])); @@ -2103,7 +2115,7 @@ suite('ExtensionsActions Test', () => { const extensions = await workbenchService.queryLocal(extensionManagementServerService.remoteExtensionManagementServer!); testObject.extension = extensions[0]; assert.ok(testObject.extension); - assert.ok(testObject.enabled); + assert.ok(!testObject.enabled); }); test('Test local install action is disabled for remote UI system extension', async () => { diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index d0fee65886..f1481570b5 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -16,16 +16,15 @@ import { EditorOptions, TextEditorOptions, IEditorCloseEvent } from 'vs/workbenc import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { FileOperationError, FileOperationResult, FileChangesEvent, IFileService, FALLBACK_MAX_MEMORY_SIZE_MB, MIN_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/common/files'; +import { FileOperationError, FileOperationResult, FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -56,8 +55,6 @@ export class TextFileEditor extends BaseTextEditor { @IThemeService themeService: IThemeService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @ITextFileService textFileService: ITextFileService, - @IWindowsService private readonly windowsService: IWindowsService, - @IPreferencesService private readonly preferencesService: IPreferencesService, @IWindowService windowService: IWindowService, @IExplorerService private readonly explorerService: IExplorerService ) { @@ -167,63 +164,48 @@ export class TextFileEditor extends BaseTextEditor { // Readonly flag textEditor.updateOptions({ readOnly: textFileModel.isReadonly() }); } catch (error) { - - // In case we tried to open a file inside the text editor and the response - // indicates that this is not a text file, reopen the file through the binary - // editor. - if ((error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY) { - return this.openAsBinary(input, options); - } - - // Similar, handle case where we were asked to open a folder in the text editor. - if ((error).fileOperationResult === FileOperationResult.FILE_IS_DIRECTORY) { - this.openAsFolder(input); - - throw new Error(nls.localize('openFolderError', "File is a directory")); - } - - // Offer to create a file from the error if we have a file not found and the name is valid - if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && isValidBasename(basename(input.getResource()))) { - throw createErrorWithActions(toErrorMessage(error), { - actions: [ - new Action('workbench.files.action.createMissingFile', nls.localize('createFile', "Create File"), undefined, true, async () => { - await this.textFileService.create(input.getResource()); - - return this.editorService.openEditor({ - resource: input.getResource(), - options: { - pinned: true // new file gets pinned by default - } - }); - }) - ] - }); - } - - if ((error).fileOperationResult === FileOperationResult.FILE_EXCEED_MEMORY_LIMIT) { - const memoryLimit = Math.max(MIN_MAX_MEMORY_SIZE_MB, +this.configurationService.getValue(undefined, 'files.maxMemoryForLargeFilesMB') || FALLBACK_MAX_MEMORY_SIZE_MB); - - throw createErrorWithActions(toErrorMessage(error), { - actions: [ - new Action('workbench.window.action.relaunchWithIncreasedMemoryLimit', nls.localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), undefined, true, () => { - return this.windowsService.relaunch({ - addArgs: [ - `--max-memory=${memoryLimit}` - ] - }); - }), - new Action('workbench.window.action.configureMemoryLimit', nls.localize('configureMemoryLimit', 'Configure Memory Limit'), undefined, true, () => { - return this.preferencesService.openGlobalSettings(undefined, { query: 'files.maxMemoryForLargeFilesMB' }); - }) - ] - }); - } - - // Otherwise make sure the error bubbles up - throw error; + this.handleSetInputError(error, input, options); } } + protected handleSetInputError(error: Error, input: FileEditorInput, options: EditorOptions | undefined): void { + + // In case we tried to open a file inside the text editor and the response + // indicates that this is not a text file, reopen the file through the binary + // editor. + if ((error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY) { + return this.openAsBinary(input, options); + } + + // Similar, handle case where we were asked to open a folder in the text editor. + if ((error).fileOperationResult === FileOperationResult.FILE_IS_DIRECTORY) { + this.openAsFolder(input); + + throw new Error(nls.localize('openFolderError', "File is a directory")); + } + + // Offer to create a file from the error if we have a file not found and the name is valid + if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && isValidBasename(basename(input.getResource()))) { + throw createErrorWithActions(toErrorMessage(error), { + actions: [ + new Action('workbench.files.action.createMissingFile', nls.localize('createFile', "Create File"), undefined, true, async () => { + await this.textFileService.create(input.getResource()); + + return this.editorService.openEditor({ + resource: input.getResource(), + options: { + pinned: true // new file gets pinned by default + } + }); + }) + ] + }); + } + + // Otherwise make sure the error bubbles up + throw error; + } + private openAsBinary(input: FileEditorInput, options: EditorOptions | undefined): void { input.setForceOpenAsBinary(); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index c28b806038..51ac4348a0 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -92,7 +92,7 @@ export class NewFileAction extends Action { @ICommandService private commandService: ICommandService ) { super('explorer.newFile', NEW_FILE_LABEL); - this.class = 'explorer-action new-file'; + this.class = 'explorer-action codicon-new-file'; this._register(explorerService.onDidChangeEditable(e => { const elementIsBeingEdited = explorerService.isEditable(e); this.enabled = !elementIsBeingEdited; @@ -114,7 +114,7 @@ export class NewFolderAction extends Action { @ICommandService private commandService: ICommandService ) { super('explorer.newFolder', NEW_FOLDER_LABEL); - this.class = 'explorer-action new-folder'; + this.class = 'explorer-action codicon-new-folder'; this._register(explorerService.onDidChangeEditable(e => { const elementIsBeingEdited = explorerService.isEditable(e); this.enabled = !elementIsBeingEdited; @@ -603,7 +603,7 @@ export class SaveAllAction extends BaseSaveAllAction { public static readonly LABEL = SAVE_ALL_LABEL; public get class(): string { - return 'explorer-action save-all'; + return 'explorer-action codicon-save-all'; } protected doRun(context: any): Promise { @@ -621,7 +621,7 @@ export class SaveAllInGroupAction extends BaseSaveAllAction { public static readonly LABEL = nls.localize('saveAllInGroup', "Save All in Group"); public get class(): string { - return 'explorer-action save-all'; + return 'explorer-action codicon-save-all'; } protected doRun(context: any): Promise { @@ -639,7 +639,7 @@ export class CloseGroupAction extends Action { public static readonly LABEL = nls.localize('closeGroup', "Close Group"); constructor(id: string, label: string, @ICommandService private readonly commandService: ICommandService) { - super(id, label, 'action-close-all-files'); + super(id, label, 'codicon-close-all'); } public run(context?: any): Promise { @@ -702,7 +702,7 @@ export class CollapseExplorerView extends Action { @IViewletService private readonly viewletService: IViewletService, @IExplorerService readonly explorerService: IExplorerService ) { - super(id, label, 'explorer-action collapse-explorer'); + super(id, label, 'explorer-action codicon-collapse-all'); this._register(explorerService.onDidChangeEditable(e => { const elementIsBeingEdited = explorerService.isEditable(e); this.enabled = !elementIsBeingEdited; @@ -730,7 +730,7 @@ export class RefreshExplorerView extends Action { @IViewletService private readonly viewletService: IViewletService, @IExplorerService private readonly explorerService: IExplorerService ) { - super(id, label, 'explorer-action refresh-explorer'); + super(id, label, 'explorer-action codicon-refresh'); this._register(explorerService.onDidChangeEditable(e => { const elementIsBeingEdited = explorerService.isEditable(e); this.enabled = !elementIsBeingEdited; diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 9ae1d100c1..91d05ef425 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -5,9 +5,9 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -// {{SQL CARBON EDIT}} import EditorInput -import { toResource, IEditorCommandsContext, SideBySideEditor, EditorInput } from 'vs/workbench/common/editor'; -import { IWindowsService, IWindowService, IURIToOpen, IOpenSettings, INewWindowOptions, isWorkspaceToOpen } from 'vs/platform/windows/common/windows'; +import { toResource, IEditorCommandsContext, SideBySideEditor, EditorInput } from 'vs/workbench/common/editor'; // {{SQL CARBON EDIT}} import EditorInput +import { IWindowService, IURIToOpen, IOpenSettings, isWorkspaceToOpen } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -101,9 +101,9 @@ export const openWindowCommand = (accessor: ServicesAccessor, urisToOpen: IURITo } }; -export const newWindowCommand = (accessor: ServicesAccessor, options?: INewWindowOptions) => { - const windowsService = accessor.get(IWindowsService); - windowsService.openNewWindow(options); +export const newWindowCommand = (accessor: ServicesAccessor, options?: { reuse?: boolean, remoteAuthority?: string }) => { + const hostService = accessor.get(IHostService); + hostService.openEmptyWindow(options); }; // {{SQL CARBON EDIT}} diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 79db1329f4..7212720977 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -18,7 +18,6 @@ import { VIEWLET_ID, SortOrderConfiguration, FILE_EDITOR_INPUT_ID, IExplorerServ import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; import { SaveErrorHandler } from 'vs/workbench/contrib/files/browser/saveErrorHandler'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; import { BinaryFileEditor } from 'vs/workbench/contrib/files/browser/editors/binaryFileEditor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -26,7 +25,6 @@ import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry' import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; -import { DirtyFilesTracker } from 'vs/workbench/contrib/files/common/dirtyFilesTracker'; import { ExplorerViewlet, ExplorerViewletViewsContribution } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput'; @@ -101,17 +99,6 @@ registry.registerWorkbenchAction( ); // Register file editors -Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( - TextFileEditor, - TextFileEditor.ID, - nls.localize('textFileEditor', "Text File Editor") - ), - [ - new SyncDescriptor(FileEditorInput) - ] -); - Registry.as(EditorExtensions.Editors).registerEditor( new EditorDescriptor( BinaryFileEditor, @@ -145,8 +132,6 @@ interface ISerializedFileInput { // Register Editor Input Factory class FileEditorInputFactory implements IEditorInputFactory { - constructor() { } - serialize(editorInput: EditorInput): string { const fileEditorInput = editorInput; const resource = fileEditorInput.getResource(); @@ -183,9 +168,6 @@ Registry.as(WorkbenchExtensions.Workbench).regi // Register Save Error Handler Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SaveErrorHandler, LifecyclePhase.Starting); -// Register Dirty Files Tracker -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DirtyFilesTracker, LifecyclePhase.Starting); - // Register uri display for file uris Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(FileUriLabelContribution, LifecyclePhase.Starting); @@ -341,7 +323,8 @@ configurationRegistry.registerConfiguration({ 'files.maxMemoryForLargeFilesMB': { 'type': 'number', 'default': 4096, - 'markdownDescription': nls.localize('maxMemoryForLargeFilesMB', "Controls the memory available to VS Code after restart when trying to open large files. Same effect as specifying `--max-memory=NEWSIZE` on the command line.") + 'markdownDescription': nls.localize('maxMemoryForLargeFilesMB', "Controls the memory available to VS Code after restart when trying to open large files. Same effect as specifying `--max-memory=NEWSIZE` on the command line."), + included: platform.isNative }, 'files.simpleDialog.enable': { 'type': 'boolean', diff --git a/src/vs/workbench/contrib/files/browser/files.web.contribution.ts b/src/vs/workbench/contrib/files/browser/files.web.contribution.ts new file mode 100644 index 0000000000..4ae1f3dc98 --- /dev/null +++ b/src/vs/workbench/contrib/files/browser/files.web.contribution.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { EditorInput } from 'vs/workbench/common/editor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; +import { DirtyFilesTracker } from 'vs/workbench/contrib/files/common/dirtyFilesTracker'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; + +// Register file editor +Registry.as(EditorExtensions.Editors).registerEditor( + new EditorDescriptor( + TextFileEditor, + TextFileEditor.ID, + nls.localize('textFileEditor', "Text File Editor") + ), + [ + new SyncDescriptor(FileEditorInput) + ] +); + +// Register Dirty Files Tracker +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DirtyFilesTracker, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/files/browser/media/action-close-dark.svg b/src/vs/workbench/contrib/files/browser/media/action-close-dark.svg deleted file mode 100644 index f8af265cc4..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/action-close-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/action-close-dirty-dark.svg b/src/vs/workbench/contrib/files/browser/media/action-close-dirty-dark.svg deleted file mode 100644 index 02dafab76f..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/action-close-dirty-dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/files/browser/media/action-close-dirty-focus.svg b/src/vs/workbench/contrib/files/browser/media/action-close-dirty-focus.svg deleted file mode 100644 index 33a3b4aeed..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/action-close-dirty-focus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/files/browser/media/action-close-dirty.svg b/src/vs/workbench/contrib/files/browser/media/action-close-dirty.svg deleted file mode 100644 index 409e5fa539..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/action-close-dirty.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/files/browser/media/action-close-focus.svg b/src/vs/workbench/contrib/files/browser/media/action-close-focus.svg deleted file mode 100644 index 865c5aaea5..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/action-close-focus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/files/browser/media/action-close-light.svg b/src/vs/workbench/contrib/files/browser/media/action-close-light.svg deleted file mode 100644 index 7acc410338..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/action-close-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/add-file-dark.svg b/src/vs/workbench/contrib/files/browser/media/add-file-dark.svg deleted file mode 100644 index f162f2046e..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/add-file-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/add-file-hc.svg b/src/vs/workbench/contrib/files/browser/media/add-file-hc.svg deleted file mode 100644 index 79d286c427..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/add-file-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/add-file-light.svg b/src/vs/workbench/contrib/files/browser/media/add-file-light.svg deleted file mode 100644 index 048c2d9824..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/add-file-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/add-folder-dark.svg b/src/vs/workbench/contrib/files/browser/media/add-folder-dark.svg deleted file mode 100644 index 2a8d206bcf..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/add-folder-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/add-folder-hc.svg b/src/vs/workbench/contrib/files/browser/media/add-folder-hc.svg deleted file mode 100644 index d247ff5400..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/add-folder-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/add-folder-light.svg b/src/vs/workbench/contrib/files/browser/media/add-folder-light.svg deleted file mode 100644 index d5e85ce9d0..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/add-folder-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/close-all-dark.svg b/src/vs/workbench/contrib/files/browser/media/close-all-dark.svg deleted file mode 100644 index 12a89f8e39..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/close-all-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/close-all-light.svg b/src/vs/workbench/contrib/files/browser/media/close-all-light.svg deleted file mode 100644 index 53421f0121..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/close-all-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/collapse-all-dark.svg b/src/vs/workbench/contrib/files/browser/media/collapse-all-dark.svg deleted file mode 100644 index 4862c55dbe..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/collapse-all-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/collapse-all-hc.svg b/src/vs/workbench/contrib/files/browser/media/collapse-all-hc.svg deleted file mode 100644 index 05f920b29b..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/collapse-all-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/collapse-all-light.svg b/src/vs/workbench/contrib/files/browser/media/collapse-all-light.svg deleted file mode 100644 index 6359b42e62..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/collapse-all-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index 79a575d5e8..3ba3d4bae9 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -62,6 +62,8 @@ .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar { visibility: hidden; + display: flex; + align-items: center; } .explorer-viewlet .panel-header .count { @@ -84,9 +86,12 @@ display: block; } -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .close-editor-action { +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .codicon-close { width: 8px; height: 22px; + display: flex; + align-items: center; + justify-content: center; } .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-close-all-files, diff --git a/src/vs/workbench/contrib/files/browser/media/fileactions.css b/src/vs/workbench/contrib/files/browser/media/fileactions.css index 85ec58c292..b97c0f5ac4 100644 --- a/src/vs/workbench/contrib/files/browser/media/fileactions.css +++ b/src/vs/workbench/contrib/files/browser/media/fileactions.css @@ -3,84 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* Close all */ -.monaco-workbench .explorer-viewlet .action-close-all-files { - background: url("close-all-light.svg") center center no-repeat; -} - -.vs-dark .monaco-workbench .explorer-viewlet .action-close-all-files { - background: url("close-all-dark.svg") center center no-repeat; -} - -.hc-black .monaco-workbench .explorer-viewlet .action-close-all-files { - background: url("close-all-light.svg") center center no-repeat; -} - -/* Save all */ -.monaco-workbench .explorer-action.save-all { - background: url("save-all-light.svg") center center no-repeat; -} - -.vs-dark .monaco-workbench .explorer-action.save-all { - background: url("save-all-dark.svg") center center no-repeat; -} - -.hc-black .monaco-workbench .explorer-action.save-all { - background: url("save-all-hc.svg") center center no-repeat; -} - -/* Add file */ -.monaco-workbench .explorer-action.new-file { - background: url("add-file-light.svg") center center no-repeat; -} - -.vs-dark .monaco-workbench .explorer-action.new-file { - background: url("add-file-dark.svg") center center no-repeat; -} - -.hc-black .monaco-workbench .explorer-action.new-file { - background: url("add-file-hc.svg") center center no-repeat; -} - -/* Add Folder */ -.monaco-workbench .explorer-action.new-folder { - background: url("add-folder-light.svg") center center no-repeat; -} - -.vs-dark .monaco-workbench .explorer-action.new-folder { - background: url("add-folder-dark.svg") center center no-repeat; -} - -.hc-black .monaco-workbench .explorer-action.new-folder { - background: url("add-folder-hc.svg") center center no-repeat; -} - -/* Refresh */ -.monaco-workbench .explorer-action.refresh-explorer { - background: url("refresh-light.svg") center center no-repeat; -} - -.vs-dark .monaco-workbench .explorer-action.refresh-explorer { - background: url("refresh-dark.svg") center center no-repeat; -} - -.hc-black .monaco-workbench .explorer-action.refresh-explorer { - background: url("refresh-hc.svg") center center no-repeat; -} - -/* Collapse all */ -.monaco-workbench .explorer-action.collapse-explorer { - background: url("collapse-all-light.svg") center center no-repeat; -} - -.vs-dark .monaco-workbench .explorer-action.collapse-explorer { - background: url("collapse-all-dark.svg") center center no-repeat; -} - -.hc-black .monaco-workbench .explorer-action.collapse-explorer { - background: url("collapse-all-hc.svg") center center no-repeat; -} - /* Split editor vertical */ .monaco-workbench .quick-open-sidebyside-vertical { background-image: url("split-editor-vertical-light.svg"); @@ -116,28 +38,6 @@ background: url("preview-dark.svg") center center no-repeat; } -.explorer-viewlet .explorer-open-editors .close-editor-action { - background: url("action-close-light.svg") center center no-repeat; -} - -.explorer-viewlet .explorer-open-editors .focused .monaco-list-row.selected:not(.highlighted) .close-editor-action { - background: url("action-close-focus.svg") center center no-repeat; -} - -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .close-editor-action { - background: url("action-close-dirty.svg") center center no-repeat; -} - -.vs-dark .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .close-editor-action, -.hc-black .monaco-workbench .explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .close-editor-action { - background: url("action-close-dirty-dark.svg") center center no-repeat; -} - -.explorer-viewlet .explorer-open-editors .monaco-list.focused .monaco-list-row.selected.dirty:not(:hover) > .monaco-action-bar .close-editor-action { - background: url("action-close-dirty-focus.svg") center center no-repeat; -} - -.vs-dark .monaco-workbench .explorer-viewlet .explorer-open-editors .close-editor-action, -.hc-black .monaco-workbench .explorer-viewlet .explorer-open-editors .close-editor-action { - background: url("action-close-dark.svg") center center no-repeat; +.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-close::before { + content: "\ea71"; } diff --git a/src/vs/workbench/contrib/files/browser/media/refresh-dark.svg b/src/vs/workbench/contrib/files/browser/media/refresh-dark.svg deleted file mode 100644 index 57473d6d9a..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/refresh-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/refresh-hc.svg b/src/vs/workbench/contrib/files/browser/media/refresh-hc.svg deleted file mode 100644 index d2eb7f7467..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/refresh-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/refresh-light.svg b/src/vs/workbench/contrib/files/browser/media/refresh-light.svg deleted file mode 100644 index e90c502c3d..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/refresh-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/files/browser/media/save-all-dark.svg b/src/vs/workbench/contrib/files/browser/media/save-all-dark.svg deleted file mode 100644 index 8acad37a99..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/save-all-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/save-all-hc.svg b/src/vs/workbench/contrib/files/browser/media/save-all-hc.svg deleted file mode 100644 index 5d519f7605..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/save-all-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/media/save-all-light.svg b/src/vs/workbench/contrib/files/browser/media/save-all-light.svg deleted file mode 100644 index 529e489a81..0000000000 --- a/src/vs/workbench/contrib/files/browser/media/save-all-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts index 3d3b3e011a..f231abad56 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts @@ -15,7 +15,7 @@ import { explorerRootErrorEmitter } from 'vs/workbench/contrib/files/browser/vie export class ExplorerDecorationsProvider implements IDecorationsProvider { readonly label: string = localize('label', "Explorer"); - private _onDidChange = new Emitter(); + private readonly _onDidChange = new Emitter(); private readonly toDispose = new DisposableStore(); constructor( diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 1ec534e70e..a3fc266af2 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -604,7 +604,6 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } } - private async handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise { const droppedResources = extractResources(originalEvent, true); // Check for dropped external files to be folders diff --git a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts index 5496e98224..3ebcc6c423 100644 --- a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts +++ b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts @@ -7,8 +7,6 @@ import * as nls from 'vs/nls'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; import { TextFileModelChangeEvent, ITextFileService, AutoSaveMode, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; -import { platform, Platform } from 'vs/base/common/platform'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -18,22 +16,18 @@ import * as arrays from 'vs/base/common/arrays'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class DirtyFilesTracker extends Disposable implements IWorkbenchContribution { - private isDocumentedEdited: boolean; private lastKnownDirtyCount: number | undefined; private readonly badgeHandle = this._register(new MutableDisposable()); constructor( - @ITextFileService private readonly textFileService: ITextFileService, + @ITextFileService protected readonly textFileService: ITextFileService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IEditorService private readonly editorService: IEditorService, @IActivityService private readonly activityService: IActivityService, - @IWindowService private readonly windowService: IWindowService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService + @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService ) { super(); - this.isDocumentedEdited = false; - this.registerListeners(); } @@ -54,23 +48,15 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut return typeof this.lastKnownDirtyCount === 'number' && this.lastKnownDirtyCount > 0; } - private onUntitledDidChangeDirty(resource: URI): void { + protected onUntitledDidChangeDirty(resource: URI): void { const gotDirty = this.untitledEditorService.isDirty(resource); - if ((!this.isDocumentedEdited && gotDirty) || (this.isDocumentedEdited && !gotDirty)) { - this.updateDocumentEdited(); - } - if (gotDirty || this.hasDirtyCount) { this.updateActivityBadge(); } } - private onTextFilesDirty(e: TextFileModelChangeEvent[]): void { - if ((this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) && !this.isDocumentedEdited) { - this.updateDocumentEdited(); // no indication needed when auto save is enabled for short delay - } - + protected onTextFilesDirty(e: TextFileModelChangeEvent[]): void { if (this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) { this.updateActivityBadge(); // no indication needed when auto save is enabled for short delay } @@ -98,29 +84,17 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut })); } - private onTextFilesSaved(e: TextFileModelChangeEvent[]): void { - if (this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - + protected onTextFilesSaved(e: TextFileModelChangeEvent[]): void { if (this.hasDirtyCount) { this.updateActivityBadge(); } } - private onTextFilesSaveError(e: TextFileModelChangeEvent[]): void { - if (!this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - + protected onTextFilesSaveError(e: TextFileModelChangeEvent[]): void { this.updateActivityBadge(); } - private onTextFilesReverted(e: TextFileModelChangeEvent[]): void { - if (this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - + protected onTextFilesReverted(e: TextFileModelChangeEvent[]): void { if (this.hasDirtyCount) { this.updateActivityBadge(); } @@ -136,13 +110,4 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut this.badgeHandle.value = this.activityService.showActivity(VIEWLET_ID, new NumberBadge(dirtyCount, num => num === 1 ? nls.localize('dirtyFile', "1 unsaved file") : nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), 'explorer-viewlet-label'); } } - - private updateDocumentEdited(): void { - if (platform === Platform.Mac) { - const hasDirtyFiles = this.textFileService.isDirty(); - this.isDocumentedEdited = hasDirtyFiles; - - this.windowService.setDocumentEdited(hasDirtyFiles); - } - } } diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index b68ff49bc4..40b6205f2d 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { memoize } from 'vs/base/common/decorators'; +import { createMemoizer } from 'vs/base/common/decorators'; import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { EncodingMode, ConfirmResult, EditorInput, IFileEditorInput, ITextEditorModel, Verbosity, IRevertOptions } from 'vs/workbench/common/editor'; @@ -28,13 +28,15 @@ const enum ForceOpenAs { * A file editor input is the input type for the file editor of file system resources. */ export class FileEditorInput extends EditorInput implements IFileEditorInput { + + private static readonly MEMOIZER = createMemoizer(); + private preferredEncoding: string; private preferredMode: string; private forceOpenAs: ForceOpenAs = ForceOpenAs.None; private textModelReference: Promise> | null = null; - private name: string; /** * An editor input who's contents are retrieved from file services. @@ -69,6 +71,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { this._register(this.textFileService.models.onModelSaved(e => this.onDirtyStateChange(e))); this._register(this.textFileService.models.onModelReverted(e => this.onDirtyStateChange(e))); this._register(this.textFileService.models.onModelOrphanedChanged(e => this.onModelOrphanedChanged(e))); + this._register(this.labelService.onDidChangeFormatters(() => FileEditorInput.MEMOIZER.clear())); } private onDirtyStateChange(e: TextFileModelChangeEvent): void { @@ -144,22 +147,22 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return FILE_EDITOR_INPUT_ID; } + @FileEditorInput.MEMOIZER getName(): string { - if (!this.name) { - this.name = this.labelService.getUriBasenameLabel(this.resource); - } - - return this.decorateLabel(this.name); + return this.decorateLabel(this.labelService.getUriBasenameLabel(this.resource)); } + @FileEditorInput.MEMOIZER private get shortDescription(): string { return this.labelService.getUriBasenameLabel(dirname(this.resource)); } + @FileEditorInput.MEMOIZER private get mediumDescription(): string { return this.labelService.getUriLabel(dirname(this.resource), { relative: true }); } + @FileEditorInput.MEMOIZER private get longDescription(): string { return this.labelService.getUriLabel(dirname(this.resource)); } @@ -176,17 +179,17 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { } } - @memoize + @FileEditorInput.MEMOIZER private get shortTitle(): string { return this.getName(); } - @memoize + @FileEditorInput.MEMOIZER private get mediumTitle(): string { return this.labelService.getUriLabel(this.resource, { relative: true }); } - @memoize + @FileEditorInput.MEMOIZER private get longTitle(): string { return this.labelService.getUriLabel(this.resource); } diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index c06fe9ac9e..d3b9d9834d 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -21,7 +21,7 @@ export class ExplorerModel implements IDisposable { private _roots!: ExplorerItem[]; private _listener: IDisposable; - private _onDidChangeRoots = new Emitter(); + private readonly _onDidChangeRoots = new Emitter(); constructor(private readonly contextService: IWorkspaceContextService) { const setRoots = () => this._roots = this.contextService.getWorkspace().folders diff --git a/src/vs/workbench/contrib/files/common/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts index 489ebe5014..8ba091f964 100644 --- a/src/vs/workbench/contrib/files/common/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -31,11 +31,11 @@ export class ExplorerService implements IExplorerService { private static readonly EXPLORER_FILE_CHANGES_REACT_DELAY = 500; // delay in ms to react to file changes to give our internal events a chance to react first - private _onDidChangeRoots = new Emitter(); - private _onDidChangeItem = new Emitter<{ item?: ExplorerItem, recursive: boolean }>(); - private _onDidChangeEditable = new Emitter(); - private _onDidSelectResource = new Emitter<{ resource?: URI, reveal?: boolean }>(); - private _onDidCopyItems = new Emitter<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>(); + private readonly _onDidChangeRoots = new Emitter(); + private readonly _onDidChangeItem = new Emitter<{ item?: ExplorerItem, recursive: boolean }>(); + private readonly _onDidChangeEditable = new Emitter(); + private readonly _onDidSelectResource = new Emitter<{ resource?: URI, reveal?: boolean }>(); + private readonly _onDidCopyItems = new Emitter<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>(); private readonly disposables = new DisposableStore(); private editable: { stat: ExplorerItem, data: IEditableData } | undefined; private _sortOrder: SortOrder; diff --git a/src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts b/src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts new file mode 100644 index 0000000000..e6b60c104c --- /dev/null +++ b/src/vs/workbench/contrib/files/electron-browser/dirtyFilesTracker.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TextFileModelChangeEvent, ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; +import { platform, Platform } from 'vs/base/common/platform'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IActivityService } from 'vs/workbench/services/activity/common/activity'; +import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { DirtyFilesTracker } from 'vs/workbench/contrib/files/common/dirtyFilesTracker'; +import { IElectronService } from 'vs/platform/electron/node/electron'; + +export class NativeDirtyFilesTracker extends DirtyFilesTracker { + private isDocumentedEdited: boolean; + + constructor( + @ITextFileService protected readonly textFileService: ITextFileService, + @ILifecycleService lifecycleService: ILifecycleService, + @IEditorService editorService: IEditorService, + @IActivityService activityService: IActivityService, + @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService, + @IElectronService private readonly electronService: IElectronService + ) { + super(textFileService, lifecycleService, editorService, activityService, untitledEditorService); + + this.isDocumentedEdited = false; + } + + protected onUntitledDidChangeDirty(resource: URI): void { + const gotDirty = this.untitledEditorService.isDirty(resource); + if ((!this.isDocumentedEdited && gotDirty) || (this.isDocumentedEdited && !gotDirty)) { + this.updateDocumentEdited(); + } + + super.onUntitledDidChangeDirty(resource); + } + + protected onTextFilesDirty(e: TextFileModelChangeEvent[]): void { + if ((this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) && !this.isDocumentedEdited) { + this.updateDocumentEdited(); // no indication needed when auto save is enabled for short delay + } + + super.onTextFilesDirty(e); + } + + protected onTextFilesSaved(e: TextFileModelChangeEvent[]): void { + if (this.isDocumentedEdited) { + this.updateDocumentEdited(); + } + + super.onTextFilesSaved(e); + } + + protected onTextFilesSaveError(e: TextFileModelChangeEvent[]): void { + if (!this.isDocumentedEdited) { + this.updateDocumentEdited(); + } + + super.onTextFilesSaveError(e); + } + + protected onTextFilesReverted(e: TextFileModelChangeEvent[]): void { + if (this.isDocumentedEdited) { + this.updateDocumentEdited(); + } + + super.onTextFilesReverted(e); + } + + private updateDocumentEdited(): void { + if (platform === Platform.Mac) { + const hasDirtyFiles = this.textFileService.isDirty(); + this.isDocumentedEdited = hasDirtyFiles; + + this.electronService.setDocumentEdited(hasDirtyFiles); + } + } +} diff --git a/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts new file mode 100644 index 0000000000..2772a704ed --- /dev/null +++ b/src/vs/workbench/contrib/files/electron-browser/files.contribution.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { EditorInput } from 'vs/workbench/common/editor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; +import { NativeTextFileEditor } from 'vs/workbench/contrib/files/electron-browser/textFileEditor'; +import { NativeDirtyFilesTracker } from 'vs/workbench/contrib/files/electron-browser/dirtyFilesTracker'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; + +// Register file editor +Registry.as(EditorExtensions.Editors).registerEditor( + new EditorDescriptor( + NativeTextFileEditor, + NativeTextFileEditor.ID, + nls.localize('textFileEditor', "Text File Editor") + ), + [ + new SyncDescriptor(FileEditorInput) + ] +); + +// Register Dirty Files Tracker +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NativeDirtyFilesTracker, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts new file mode 100644 index 0000000000..9bfad06f07 --- /dev/null +++ b/src/vs/workbench/contrib/files/electron-browser/textFileEditor.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { EditorOptions } from 'vs/workbench/common/editor'; +import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { MIN_MAX_MEMORY_SIZE_MB, FALLBACK_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/node/files'; +import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { Action } from 'vs/base/common/actions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { IElectronService } from 'vs/platform/electron/node/electron'; + +/** + * An implementation of editor for file system resources. + */ +export class NativeTextFileEditor extends TextFileEditor { + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IFileService fileService: IFileService, + @IViewletService viewletService: IViewletService, + @IInstantiationService instantiationService: IInstantiationService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IStorageService storageService: IStorageService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @IEditorService editorService: IEditorService, + @IThemeService themeService: IThemeService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @ITextFileService textFileService: ITextFileService, + @IElectronService private readonly electronService: IElectronService, + @IPreferencesService private readonly preferencesService: IPreferencesService, + @IWindowService windowService: IWindowService, + @IExplorerService explorerService: IExplorerService + ) { + super(telemetryService, fileService, viewletService, instantiationService, contextService, storageService, configurationService, editorService, themeService, editorGroupService, textFileService, windowService, explorerService); + } + + protected handleSetInputError(error: Error, input: FileEditorInput, options: EditorOptions | undefined): void { + + // Allow to restart with higher memory limit if the file is too large + if ((error).fileOperationResult === FileOperationResult.FILE_EXCEED_MEMORY_LIMIT) { + const memoryLimit = Math.max(MIN_MAX_MEMORY_SIZE_MB, +this.configurationService.getValue(undefined, 'files.maxMemoryForLargeFilesMB') || FALLBACK_MAX_MEMORY_SIZE_MB); + + throw createErrorWithActions(toErrorMessage(error), { + actions: [ + new Action('workbench.window.action.relaunchWithIncreasedMemoryLimit', nls.localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), undefined, true, () => { + return this.electronService.relaunch({ + addArgs: [ + `--max-memory=${memoryLimit}` + ] + }); + }), + new Action('workbench.window.action.configureMemoryLimit', nls.localize('configureMemoryLimit', 'Configure Memory Limit'), undefined, true, () => { + return this.preferencesService.openGlobalSettings(undefined, { query: 'files.maxMemoryForLargeFilesMB' }); + }) + ] + }); + } + + // Fallback to handling in super type + super.handleSetInputError(error, input, options); + } +} diff --git a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index cc515c8994..d3366c4104 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -20,7 +20,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import Severity from 'vs/base/common/severity'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { VIEWLET_ID as EXTENSIONS_VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/contrib/extensions/common/extensions'; @@ -39,7 +39,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo @INotificationService private readonly notificationService: INotificationService, @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IWindowsService private readonly windowsService: IWindowsService, + @IHostService private readonly hostService: IHostService, @IStorageService private readonly storageService: IStorageService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @@ -81,7 +81,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); - updatePromise.then(() => this.windowsService.relaunch({}), e => this.notificationService.error(e)); + updatePromise.then(() => this.hostService.restart(), e => this.notificationService.error(e)); } }], { @@ -171,7 +171,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo label: translations['installAndRestart'], run: () => { logUserReaction('installAndRestart'); - this.installExtension(extensionToInstall).then(() => this.windowsService.relaunch({})); + this.installExtension(extensionToInstall).then(() => this.hostService.restart()); } }; @@ -233,7 +233,7 @@ function registerLocaleDefinitionSchema(languages: string[]): void { jsonRegistry.registerSchema(localeDefinitionFileSchemaId, { id: localeDefinitionFileSchemaId, allowComments: true, - allowsTrailingCommas: true, + allowTrailingCommas: true, description: 'Locale Definition file', type: 'object', default: { diff --git a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts index 80052645f8..eadcbe2694 100644 --- a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts @@ -9,7 +9,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { ILocalizationsService, LanguageType } from 'vs/platform/localizations/common/localizations'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { language } from 'vs/base/common/platform'; import { firstIndex } from 'vs/base/common/arrays'; @@ -26,7 +26,7 @@ export class ConfigureLocaleAction extends Action { @ILocalizationsService private readonly localizationService: ILocalizationsService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, - @IWindowsService private readonly windowsService: IWindowsService, + @IHostService private readonly hostService: IHostService, @INotificationService private readonly notificationService: INotificationService, @IViewletService private readonly viewletService: IViewletService, @IDialogService private readonly dialogService: IDialogService @@ -74,7 +74,7 @@ export class ConfigureLocaleAction extends Action { }); if (restart.confirmed) { - this.windowsService.relaunch({}); + this.hostService.restart(); } } } catch (e) { diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index 1a1ccf254c..9f99d73b74 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 codicon-collapse-all', true, async () => this.collapseAll())); + this.collapseAllAction = this._register(new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action 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 9e58ba1ca7..a6fa78298f 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts @@ -298,7 +298,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { export class QuickFixAction extends Action { public static readonly ID: string = 'workbench.actions.problems.quickfix'; - private static readonly CLASS: string = 'markers-panel-action-quickfix'; + private static readonly CLASS: string = 'markers-panel-action-quickfix codicon-lightbulb'; private static readonly AUTO_FIX_CLASS: string = QuickFixAction.CLASS + ' autofixable'; private readonly _onShowQuickFixes = this._register(new Emitter()); diff --git a/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix-dark.svg b/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix-dark.svg deleted file mode 100644 index 34d4f3aedf..0000000000 --- a/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix-hc.svg b/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix-hc.svg deleted file mode 100644 index 34d4f3aedf..0000000000 --- a/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix-light.svg b/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix-light.svg deleted file mode 100644 index c34a0c2805..0000000000 --- a/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/lightbulb-dark.svg b/src/vs/workbench/contrib/markers/browser/media/lightbulb-dark.svg deleted file mode 100644 index d2b6e1287a..0000000000 --- a/src/vs/workbench/contrib/markers/browser/media/lightbulb-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/lightbulb-hc.svg b/src/vs/workbench/contrib/markers/browser/media/lightbulb-hc.svg deleted file mode 100644 index d2b6e1287a..0000000000 --- a/src/vs/workbench/contrib/markers/browser/media/lightbulb-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/lightbulb-light.svg b/src/vs/workbench/contrib/markers/browser/media/lightbulb-light.svg deleted file mode 100644 index 8572effd08..0000000000 --- a/src/vs/workbench/contrib/markers/browser/media/lightbulb-light.svg +++ /dev/null @@ -1,4 +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 b7c60be8d8..dd21e3bd6f 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -153,31 +153,6 @@ height: 22px; } -.markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix { - background: url('lightbulb-light.svg') center no-repeat; - margin-right: 0px; -} - -.markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix.autofixable { - background: url('lightbulb-autofix-light.svg') center center no-repeat; -} - -.vs-dark .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix { - background: url('lightbulb-dark.svg') center no-repeat; -} - -.hc-black .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix { - background: url('lightbulb-hc.svg') center no-repeat; -} - -.vs-dark .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix.autofixable { - background: url('lightbulb-autofix-dark.svg') center center no-repeat; -} - -.hc-black .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix.autofixable { - background: url('lightbulb-autofix-hc.svg') center center no-repeat; -} - .markers-panel .monaco-tl-contents .actions .monaco-action-bar { display: none; } diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts index a0a3b22e1a..eb074dae00 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts @@ -169,7 +169,7 @@ class OutlineViewState { private _filterOnType = true; private _sortBy = OutlineSortOrder.ByKind; - private _onDidChange = new Emitter<{ followCursor?: boolean, sortBy?: boolean, filterOnType?: boolean }>(); + private readonly _onDidChange = new Emitter<{ followCursor?: boolean, sortBy?: boolean, filterOnType?: boolean }>(); readonly onDidChange = this._onDidChange.event; set followCursor(value: boolean) { @@ -385,7 +385,7 @@ export class OutlinePanel extends ViewletPanel { getActions(): IAction[] { return [ - new Action('collapse', localize('collapse', "Collapse All"), 'explorer-action collapse-explorer', true, () => { + new Action('collapse', localize('collapse', "Collapse All"), 'explorer-action codicon-collapse-all', true, () => { return new CollapseAction(this._tree, true, undefined).run(); }) ]; diff --git a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index 8039bd2521..b5f53dd469 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -177,10 +177,10 @@ class PerfModelContentProvider implements ITextModelContentProvider { if (!times) { continue; } - if (times.startup) { - eager.push([id, times.startup, times.codeLoadingTime, times.activateCallTime, times.activateResolvedTime, times.activationEvent]); + if (times.activationReason.startup) { + eager.push([id, times.activationReason.startup, times.codeLoadingTime, times.activateCallTime, times.activateResolvedTime, times.activationReason.activationEvent, times.activationReason.extensionId.value]); } else { - normal.push([id, times.startup, times.codeLoadingTime, times.activateCallTime, times.activateResolvedTime, times.activationEvent]); + normal.push([id, times.activationReason.startup, times.codeLoadingTime, times.activateCallTime, times.activateResolvedTime, times.activationReason.activationEvent, times.activationReason.extensionId.value]); } } @@ -188,7 +188,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { if (table.length > 0) { md.heading(2, 'Extension Activation Stats'); md.table( - ['Extension', 'Eager', 'Load Code', 'Call Activate', 'Finish Activate', 'Event'], + ['Extension', 'Eager', 'Load Code', 'Call Activate', 'Finish Activate', 'Event', 'By'], table ); } diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts index 94e2657659..e63de97ca4 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts @@ -11,7 +11,6 @@ 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/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'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -23,7 +22,6 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; export class StartupProfiler implements IWorkbenchContribution { constructor( - @IWindowsService private readonly _windowsService: IWindowsService, @IDialogService private readonly _dialogService: IDialogService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @ITextModelService private readonly _textModelResolverService: ITextModelService, @@ -95,13 +93,13 @@ export class StartupProfiler implements IWorkbenchContribution { secondaryButton: undefined }).then(() => { // now we are ready to restart - this._windowsService.relaunch({ removeArgs }); + this._electronService.relaunch({ removeArgs }); }); }); } else { // simply restart - this._windowsService.relaunch({ removeArgs }); + this._electronService.relaunch({ removeArgs }); } }); }); diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts index d8f53e1242..ff6acb185d 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts @@ -13,7 +13,7 @@ import { ILifecycleService, StartupKind } from 'vs/platform/lifecycle/common/lif 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'; +import { IElectronService } from 'vs/platform/electron/node/electron'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import * as files from 'vs/workbench/contrib/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -27,7 +27,7 @@ export class StartupTimings implements IWorkbenchContribution { constructor( @ITimerService private readonly _timerService: ITimerService, - @IWindowsService private readonly _windowsService: IWindowsService, + @IElectronService private readonly _electronService: IElectronService, @IEditorService private readonly _editorService: IEditorService, @IViewletService private readonly _viewletService: IViewletService, @IPanelService private readonly _panelService: IPanelService, @@ -76,10 +76,10 @@ export class StartupTimings implements IWorkbenchContribution { ]).then(([startupMetrics]) => { return promisify(appendFile)(appendTo, `${startupMetrics.ellapsed}\t${product.nameShort}\t${(product.commit || '').slice(0, 10) || '0000000000'}\t${sessionId}\t${isStandardStartup ? 'standard_start' : 'NO_standard_start'}\n`); }).then(() => { - this._windowsService.quit(); + this._electronService.quit(); }).catch(err => { console.error(err); - this._windowsService.quit(); + this._electronService.quit(); }); } diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index fedad0c3b2..0753536523 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/keybindings'; import * as nls from 'vs/nls'; import { OS } from 'vs/base/common/platform'; -import { Disposable, dispose, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { Widget } from 'vs/base/browser/ui/widget'; @@ -36,7 +36,7 @@ export class KeybindingsSearchWidget extends SearchWidget { private _chordPart: ResolvedKeybinding | null; private _inputValue: string; - private recordDisposables: IDisposable[] = []; + private readonly recordDisposables = this._register(new DisposableStore()); private _onKeybinding = this._register(new Emitter<[ResolvedKeybinding | null, ResolvedKeybinding | null]>()); readonly onKeybinding: Event<[ResolvedKeybinding | null, ResolvedKeybinding | null]> = this._onKeybinding.event; @@ -69,9 +69,9 @@ export class KeybindingsSearchWidget extends SearchWidget { } startRecordingKeys(): void { - this.recordDisposables.push(dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => this._onKeyDown(new StandardKeyboardEvent(e)))); - this.recordDisposables.push(dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.BLUR, () => this._onBlur.fire())); - this.recordDisposables.push(dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.INPUT, () => { + this.recordDisposables.add(dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => this._onKeyDown(new StandardKeyboardEvent(e)))); + this.recordDisposables.add(dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.BLUR, () => this._onBlur.fire())); + this.recordDisposables.add(dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.INPUT, () => { // Prevent other characters from showing up this.setInputValue(this._inputValue); })); @@ -79,7 +79,7 @@ export class KeybindingsSearchWidget extends SearchWidget { stopRecordingKeys(): void { this._reset(); - dispose(this.recordDisposables); + this.recordDisposables.dispose(); } setInputValue(value: string): void { diff --git a/src/vs/workbench/contrib/preferences/browser/media/preferences.css b/src/vs/workbench/contrib/preferences/browser/media/preferences.css index a70bffd262..91da22206b 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/preferences.css +++ b/src/vs/workbench/contrib/preferences/browser/media/preferences.css @@ -3,6 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.preferences-editor { + display: flex; + flex-direction: column; +} + .preferences-editor > .preferences-header { padding-left: 27px; padding-right: 32px; @@ -11,7 +16,7 @@ } .preferences-editor > .preferences-editors-container.side-by-side-preferences-editor { - position: relative; + flex: 1; } .preferences-editor > .preferences-editors-container.side-by-side-preferences-editor .preferences-header-container { @@ -245,4 +250,4 @@ padding: 10px; border-radius: 5px; cursor: pointer; -} \ No newline at end of file +} diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index 0a5232f9d4..2be433504b 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -154,14 +154,14 @@ width: 26px; } -.settings-editor > .settings-body .settings-tree-container .monaco-list-row .mouseover .setting-toolbar-container > .monaco-toolbar .toolbar-toggle-more, -.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-item.focused .setting-toolbar-container > .monaco-toolbar .toolbar-toggle-more, -.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-toolbar-container:hover > .monaco-toolbar .toolbar-toggle-more, -.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-toolbar-container > .monaco-toolbar .active .toolbar-toggle-more { +.settings-editor > .settings-body .settings-tree-container .monaco-list-row .mouseover .setting-toolbar-container > .monaco-toolbar .codicon-more, +.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-item.focused .setting-toolbar-container > .monaco-toolbar .codicon-more, +.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-toolbar-container:hover > .monaco-toolbar .codicon-more, +.settings-editor > .settings-body .settings-tree-container .monaco-list-row .setting-toolbar-container > .monaco-toolbar .active .codicon-more { opacity: 1; } -.settings-editor > .settings-body .settings-tree-container .setting-toolbar-container > .monaco-toolbar .toolbar-toggle-more { +.settings-editor > .settings-body .settings-tree-container .setting-toolbar-container > .monaco-toolbar .codicon-more { opacity: 0; transition: opacity .3s; width: 22px; @@ -171,11 +171,15 @@ background-size: 16px; } -.vs .settings-editor > .settings-body .settings-tree-container .monaco-toolbar .toolbar-toggle-more { +.settings-editor > .settings-body .settings-tree-container .setting-toolbar-container > .monaco-toolbar .codicon-more::before { + content: ' '; +} + +.vs .settings-editor > .settings-body .settings-tree-container .monaco-toolbar .codicon-more { background-image: url('configure-light.svg'); } -.vs-dark .settings-editor > .settings-body .settings-tree-container .monaco-toolbar .toolbar-toggle-more { +.vs-dark .settings-editor > .settings-body .settings-tree-container .monaco-toolbar .codicon-more { background-image: url('configure-dark.svg'); } diff --git a/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts similarity index 92% rename from src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts rename to src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index c86df42212..ad8ca6f654 100644 --- a/src/vs/workbench/contrib/relauncher/common/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -6,7 +6,8 @@ import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWindowsService, IWindowService, IWindowsConfiguration } from 'vs/platform/windows/common/windows'; +import { IWindowService, IWindowsConfiguration } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -23,7 +24,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ interface IConfiguration extends IWindowsConfiguration { update: { mode: string; }; telemetry: { enableCrashReporter: boolean }; - workbench: { list: { horizontalScrolling: boolean }, useExperimentalGridLayout: boolean }; + workbench: { list: { horizontalScrolling: boolean } }; debug: { console: { wordWrap: boolean } }; } @@ -36,11 +37,10 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private updateMode: string | undefined; private enableCrashReporter: boolean | undefined; private treeHorizontalScrolling: boolean | undefined; - private useGridLayout: boolean | undefined; private debugConsoleWordWrap: boolean | undefined; constructor( - @IWindowsService private readonly windowsService: IWindowsService, + @IHostService private readonly hostService: IHostService, @IWindowService private readonly windowService: IWindowService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEnvironmentService private readonly envService: IEnvironmentService, @@ -61,12 +61,6 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo changed = true; } - // Workbench Grid Layout - if (config.workbench && typeof config.workbench.useExperimentalGridLayout === 'boolean' && config.workbench.useExperimentalGridLayout !== this.useGridLayout) { - this.useGridLayout = config.workbench.useExperimentalGridLayout; - changed = true; - } - // Debug console word wrap if (config.debug && typeof config.debug.console.wordWrap === 'boolean' && config.debug.console.wordWrap !== this.debugConsoleWordWrap) { this.debugConsoleWordWrap = config.debug.console.wordWrap; @@ -124,7 +118,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo isNative ? localize('restart', "&&Restart") : localize('restartWeb', "&&Reload"), - () => this.windowsService.relaunch(Object.create(null)) + () => this.hostService.restart() ); } } @@ -159,7 +153,7 @@ export class WorkspaceChangeExtHostRelauncher extends Disposable implements IWor constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IExtensionService extensionService: IExtensionService, - @IWindowService windowService: IWindowService, + @IHostService hostService: IHostService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService ) { super(); @@ -170,7 +164,7 @@ export class WorkspaceChangeExtHostRelauncher extends Disposable implements IWor } if (environmentService.configuration.remoteAuthority) { - windowService.reloadWindow(); // TODO@aeschli, workaround + hostService.reload(); // TODO@aeschli, workaround } else if (isNative) { extensionService.restartExtensionHost(); } diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index a6ca519382..affc555f95 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -47,6 +47,7 @@ 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'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; interface HelpInformation { extensionDescription: IExtensionDescription; @@ -378,7 +379,8 @@ export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService + @IExtensionService extensionService: IExtensionService, + @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, ) { super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); @@ -419,7 +421,29 @@ export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { // too late, already added to the view model - return super.onDidAddViews(added); + const result = super.onDidAddViews(added); + + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + if (remoteAuthority) { + const actualRemoteAuthority = remoteAuthority.split('+')[0]; + added.forEach((descriptor) => { + const panel = this.getView(descriptor.viewDescriptor.id); + if (!panel) { + return; + } + + if (typeof descriptor.viewDescriptor.remoteAuthority === 'undefined' || + descriptor.viewDescriptor.remoteAuthority === actualRemoteAuthority || + descriptor.viewDescriptor.id === HelpPanel.ID + ) { + panel.setExpanded(true); + } else { + panel.setExpanded(false); + } + }); + } + + return result; } getTitle(): string { 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 62bfc2f8bd..e7386d39c4 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -12,13 +12,13 @@ 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 { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry, registerAction } 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'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; @@ -34,13 +34,13 @@ import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteA import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; 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'; +import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; -const WINDOW_ACTIONS_COMMAND_ID = 'remote.showActions'; -const CLOSE_REMOTE_COMMAND_ID = 'remote.closeRemote'; +const WINDOW_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu'; +const CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close'; export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenchContribution { @@ -61,20 +61,39 @@ export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenc @IExtensionService extensionService: IExtensionService, @IRemoteAgentService remoteAgentService: IRemoteAgentService, @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, - @IWindowsService windowService: IWindowsService + @IHostService hostService: IHostService ) { super(); this.windowCommandMenu = this.menuService.createMenu(MenuId.StatusBarWindowIndicatorMenu, this.contextKeyService); this._register(this.windowCommandMenu); - this._register(CommandsRegistry.registerCommand(WINDOW_ACTIONS_COMMAND_ID, _ => this.showIndicatorActions(this.windowCommandMenu))); - this._register(CommandsRegistry.registerCommand(CLOSE_REMOTE_COMMAND_ID, _ => this.remoteAuthority && windowService.openNewWindow({ reuseWindow: true }))); + const category = nls.localize('remote.category', "Remote"); + + registerAction({ + id: WINDOW_ACTIONS_COMMAND_ID, + category, + title: { value: nls.localize('remote.showMenu', "Show Remote Menu"), original: 'Show Remote Menu' }, + menu: { + menuId: MenuId.CommandPalette + }, + handler: (_accessor) => this.showIndicatorActions(this.windowCommandMenu) + }); this.remoteAuthority = environmentService.configuration.remoteAuthority; Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(this.remoteAuthority || ''); if (this.remoteAuthority) { + registerAction({ + id: CLOSE_REMOTE_COMMAND_ID, + category, + title: { value: nls.localize('remote.close', "Close Remote Connection"), original: 'Close Remote Connection' }, + menu: { + menuId: MenuId.CommandPalette + }, + handler: (_accessor) => this.remoteAuthority && hostService.openEmptyWindow({ reuse: true }) + }); + // Pending entry until extensions are ready this.renderWindowIndicator(nls.localize('host.open', "$(sync~spin) Opening Remote..."), undefined, WINDOW_ACTIONS_COMMAND_ID); this.connectionState = 'initializing'; @@ -305,6 +324,7 @@ class RemoteTelemetryEnablementUpdater extends Disposable implements IWorkbenchC } } + class RemoteEmptyWorkbenchPresentation extends Disposable implements IWorkbenchContribution { constructor( @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, diff --git a/src/vs/workbench/contrib/scm/browser/scmActivity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts similarity index 100% rename from src/vs/workbench/contrib/scm/browser/scmActivity.ts rename to src/vs/workbench/contrib/scm/browser/activity.ts diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index c986da1d35..cae326a28a 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -149,7 +149,7 @@ function getChangeTypeColor(theme: ITheme, changeType: ChangeType): Color | unde } } -function getOuterEditorFromDiffEditor(accessor: ServicesAccessor): ICodeEditor | null { +function getOuterEditorFromDiffEditor(accessor: ServicesAccessor): ICodeEditor | undefined { const diffEditors = accessor.get(ICodeEditorService).listDiffEditors(); for (const diffEditor of diffEditors) { @@ -250,8 +250,8 @@ class DirtyDiffWidget extends PeekViewWidget { protected _fillHead(container: HTMLElement): void { super._fillHead(container); - const previous = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowPreviousChangeAction(), 'show-previous-change chevron-up'); - const next = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowNextChangeAction(), 'show-next-change chevron-down'); + const previous = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowPreviousChangeAction(), 'codicon-arrow-up'); + const next = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowNextChangeAction(), 'codicon-arrow-down'); this._disposables.add(previous); this._disposables.add(next); @@ -962,7 +962,7 @@ export class DirtyDiffModel extends Disposable { private repositoryDisposables = new Set(); private readonly originalModelDisposables = this._register(new DisposableStore()); - private _onDidChange = new Emitter[]>(); + private readonly _onDidChange = new Emitter[]>(); readonly onDidChange: Event[]> = this._onDidChange.event; private _changes: IChange[] = []; diff --git a/src/vs/workbench/contrib/scm/browser/mainPanel.ts b/src/vs/workbench/contrib/scm/browser/mainPanel.ts new file mode 100644 index 0000000000..2dbb63f3b0 --- /dev/null +++ b/src/vs/workbench/contrib/scm/browser/mainPanel.ts @@ -0,0 +1,329 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./media/scmViewlet'; +import { localize } from 'vs/nls'; +import { Event, Emitter } from 'vs/base/common/event'; +import { basename } from 'vs/base/common/resources'; +import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; +import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { append, $, toggleClass } from 'vs/base/browser/dom'; +import { List } from 'vs/base/browser/ui/list/listWidget'; +import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; +import { ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; +import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IAction, Action } from 'vs/base/common/actions'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; +import { Command } from 'vs/editor/common/modes'; +import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; +import { WorkbenchList } from 'vs/platform/list/browser/listService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IViewDescriptor } from 'vs/workbench/common/views'; + +export interface ISpliceEvent { + index: number; + deleteCount: number; + elements: T[]; +} + +export interface IViewModel { + readonly repositories: ISCMRepository[]; + readonly onDidSplice: Event>; + + readonly visibleRepositories: ISCMRepository[]; + readonly onDidChangeVisibleRepositories: Event; + setVisibleRepositories(repositories: ISCMRepository[]): void; + + isVisible(): boolean; + readonly onDidChangeVisibility: Event; +} + +class ProvidersListDelegate implements IListVirtualDelegate { + + getHeight(): number { + return 22; + } + + getTemplateId(): string { + return 'provider'; + } +} + +class StatusBarAction extends Action { + + constructor( + private command: Command, + private commandService: ICommandService + ) { + super(`statusbaraction{${command.id}}`, command.title, '', true); + this.tooltip = command.tooltip || ''; + } + + run(): Promise { + return this.commandService.executeCommand(this.command.id, ...(this.command.arguments || [])); + } +} + +class StatusBarActionViewItem extends ActionViewItem { + + constructor(action: StatusBarAction) { + super(null, action, {}); + } + + updateLabel(): void { + if (this.options.label) { + this.label.innerHTML = renderOcticons(this.getAction().label); + } + } +} + +interface RepositoryTemplateData { + title: HTMLElement; + type: HTMLElement; + countContainer: HTMLElement; + count: CountBadge; + actionBar: ActionBar; + disposable: IDisposable; + templateDisposable: IDisposable; +} + +class ProviderRenderer implements IListRenderer { + + readonly templateId = 'provider'; + + private readonly _onDidRenderElement = new Emitter(); + readonly onDidRenderElement = this._onDidRenderElement.event; + + constructor( + @ICommandService protected commandService: ICommandService, + @IThemeService protected themeService: IThemeService + ) { } + + renderTemplate(container: HTMLElement): RepositoryTemplateData { + const provider = append(container, $('.scm-provider')); + const name = append(provider, $('.name')); + const title = append(name, $('span.title')); + const type = append(name, $('span.type')); + const countContainer = append(provider, $('.count')); + const count = new CountBadge(countContainer); + const badgeStyler = attachBadgeStyler(count, this.themeService); + const actionBar = new ActionBar(provider, { actionViewItemProvider: a => new StatusBarActionViewItem(a as StatusBarAction) }); + const disposable = Disposable.None; + const templateDisposable = combinedDisposable(actionBar, badgeStyler); + + return { title, type, countContainer, count, actionBar, disposable, templateDisposable }; + } + + renderElement(repository: ISCMRepository, index: number, templateData: RepositoryTemplateData): void { + templateData.disposable.dispose(); + const disposables = new DisposableStore(); + + if (repository.provider.rootUri) { + templateData.title.textContent = basename(repository.provider.rootUri); + templateData.type.textContent = repository.provider.label; + } else { + templateData.title.textContent = repository.provider.label; + templateData.type.textContent = ''; + } + + const actions: IAction[] = []; + const disposeActions = () => dispose(actions); + disposables.add({ dispose: disposeActions }); + + const update = () => { + disposeActions(); + + const commands = repository.provider.statusBarCommands || []; + actions.splice(0, actions.length, ...commands.map(c => new StatusBarAction(c, this.commandService))); + templateData.actionBar.clear(); + templateData.actionBar.push(actions); + + const count = repository.provider.count || 0; + toggleClass(templateData.countContainer, 'hidden', count === 0); + templateData.count.setCount(count); + + this._onDidRenderElement.fire(repository); + }; + + disposables.add(repository.provider.onDidChange(update, null)); + update(); + + templateData.disposable = disposables; + } + + disposeTemplate(templateData: RepositoryTemplateData): void { + templateData.disposable.dispose(); + templateData.templateDisposable.dispose(); + } +} + +export class MainPanel extends ViewletPanel { + + static readonly ID = 'scm.mainPanel'; + static readonly TITLE = localize('scm providers', "Source Control Providers"); + + private list: List; + + constructor( + protected viewModel: IViewModel, + options: IViewletPanelOptions, + @IKeybindingService protected keybindingService: IKeybindingService, + @IContextMenuService protected contextMenuService: IContextMenuService, + @ISCMService protected scmService: ISCMService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IMenuService private readonly menuService: IMenuService, + @IConfigurationService configurationService: IConfigurationService + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService); + } + + protected renderBody(container: HTMLElement): void { + const delegate = new ProvidersListDelegate(); + const renderer = this.instantiationService.createInstance(ProviderRenderer); + const identityProvider = { getId: (r: ISCMRepository) => r.provider.id }; + + this.list = this.instantiationService.createInstance(WorkbenchList, `SCM Main`, container, delegate, [renderer], { + identityProvider, + horizontalScrolling: false + }); + + this._register(renderer.onDidRenderElement(e => this.list.updateWidth(this.viewModel.repositories.indexOf(e)), null)); + this._register(this.list.onSelectionChange(this.onListSelectionChange, this)); + this._register(this.list.onFocusChange(this.onListFocusChange, this)); + this._register(this.list.onContextMenu(this.onListContextMenu, this)); + + this._register(this.viewModel.onDidChangeVisibleRepositories(this.updateListSelection, this)); + + this._register(this.viewModel.onDidSplice(({ index, deleteCount, elements }) => this.splice(index, deleteCount, elements), null)); + this.splice(0, 0, this.viewModel.repositories); + + this._register(this.list); + + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('scm.providers.visible')) { + this.updateBodySize(); + } + })); + + this.updateListSelection(); + } + + private splice(index: number, deleteCount: number, repositories: ISCMRepository[] = []): void { + this.list.splice(index, deleteCount, repositories); + + const empty = this.list.length === 0; + toggleClass(this.element, 'empty', empty); + + this.updateBodySize(); + } + + protected layoutBody(height: number, width: number): void { + this.list.layout(height, width); + } + + private updateBodySize(): void { + const visibleCount = this.configurationService.getValue('scm.providers.visible'); + const empty = this.list.length === 0; + const size = Math.min(this.viewModel.repositories.length, visibleCount) * 22; + + this.minimumBodySize = visibleCount === 0 ? 22 : size; + this.maximumBodySize = visibleCount === 0 ? Number.POSITIVE_INFINITY : empty ? Number.POSITIVE_INFINITY : size; + } + + private onListContextMenu(e: IListContextMenuEvent): void { + if (!e.element) { + return; + } + + const repository = e.element; + const contextKeyService = this.contextKeyService.createScoped(); + const scmProviderKey = contextKeyService.createKey('scmProvider', undefined); + scmProviderKey.set(repository.provider.contextValue); + + const menu = this.menuService.createMenu(MenuId.SCMSourceControl, contextKeyService); + const primary: IAction[] = []; + const secondary: IAction[] = []; + const result = { primary, secondary }; + + const disposable = createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => g === 'inline'); + + menu.dispose(); + contextKeyService.dispose(); + + if (secondary.length === 0) { + return; + } + + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => secondary, + getActionsContext: () => repository.provider + }); + + disposable.dispose(); + } + + private onListSelectionChange(e: IListEvent): void { + if (e.browserEvent && e.elements.length > 0) { + const scrollTop = this.list.scrollTop; + this.viewModel.setVisibleRepositories(e.elements); + this.list.scrollTop = scrollTop; + } + } + + private onListFocusChange(e: IListEvent): void { + if (e.browserEvent && e.elements.length > 0) { + e.elements[0].focus(); + } + } + + private updateListSelection(): void { + const set = new Set(); + + for (const repository of this.viewModel.visibleRepositories) { + set.add(repository); + } + + const selection: number[] = []; + + for (let i = 0; i < this.list.length; i++) { + if (set.has(this.list.element(i))) { + selection.push(i); + } + } + + this.list.setSelection(selection); + + if (selection.length > 0) { + this.list.setFocus([selection[0]]); + } + } +} + +export class MainPanelDescriptor implements IViewDescriptor { + + readonly id = MainPanel.ID; + readonly name = MainPanel.TITLE; + readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly canToggleVisibility = true; + readonly hideByDefault = false; + readonly order = -1000; + readonly workspace = true; + readonly when = ContextKeyExpr.or(ContextKeyExpr.equals('config.scm.alwaysShowProviders', true), ContextKeyExpr.and(ContextKeyExpr.notEquals('scm.providerCount', 0), ContextKeyExpr.notEquals('scm.providerCount', 1))); + + constructor(viewModel: IViewModel) { + this.ctorDescriptor = { ctor: MainPanel, arguments: [viewModel] }; + } +} diff --git a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css index df56fd8d97..c6673e31db 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -33,6 +33,7 @@ align-items: center; flex-wrap: wrap; height: 100%; + padding: 0 12px 0 20px; } .scm-viewlet .monaco-list-row > .scm-provider > .monaco-action-bar { @@ -60,16 +61,16 @@ } .scm-viewlet .monaco-list-row { - padding: 0 12px 0 20px; line-height: 22px; } -.scm-viewlet .monaco-list-row > .resource-group { +.scm-viewlet .monaco-list-row .resource-group { display: flex; height: 100%; + align-items: center; } -.scm-viewlet .monaco-list-row > .resource-group > .name { +.scm-viewlet .monaco-list-row .resource-group > .name { flex: 1; font-size: 11px; font-weight: bold; @@ -77,61 +78,66 @@ text-overflow: ellipsis; } -.scm-viewlet .monaco-list-row > .resource { +.scm-viewlet .monaco-list-row .resource { display: flex; height: 100%; } -.scm-viewlet .monaco-list-row > .resource.faded { +.scm-viewlet .monaco-list-row .resource.faded { opacity: 0.7; } -.scm-viewlet .monaco-list-row > .resource > .name { +.scm-viewlet .monaco-list-row .resource > .name { flex: 1; overflow: hidden; } -.scm-viewlet .monaco-list-row > .resource > .name.strike-through > .monaco-icon-label > .monaco-icon-label-description-container > .label-name { +.scm-viewlet .monaco-list-row .resource > .name.strike-through > .monaco-icon-label > .monaco-icon-label-description-container > .label-name { text-decoration: line-through; } -.scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label::after { - padding: 0 4px; +.scm-viewlet .monaco-list-row .resource > .name > .monaco-icon-label::after { + padding: 0 8px; } -.scm-viewlet .monaco-list-row > .resource > .decoration-icon { +.scm-viewlet .monaco-list-row .resource-group > .count { + padding: 0 8px; + display: flex; +} + +.scm-viewlet .monaco-list-row .resource > .decoration-icon { width: 16px; height: 100%; background-repeat: no-repeat; background-position: 50% 50%; } -.scm-viewlet .monaco-list .monaco-list-row > .resource > .name > .monaco-icon-label > .actions { +.scm-viewlet .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { flex-grow: 100; } -.scm-viewlet .monaco-list .monaco-list-row > .resource-group > .actions, -.scm-viewlet .monaco-list .monaco-list-row > .resource > .name > .monaco-icon-label > .actions { +.scm-viewlet .monaco-list .monaco-list-row .resource-group > .actions, +.scm-viewlet .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { display: none; } -.scm-viewlet .monaco-list .monaco-list-row:hover > .resource-group > .actions, -.scm-viewlet .monaco-list .monaco-list-row:hover > .resource > .name > .monaco-icon-label > .actions, -.scm-viewlet .monaco-list .monaco-list-row.selected > .resource-group > .actions, -.scm-viewlet .monaco-list .monaco-list-row.focused > .resource-group > .actions, -.scm-viewlet .monaco-list .monaco-list-row.selected > .resource > .name > .monaco-icon-label > .actions, -.scm-viewlet .monaco-list .monaco-list-row.focused > .resource > .name > .monaco-icon-label > .actions, -.scm-viewlet .monaco-list:not(.selection-multiple) .monaco-list-row > .resource:hover > .actions { +.scm-viewlet .monaco-list .monaco-list-row:hover .resource-group > .actions, +.scm-viewlet .monaco-list .monaco-list-row:hover .resource > .name > .monaco-icon-label > .actions, +.scm-viewlet .monaco-list .monaco-list-row.selected .resource-group > .actions, +.scm-viewlet .monaco-list .monaco-list-row.focused .resource-group > .actions, +.scm-viewlet .monaco-list .monaco-list-row.selected .resource > .name > .monaco-icon-label > .actions, +.scm-viewlet .monaco-list .monaco-list-row.focused .resource > .name > .monaco-icon-label > .actions, +.scm-viewlet .monaco-list:not(.selection-multiple) .monaco-list-row .resource:hover > .actions { display: block; } -.scm-viewlet .scm-status.show-actions > .monaco-list .monaco-list-row > .resource-group > .actions, -.scm-viewlet .scm-status.show-actions > .monaco-list .monaco-list-row > .resource > .name > .monaco-icon-label > .actions { +.scm-viewlet .scm-status.show-actions > .monaco-list .monaco-list-row .resource-group > .actions, +.scm-viewlet .scm-status.show-actions > .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { display: block; } -.scm-viewlet .monaco-list-row > .resource > .name > .monaco-icon-label > .actions .action-label, -.scm-viewlet .monaco-list-row > .resource-group > .actions .action-label { +.scm-viewlet .monaco-list-row .resource > .name > .monaco-icon-label > .actions .action-label, +.scm-viewlet .monaco-list-row .resource-group > .actions .action-label { width: 16px; height: 100%; background-position: 50% 50%; @@ -162,3 +168,9 @@ .scm-viewlet .scm-editor.scroll > .monaco-inputbox > .wrapper > textarea.input { overflow-y: scroll; } + +.scm-viewlet .list-view-mode .monaco-tl-twistie:not(.force-twistie):not(.collapsible) { + background-image: none !important; + width: 8px !important; + margin-right: 0 !important; +} diff --git a/src/vs/workbench/contrib/scm/browser/scmMenus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts similarity index 83% rename from src/vs/workbench/contrib/scm/browser/scmMenus.ts rename to src/vs/workbench/contrib/scm/browser/menus.ts index c9eaeb0a82..44ab7feea7 100644 --- a/src/vs/workbench/contrib/scm/browser/scmMenus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -5,13 +5,13 @@ import 'vs/css!./media/scmViewlet'; import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { IAction } from 'vs/base/common/actions'; import { createAndFillInContextMenuActions, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { ISCMProvider, ISCMResource, ISCMResourceGroup } from 'vs/workbench/contrib/scm/common/scm'; -import { isSCMResource } from './scmUtil'; +import { isSCMResource } from './util'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { equals } from 'vs/base/common/arrays'; import { ISplice } from 'vs/base/common/sequence'; @@ -20,13 +20,15 @@ function actionEquals(a: IAction, b: IAction): boolean { return a.id === b.id; } -interface ISCMResourceGroupMenuEntry extends IDisposable { +interface ISCMResourceGroupMenuEntry { readonly group: ISCMResourceGroup; + readonly disposable: IDisposable; } interface ISCMMenus { readonly resourceGroupMenu: IMenu; readonly resourceMenu: IMenu; + readonly resourceFolderMenu: IMenu; } export function getSCMResourceContextKey(resource: ISCMResourceGroup | ISCMResource): string { @@ -48,7 +50,7 @@ export class SCMMenus implements IDisposable { private readonly resourceGroupMenuEntries: ISCMResourceGroupMenuEntry[] = []; private readonly resourceGroupMenus = new Map(); - private readonly disposables: IDisposable[] = []; + private readonly disposables = new DisposableStore(); constructor( provider: ISCMProvider | undefined, @@ -68,7 +70,7 @@ export class SCMMenus implements IDisposable { } this.titleMenu = this.menuService.createMenu(MenuId.SCMTitle, this.contextKeyService); - this.disposables.push(this.titleMenu); + this.disposables.add(this.titleMenu); this.titleMenu.onDidChange(this.updateTitleActions, this, this.disposables); this.updateTitleActions(); @@ -109,6 +111,10 @@ export class SCMMenus implements IDisposable { return this.getActions(MenuId.SCMResourceContext, resource).secondary; } + getResourceFolderContextActions(group: ISCMResourceGroup): IAction[] { + return this.getActions(MenuId.SCMResourceFolderContext, group).secondary; + } + private getActions(menuId: MenuId, resource: ISCMResourceGroup | ISCMResource): { primary: IAction[]; secondary: IAction[]; } { const contextKeyService = this.contextKeyService.createScoped(); contextKeyService.createKey('scmResourceGroup', getSCMResourceContextKey(resource)); @@ -141,6 +147,14 @@ export class SCMMenus implements IDisposable { return this.resourceGroupMenus.get(group)!.resourceMenu; } + getResourceFolderMenu(group: ISCMResourceGroup): IMenu { + if (!this.resourceGroupMenus.has(group)) { + throw new Error('SCM Resource Group menu not found'); + } + + return this.resourceGroupMenus.get(group)!.resourceFolderMenu; + } + private onDidSpliceGroups({ start, deleteCount, toInsert }: ISplice): void { const menuEntriesToInsert = toInsert.map(group => { const contextKeyService = this.contextKeyService.createScoped(); @@ -149,30 +163,23 @@ export class SCMMenus implements IDisposable { const resourceGroupMenu = this.menuService.createMenu(MenuId.SCMResourceGroupContext, contextKeyService); const resourceMenu = this.menuService.createMenu(MenuId.SCMResourceContext, contextKeyService); + const resourceFolderMenu = this.menuService.createMenu(MenuId.SCMResourceFolderContext, contextKeyService); + const disposable = combinedDisposable(contextKeyService, resourceGroupMenu, resourceMenu, resourceFolderMenu); - this.resourceGroupMenus.set(group, { resourceGroupMenu, resourceMenu }); - - return { - group, - dispose() { - contextKeyService.dispose(); - resourceGroupMenu.dispose(); - resourceMenu.dispose(); - } - }; + this.resourceGroupMenus.set(group, { resourceGroupMenu, resourceMenu, resourceFolderMenu }); + return { group, disposable }; }); const deleted = this.resourceGroupMenuEntries.splice(start, deleteCount, ...menuEntriesToInsert); for (const entry of deleted) { this.resourceGroupMenus.delete(entry.group); - entry.dispose(); + entry.disposable.dispose(); } } dispose(): void { - dispose(this.disposables); - dispose(this.resourceGroupMenuEntries); - this.resourceGroupMenus.clear(); + this.disposables.dispose(); + this.resourceGroupMenuEntries.forEach(e => e.disposable.dispose()); } } diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts b/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts new file mode 100644 index 0000000000..f44d3052c5 --- /dev/null +++ b/src/vs/workbench/contrib/scm/browser/repositoryPanel.ts @@ -0,0 +1,864 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./media/scmViewlet'; +import { Event, Emitter } from 'vs/base/common/event'; +import { domEvent } from 'vs/base/browser/event'; +import { basename } from 'vs/base/common/resources'; +import { IDisposable, Disposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; +import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { append, $, addClass, toggleClass, trackFocus, removeClass } from 'vs/base/browser/dom'; +import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; +import { ISCMRepository, ISCMResourceGroup, ISCMResource, InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; +import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; +import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { MenuItemAction, IMenuService } from 'vs/platform/actions/common/actions'; +import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/actions'; +import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { SCMMenus } from './menus'; +import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; +import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar } from './util'; +import { attachBadgeStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; +import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; +import { format } from 'vs/base/common/strings'; +import { WorkbenchCompressibleObjectTree } from 'vs/platform/list/browser/listService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ThrottledDelayer } from 'vs/base/common/async'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import * as platform from 'vs/base/common/platform'; +import { ITreeNode, ITreeFilter, ITreeSorter, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; +import { ISequence, ISplice } from 'vs/base/common/sequence'; +import { ResourceTree, IBranchNode, INode } from 'vs/base/common/resourceTree'; +import { ObjectTree, ICompressibleTreeRenderer, ICompressibleKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/tree/objectTree'; +import { Iterator } from 'vs/base/common/iterator'; +import { ICompressedTreeNode, ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; +import { URI } from 'vs/base/common/uri'; +import { FileKind } from 'vs/platform/files/common/files'; +import { compareFileNames } from 'vs/base/common/comparers'; +import { FuzzyScore, createMatches } from 'vs/base/common/filters'; +import { IViewDescriptor } from 'vs/workbench/common/views'; +import { localize } from 'vs/nls'; +import { flatten } from 'vs/base/common/arrays'; +import { memoize } from 'vs/base/common/decorators'; +import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; + +type TreeElement = ISCMResourceGroup | IBranchNode | ISCMResource; + +interface ResourceGroupTemplate { + readonly name: HTMLElement; + readonly count: CountBadge; + readonly actionBar: ActionBar; + elementDisposables: IDisposable; + readonly disposables: IDisposable; +} + +class ResourceGroupRenderer implements ICompressibleTreeRenderer { + + static TEMPLATE_ID = 'resource group'; + get templateId(): string { return ResourceGroupRenderer.TEMPLATE_ID; } + + constructor( + private actionViewItemProvider: IActionViewItemProvider, + private themeService: IThemeService, + private menus: SCMMenus + ) { } + + renderTemplate(container: HTMLElement): ResourceGroupTemplate { + // hack + addClass(container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement, 'force-twistie'); + + const element = append(container, $('.resource-group')); + const name = append(element, $('.name')); + const actionsContainer = append(element, $('.actions')); + const actionBar = new ActionBar(actionsContainer, { actionViewItemProvider: this.actionViewItemProvider }); + const countContainer = append(element, $('.count')); + const count = new CountBadge(countContainer); + const styler = attachBadgeStyler(count, this.themeService); + const elementDisposables = Disposable.None; + const disposables = combinedDisposable(actionBar, styler); + + return { name, count, actionBar, elementDisposables, disposables }; + } + + renderElement(node: ITreeNode, index: number, template: ResourceGroupTemplate): void { + template.elementDisposables.dispose(); + + const group = node.element; + template.name.textContent = group.label; + template.actionBar.clear(); + template.actionBar.context = group; + template.count.setCount(group.elements.length); + + const disposables = new DisposableStore(); + disposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceGroupMenu(group), template.actionBar)); + + template.elementDisposables = disposables; + } + + renderCompressedElements(node: ITreeNode, FuzzyScore>, index: number, templateData: ResourceGroupTemplate, height: number | undefined): void { + throw new Error('Should never happen since node is incompressible'); + } + + disposeElement(group: ITreeNode, index: number, template: ResourceGroupTemplate): void { + template.elementDisposables.dispose(); + } + + disposeTemplate(template: ResourceGroupTemplate): void { + template.elementDisposables.dispose(); + template.disposables.dispose(); + } +} + +interface ResourceTemplate { + element: HTMLElement; + name: HTMLElement; + fileLabel: IResourceLabel; + decorationIcon: HTMLElement; + actionBar: ActionBar; + elementDisposables: IDisposable; + disposables: IDisposable; +} + +class MultipleSelectionActionRunner extends ActionRunner { + + constructor(private getSelectedResources: () => (ISCMResource | IBranchNode)[]) { + super(); + } + + runAction(action: IAction, context: ISCMResource | IBranchNode): Promise { + if (!(action instanceof MenuItemAction)) { + return super.runAction(action, context); + } + + const selection = this.getSelectedResources(); + const contextIsSelected = selection.some(s => s === context); + const actualContext = contextIsSelected ? selection : [context]; + const args = flatten(actualContext.map(e => ResourceTree.isBranchNode(e) ? ResourceTree.collect(e) : [e])); + return action.run(...args); + } +} + +class ResourceRenderer implements ICompressibleTreeRenderer, FuzzyScore, ResourceTemplate> { + + static TEMPLATE_ID = 'resource'; + get templateId(): string { return ResourceRenderer.TEMPLATE_ID; } + + constructor( + private viewModelProvider: () => ViewModel, + private labels: ResourceLabels, + private actionViewItemProvider: IActionViewItemProvider, + private getSelectedResources: () => (ISCMResource | IBranchNode)[], + private themeService: IThemeService, + private menus: SCMMenus + ) { } + + renderTemplate(container: HTMLElement): ResourceTemplate { + const element = append(container, $('.resource')); + const name = append(element, $('.name')); + const fileLabel = this.labels.create(name, { supportHighlights: true }); + const actionsContainer = append(fileLabel.element, $('.actions')); + const actionBar = new ActionBar(actionsContainer, { + actionViewItemProvider: this.actionViewItemProvider, + actionRunner: new MultipleSelectionActionRunner(this.getSelectedResources) + }); + + const decorationIcon = append(element, $('.decoration-icon')); + const disposables = combinedDisposable(actionBar, fileLabel); + + return { element, name, fileLabel, decorationIcon, actionBar, elementDisposables: Disposable.None, disposables }; + } + + renderElement(node: ITreeNode | ITreeNode, FuzzyScore>, index: number, template: ResourceTemplate): void { + template.elementDisposables.dispose(); + + const elementDisposables = new DisposableStore(); + const resourceOrFolder = node.element; + const theme = this.themeService.getTheme(); + const icon = !ResourceTree.isBranchNode(resourceOrFolder) && (theme.type === LIGHT ? resourceOrFolder.decorations.icon : resourceOrFolder.decorations.iconDark); + + const uri = ResourceTree.isBranchNode(resourceOrFolder) ? resourceOrFolder.uri : resourceOrFolder.sourceUri; + const fileKind = ResourceTree.isBranchNode(resourceOrFolder) ? FileKind.FOLDER : FileKind.FILE; + const viewModel = this.viewModelProvider(); + + template.fileLabel.setFile(uri, { + fileDecorations: { colors: false, badges: !icon }, + hidePath: viewModel.mode === ViewModelMode.Tree, + fileKind, + matches: createMatches(node.filterData) + }); + + template.actionBar.clear(); + template.actionBar.context = resourceOrFolder; + + if (ResourceTree.isBranchNode(resourceOrFolder)) { + elementDisposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceFolderMenu(resourceOrFolder.context), template.actionBar)); + removeClass(template.name, 'strike-through'); + removeClass(template.element, 'faded'); + } else { + elementDisposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceMenu(resourceOrFolder.resourceGroup), template.actionBar)); + toggleClass(template.name, 'strike-through', resourceOrFolder.decorations.strikeThrough); + toggleClass(template.element, 'faded', resourceOrFolder.decorations.faded); + } + + const tooltip = !ResourceTree.isBranchNode(resourceOrFolder) && resourceOrFolder.decorations.tooltip || ''; + + if (icon) { + template.decorationIcon.style.display = ''; + template.decorationIcon.style.backgroundImage = `url('${icon}')`; + template.decorationIcon.title = tooltip; + } else { + template.decorationIcon.style.display = 'none'; + template.decorationIcon.style.backgroundImage = ''; + template.decorationIcon.title = ''; + } + + template.element.setAttribute('data-tooltip', tooltip); + template.elementDisposables = elementDisposables; + } + + disposeElement(resource: ITreeNode | ITreeNode, FuzzyScore>, index: number, template: ResourceTemplate): void { + template.elementDisposables.dispose(); + } + + renderCompressedElements(node: ITreeNode | ICompressedTreeNode>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void { + template.elementDisposables.dispose(); + + const elementDisposables = new DisposableStore(); + const compressed = node.element as ICompressedTreeNode>; + const folder = compressed.elements[compressed.elements.length - 1]; + + const label = compressed.elements.map(e => e.name).join('/'); + const fileKind = FileKind.FOLDER; + + template.fileLabel.setResource({ resource: folder.uri, name: label }, { + fileDecorations: { colors: false, badges: true }, + fileKind, + matches: createMatches(node.filterData) + }); + + template.actionBar.clear(); + template.actionBar.context = folder; + + elementDisposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceFolderMenu(folder.context), template.actionBar)); + + removeClass(template.name, 'strike-through'); + removeClass(template.element, 'faded'); + template.decorationIcon.style.display = 'none'; + template.decorationIcon.style.backgroundImage = ''; + + template.element.setAttribute('data-tooltip', ''); + template.elementDisposables = elementDisposables; + } + + disposeCompressedElements(node: ITreeNode | ICompressedTreeNode>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void { + template.elementDisposables.dispose(); + } + + disposeTemplate(template: ResourceTemplate): void { + template.elementDisposables.dispose(); + template.disposables.dispose(); + } +} + +class ProviderListDelegate implements IListVirtualDelegate { + + getHeight() { return 22; } + + getTemplateId(element: TreeElement) { + if (ResourceTree.isBranchNode(element) || isSCMResource(element)) { + return ResourceRenderer.TEMPLATE_ID; + } else { + return ResourceGroupRenderer.TEMPLATE_ID; + } + } +} + +class SCMTreeFilter implements ITreeFilter { + + filter(element: TreeElement): boolean { + if (ResourceTree.isBranchNode(element)) { + return true; + } else if (isSCMResourceGroup(element)) { + return element.elements.length > 0 || !element.hideWhenEmpty; + } else { + return true; + } + } +} + +export class SCMTreeSorter implements ITreeSorter { + + @memoize + private get viewModel(): ViewModel { return this.viewModelProvider(); } + + constructor(private viewModelProvider: () => ViewModel) { } + + compare(one: TreeElement, other: TreeElement): number { + if (this.viewModel.mode === ViewModelMode.List) { + return 0; + } + + if (isSCMResourceGroup(one) && isSCMResourceGroup(other)) { + return 0; + } + + const oneIsDirectory = ResourceTree.isBranchNode(one); + const otherIsDirectory = ResourceTree.isBranchNode(other); + + if (oneIsDirectory !== otherIsDirectory) { + return oneIsDirectory ? -1 : 1; + } + + const oneName = ResourceTree.isBranchNode(one) ? one.name : basename((one as ISCMResource).sourceUri); + const otherName = ResourceTree.isBranchNode(other) ? other.name : basename((other as ISCMResource).sourceUri); + + return compareFileNames(oneName, otherName); + } +} + +export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyboardNavigationLabelProvider { + + getKeyboardNavigationLabel(element: TreeElement): { toString(): string; } | undefined { + if (ResourceTree.isBranchNode(element)) { + return element.name; + } else if (isSCMResourceGroup(element)) { + return element.label; + } else { + return basename(element.sourceUri); + } + } + + getCompressedNodeKeyboardNavigationLabel(elements: TreeElement[]): { toString(): string | undefined; } | undefined { + const folders = elements as IBranchNode[]; + return folders.map(e => e.name).join('/'); + } +} + +class SCMResourceIdentityProvider implements IIdentityProvider { + + getId(element: TreeElement): string { + if (ResourceTree.isBranchNode(element)) { + const group = element.context; + return `${group.provider.contextValue}/${group.id}/$FOLDER/${element.uri.toString()}`; + } else if (isSCMResource(element)) { + const group = element.resourceGroup; + const provider = group.provider; + return `${provider.contextValue}/${group.id}/${element.sourceUri.toString()}`; + } else { + const provider = element.provider; + return `${provider.contextValue}/${element.id}`; + } + } +} + +interface IGroupItem { + readonly group: ISCMResourceGroup; + readonly resources: ISCMResource[]; + readonly tree: ResourceTree; + readonly disposable: IDisposable; +} + +function groupItemAsTreeElement(item: IGroupItem, mode: ViewModelMode): ICompressedTreeElement { + const children = mode === ViewModelMode.List + ? Iterator.map(Iterator.fromArray(item.resources), element => ({ element, incompressible: true })) + : Iterator.map(item.tree.root.children, node => asTreeElement(node, true)); + + return { element: item.group, children, incompressible: true }; +} + +function asTreeElement(node: INode, incompressible: boolean): ICompressedTreeElement { + if (ResourceTree.isBranchNode(node)) { + return { + element: node, + children: Iterator.map(node.children, node => asTreeElement(node, false)), + incompressible, + collapsed: false + }; + } + + return { element: node.element, incompressible: true }; +} + +const enum ViewModelMode { + List = 'codicon-filter', + Tree = 'codicon-selection' +} + +class ViewModel { + + private _mode = ViewModelMode.Tree; + private readonly _onDidChangeMode = new Emitter(); + readonly onDidChangeMode = this._onDidChangeMode.event; + + get mode(): ViewModelMode { return this._mode; } + set mode(mode: ViewModelMode) { + this._mode = mode; + this.refresh(); + this._onDidChangeMode.fire(mode); + } + + private items: IGroupItem[] = []; + private visibilityDisposables = new DisposableStore(); + private scrollTop: number | undefined; + private disposables = new DisposableStore(); + + constructor( + private groups: ISequence, + private tree: ObjectTree + ) { } + + private onDidSpliceGroups({ start, deleteCount, toInsert }: ISplice): void { + const itemsToInsert: IGroupItem[] = []; + + for (const group of toInsert) { + const tree = new ResourceTree(group, group.provider.rootUri || URI.file('/')); + const resources: ISCMResource[] = [...group.elements]; + const disposable = combinedDisposable( + group.onDidChange(() => this.tree.refilter()), + group.onDidSplice(splice => this.onDidSpliceGroup(item, splice)) + ); + + const item = { group, resources, tree, disposable }; + + for (const resource of resources) { + item.tree.add(resource.sourceUri, resource); + } + + itemsToInsert.push(item); + } + + const itemsToDispose = this.items.splice(start, deleteCount, ...itemsToInsert); + + for (const item of itemsToDispose) { + item.disposable.dispose(); + } + + this.refresh(); + } + + private onDidSpliceGroup(item: IGroupItem, { start, deleteCount, toInsert }: ISplice): void { + for (const resource of toInsert) { + item.tree.add(resource.sourceUri, resource); + } + + const deleted = item.resources.splice(start, deleteCount, ...toInsert); + + for (const resource of deleted) { + item.tree.delete(resource.sourceUri); + } + + this.refresh(item); + } + + setVisible(visible: boolean): void { + if (visible) { + this.visibilityDisposables = new DisposableStore(); + this.groups.onDidSplice(this.onDidSpliceGroups, this, this.visibilityDisposables); + this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: this.groups.elements }); + + if (typeof this.scrollTop === 'number') { + this.tree.scrollTop = this.scrollTop; + this.scrollTop = undefined; + } + } else { + this.visibilityDisposables.dispose(); + this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: [] }); + this.scrollTop = this.tree.scrollTop; + } + } + + private refresh(item?: IGroupItem): void { + if (item) { + this.tree.setChildren(item.group, groupItemAsTreeElement(item, this.mode).children); + } else { + this.tree.setChildren(null, this.items.map(item => groupItemAsTreeElement(item, this.mode))); + } + } + + dispose(): void { + this.visibilityDisposables.dispose(); + this.disposables.dispose(); + } +} + +export class ToggleViewModeAction extends Action { + + static readonly ID = 'workbench.scm.action.toggleViewMode'; + static readonly LABEL = localize('toggleViewMode', "Toggle View Mode"); + + constructor(private viewModel: ViewModel) { + super(ToggleViewModeAction.ID, ToggleViewModeAction.LABEL); + + this._register(this.viewModel.onDidChangeMode(this.onDidChangeMode, this)); + this.onDidChangeMode(this.viewModel.mode); + } + + async run(): Promise { + this.viewModel.mode = this.viewModel.mode === ViewModelMode.List ? ViewModelMode.Tree : ViewModelMode.List; + } + + private onDidChangeMode(mode: ViewModelMode): void { + this.class = `scm-action toggle-view-mode ${mode}`; + } +} + +function convertValidationType(type: InputValidationType): MessageType { + switch (type) { + case InputValidationType.Information: return MessageType.INFO; + case InputValidationType.Warning: return MessageType.WARNING; + case InputValidationType.Error: return MessageType.ERROR; + } +} + +export class RepositoryPanel extends ViewletPanel { + + private cachedHeight: number | undefined = undefined; + private cachedWidth: number | undefined = undefined; + private inputBoxContainer: HTMLElement; + private inputBox: InputBox; + private listContainer: HTMLElement; + private tree: ObjectTree; + private viewModel: ViewModel; + private listLabels: ResourceLabels; + private menus: SCMMenus; + private toggleViewModelModeAction: ToggleViewModeAction | undefined; + protected contextKeyService: IContextKeyService; + + constructor( + readonly repository: ISCMRepository, + options: IViewletPanelOptions, + @IKeybindingService protected keybindingService: IKeybindingService, + @IWorkbenchThemeService protected themeService: IWorkbenchThemeService, + @IContextMenuService protected contextMenuService: IContextMenuService, + @IContextViewService protected contextViewService: IContextViewService, + @ICommandService protected commandService: ICommandService, + @INotificationService private readonly notificationService: INotificationService, + @IEditorService protected editorService: IEditorService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IConfigurationService protected configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IMenuService protected menuService: IMenuService + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService); + + this.menus = instantiationService.createInstance(SCMMenus, this.repository.provider); + this._register(this.menus); + this._register(this.menus.onDidChangeTitle(this._onDidChangeTitleArea.fire, this._onDidChangeTitleArea)); + + this.contextKeyService = contextKeyService.createScoped(this.element); + this.contextKeyService.createKey('scmRepository', this.repository); + } + + render(): void { + super.render(); + this._register(this.menus.onDidChangeTitle(this.updateActions, this)); + } + + protected renderHeaderTitle(container: HTMLElement): void { + let title: string; + let type: string; + + if (this.repository.provider.rootUri) { + title = basename(this.repository.provider.rootUri); + type = this.repository.provider.label; + } else { + title = this.repository.provider.label; + type = ''; + } + + super.renderHeaderTitle(container, title); + addClass(container, 'scm-provider'); + append(container, $('span.type', undefined, type)); + } + + protected renderBody(container: HTMLElement): void { + const focusTracker = trackFocus(container); + this._register(focusTracker.onDidFocus(() => this.repository.focus())); + this._register(focusTracker); + + // Input + this.inputBoxContainer = append(container, $('.scm-editor')); + + const updatePlaceholder = () => { + const binding = this.keybindingService.lookupKeybinding('scm.acceptInput'); + const label = binding ? binding.getLabel() : (platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter'); + const placeholder = format(this.repository.input.placeholder, label); + + this.inputBox.setPlaceHolder(placeholder); + }; + + const validationDelayer = new ThrottledDelayer(200); + const validate = () => { + return this.repository.input.validateInput(this.inputBox.value, this.inputBox.inputElement.selectionStart || 0).then(result => { + if (!result) { + this.inputBox.inputElement.removeAttribute('aria-invalid'); + this.inputBox.hideMessage(); + } else { + this.inputBox.inputElement.setAttribute('aria-invalid', 'true'); + this.inputBox.showMessage({ content: result.message, type: convertValidationType(result.type) }); + } + }); + }; + + const triggerValidation = () => validationDelayer.trigger(validate); + + this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { flexibleHeight: true, flexibleMaxHeight: 134 }); + this.inputBox.setEnabled(this.isBodyVisible()); + this._register(attachInputBoxStyler(this.inputBox, this.themeService)); + this._register(this.inputBox); + + this._register(this.inputBox.onDidChange(triggerValidation, null)); + + const onKeyUp = domEvent(this.inputBox.inputElement, 'keyup'); + const onMouseUp = domEvent(this.inputBox.inputElement, 'mouseup'); + this._register(Event.any(onKeyUp, onMouseUp)(triggerValidation, null)); + + this.inputBox.value = this.repository.input.value; + this._register(this.inputBox.onDidChange(value => this.repository.input.value = value, null)); + this._register(this.repository.input.onDidChange(value => this.inputBox.value = value, null)); + + updatePlaceholder(); + this._register(this.repository.input.onDidChangePlaceholder(updatePlaceholder, null)); + this._register(this.keybindingService.onDidUpdateKeybindings(updatePlaceholder, null)); + + this._register(this.inputBox.onDidHeightChange(() => this.layoutBody())); + + if (this.repository.provider.onDidChangeCommitTemplate) { + this._register(this.repository.provider.onDidChangeCommitTemplate(this.updateInputBox, this)); + } + + this.updateInputBox(); + + // Input box visibility + this._register(this.repository.input.onDidChangeVisibility(this.updateInputBoxVisibility, this)); + this.updateInputBoxVisibility(); + + // List + this.listContainer = append(container, $('.scm-status.show-file-icons')); + + const updateActionsVisibility = () => toggleClass(this.listContainer, 'show-actions', this.configurationService.getValue('scm.alwaysShowActions')); + Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowActions'))(updateActionsVisibility); + updateActionsVisibility(); + + const delegate = new ProviderListDelegate(); + + const actionViewItemProvider = (action: IAction) => this.getActionViewItem(action); + + this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); + this._register(this.listLabels); + + const renderers = [ + new ResourceGroupRenderer(actionViewItemProvider, this.themeService, this.menus), + new ResourceRenderer(() => this.viewModel, this.listLabels, actionViewItemProvider, () => this.getSelectedResources(), this.themeService, this.menus) + ]; + + const filter = new SCMTreeFilter(); + const sorter = new SCMTreeSorter(() => this.viewModel); + const keyboardNavigationLabelProvider = new SCMTreeKeyboardNavigationLabelProvider(); + const identityProvider = new SCMResourceIdentityProvider(); + + this.tree = this.instantiationService.createInstance( + WorkbenchCompressibleObjectTree, + 'SCM Tree Repo', + this.listContainer, + delegate, + renderers, + { + identityProvider, + horizontalScrolling: false, + filter, + sorter, + keyboardNavigationLabelProvider + }); + + this._register(Event.chain(this.tree.onDidOpen) + .map(e => e.elements[0]) + .filter(e => !!e && !ResourceTree.isBranchNode(e) && isSCMResource(e)) + .on(this.open, this)); + + this._register(Event.chain(this.tree.onDidPin) + .map(e => e.elements[0]) + .filter(e => !!e && !ResourceTree.isBranchNode(e) && isSCMResource(e)) + .on(this.pin, this)); + + this._register(this.tree.onContextMenu(this.onListContextMenu, this)); + this._register(this.tree); + + this.viewModel = new ViewModel(this.repository.provider.groups, this.tree); + this._register(this.viewModel); + + addClass(this.listContainer, 'file-icon-themable-tree'); + addClass(this.listContainer, 'show-file-icons'); + + const updateIndentStyles = (theme: IFileIconTheme) => { + toggleClass(this.listContainer, 'list-view-mode', this.viewModel.mode === ViewModelMode.List); + toggleClass(this.listContainer, 'align-icons-and-twisties', this.viewModel.mode === ViewModelMode.Tree && theme.hasFileIcons && !theme.hasFolderIcons); + toggleClass(this.listContainer, 'hide-arrows', this.viewModel.mode === ViewModelMode.Tree && theme.hidesExplorerArrows === true); + }; + + updateIndentStyles(this.themeService.getFileIconTheme()); + this._register(this.themeService.onDidFileIconThemeChange(updateIndentStyles)); + this._register(this.viewModel.onDidChangeMode(() => updateIndentStyles(this.themeService.getFileIconTheme()))); + + this.toggleViewModelModeAction = new ToggleViewModeAction(this.viewModel); + this._register(this.toggleViewModelModeAction); + + this._register(this.onDidChangeBodyVisibility(this._onDidChangeVisibility, this)); + + this.updateActions(); + } + + layoutBody(height: number | undefined = this.cachedHeight, width: number | undefined = this.cachedWidth): void { + if (height === undefined) { + return; + } + + this.cachedHeight = height; + + if (this.repository.input.visible) { + removeClass(this.inputBoxContainer, 'hidden'); + this.inputBox.layout(); + + const editorHeight = this.inputBox.height; + const listHeight = height - (editorHeight + 12 /* margin */); + this.listContainer.style.height = `${listHeight}px`; + this.tree.layout(listHeight, width); + } else { + addClass(this.inputBoxContainer, 'hidden'); + + this.listContainer.style.height = `${height}px`; + this.tree.layout(height, width); + } + } + + focus(): void { + super.focus(); + + if (this.isExpanded()) { + if (this.repository.input.visible) { + this.inputBox.focus(); + } else { + this.tree.domFocus(); + } + + this.repository.focus(); + } + } + + private _onDidChangeVisibility(visible: boolean): void { + this.inputBox.setEnabled(visible); + this.viewModel.setVisible(visible); + } + + getActions(): IAction[] { + if (this.toggleViewModelModeAction) { + + return [ + this.toggleViewModelModeAction, + ...this.menus.getTitleActions() + ]; + } else { + return this.menus.getTitleActions(); + } + } + + getSecondaryActions(): IAction[] { + return this.menus.getTitleSecondaryActions(); + } + + getActionViewItem(action: IAction): IActionViewItem | undefined { + if (!(action instanceof MenuItemAction)) { + return undefined; + } + + return new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + } + + getActionsContext(): any { + return this.repository.provider; + } + + private open(e: ISCMResource): void { + e.open(); + } + + private pin(): void { + const activeControl = this.editorService.activeControl; + + if (activeControl) { + activeControl.group.pinEditor(activeControl.input); + } + } + + private onListContextMenu(e: ITreeContextMenuEvent): void { + if (!e.element) { + return; + } + + const element = e.element; + let actions: IAction[] = []; + + if (ResourceTree.isBranchNode(element)) { + actions = this.menus.getResourceFolderContextActions(element.context); + } else if (isSCMResource(element)) { + actions = this.menus.getResourceContextActions(element); + } else { + actions = this.menus.getResourceGroupContextActions(element); + } + + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => actions, + getActionsContext: () => element, + actionRunner: new MultipleSelectionActionRunner(() => this.getSelectedResources()) + }); + } + + private getSelectedResources(): (ISCMResource | IBranchNode)[] { + return this.tree.getSelection() + .filter(r => !!r && !isSCMResourceGroup(r))! as any; + } + + private updateInputBox(): void { + if (typeof this.repository.provider.commitTemplate === 'undefined' || !this.repository.input.visible || this.inputBox.value) { + return; + } + + this.inputBox.value = this.repository.provider.commitTemplate; + } + + private updateInputBoxVisibility(): void { + if (this.cachedHeight) { + this.layoutBody(this.cachedHeight); + } + } +} + +export class RepositoryViewDescriptor implements IViewDescriptor { + + private static counter = 0; + + readonly id: string; + readonly name: string; + readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly canToggleVisibility = true; + readonly order = -500; + readonly workspace = true; + + constructor(readonly repository: ISCMRepository, readonly hideByDefault: boolean) { + const repoId = repository.provider.rootUri ? repository.provider.rootUri.toString() : `#${RepositoryViewDescriptor.counter++}`; + this.id = `scm:repository:${repository.provider.label}:${repoId}`; + this.name = repository.provider.rootUri ? basename(repository.provider.rootUri) : repository.provider.label; + + this.ctorDescriptor = { ctor: RepositoryPanel, arguments: [repository] }; + } +} diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 54aaf2ab62..024e318f1d 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -13,7 +13,7 @@ import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } fro import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { SCMStatusController } from './scmActivity'; +import { SCMStatusController } from './activity'; import { SCMViewlet } from 'vs/workbench/contrib/scm/browser/scmViewlet'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 7732e6b4a0..8253c479fc 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -6,50 +6,31 @@ import 'vs/css!./media/scmViewlet'; import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -import { domEvent } from 'vs/base/browser/event'; -import { basename } from 'vs/base/common/resources'; -import { IDisposable, dispose, Disposable, DisposableStore, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; -import { append, $, addClass, toggleClass, trackFocus, removeClass, addClasses } from 'vs/base/browser/dom'; +import { append, $, toggleClass, addClasses } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { List } from 'vs/base/browser/ui/list/listWidget'; -import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent, IKeyboardNavigationLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list'; -import { VIEWLET_ID, ISCMService, ISCMRepository, ISCMResourceGroup, ISCMResource, InputValidationType, VIEW_CONTAINER } from 'vs/workbench/contrib/scm/common/scm'; -import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; -import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { VIEWLET_ID, ISCMService, ISCMRepository, VIEW_CONTAINER } from 'vs/workbench/contrib/scm/common/scm'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IContextKeyService, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { MenuItemAction, IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; -import { IAction, Action, IActionViewItem, ActionRunner } from 'vs/base/common/actions'; -import { createAndFillInContextMenuActions, ContextAwareMenuEntryActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { SCMMenus } from './scmMenus'; -import { ActionBar, IActionViewItemProvider, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; -import { isSCMResource } from './scmUtil'; -import { attachBadgeStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; +import { MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IAction, IActionViewItem } from 'vs/base/common/actions'; +import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { SCMMenus } from './menus'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; -import { Command } from 'vs/editor/common/modes'; -import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; -import { format } from 'vs/base/common/strings'; -import { ISpliceable, ISequence, ISplice } from 'vs/base/common/sequence'; -import { firstIndex, equals } from 'vs/base/common/arrays'; -import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ThrottledDelayer } from 'vs/base/common/async'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import * as platform from 'vs/base/common/platform'; import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IViewsRegistry, IViewDescriptor, Extensions } from 'vs/workbench/common/views'; +import { IViewsRegistry, Extensions } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { nextTick } from 'vs/base/common/process'; +import { RepositoryPanel, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPanel'; +import { MainPanelDescriptor, MainPanel } from 'vs/workbench/contrib/scm/browser/mainPanel'; export interface ISpliceEvent { index: number; @@ -69,977 +50,6 @@ export interface IViewModel { readonly onDidChangeVisibility: Event; } -class ProvidersListDelegate implements IListVirtualDelegate { - - getHeight(element: ISCMRepository): number { - return 22; - } - - getTemplateId(element: ISCMRepository): string { - return 'provider'; - } -} - -class StatusBarAction extends Action { - - constructor( - private command: Command, - private commandService: ICommandService - ) { - super(`statusbaraction{${command.id}}`, command.title, '', true); - this.tooltip = command.tooltip || ''; - } - - run(): Promise { - return this.commandService.executeCommand(this.command.id, ...(this.command.arguments || [])); - } -} - -class StatusBarActionViewItem extends ActionViewItem { - - constructor(action: StatusBarAction) { - super(null, action, {}); - } - - updateLabel(): void { - if (this.options.label) { - this.label.innerHTML = renderOcticons(this.getAction().label); - } - } -} - -function connectPrimaryMenuToInlineActionBar(menu: IMenu, actionBar: ActionBar): IDisposable { - let cachedDisposable: IDisposable = Disposable.None; - let cachedPrimary: IAction[] = []; - - const updateActions = () => { - const primary: IAction[] = []; - const secondary: IAction[] = []; - - const disposable = createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, { primary, secondary }, g => /^inline/.test(g)); - - if (equals(cachedPrimary, primary, (a, b) => a.id === b.id)) { - disposable.dispose(); - return; - } - - cachedDisposable = disposable; - cachedPrimary = primary; - - actionBar.clear(); - actionBar.push(primary, { icon: true, label: false }); - }; - - updateActions(); - - return combinedDisposable(menu.onDidChange(updateActions), toDisposable(() => { - cachedDisposable.dispose(); - })); -} - -interface RepositoryTemplateData { - title: HTMLElement; - type: HTMLElement; - countContainer: HTMLElement; - count: CountBadge; - actionBar: ActionBar; - disposable: IDisposable; - templateDisposable: IDisposable; -} - -class ProviderRenderer implements IListRenderer { - - readonly templateId = 'provider'; - - private _onDidRenderElement = new Emitter(); - readonly onDidRenderElement = this._onDidRenderElement.event; - - constructor( - @ICommandService protected commandService: ICommandService, - @IThemeService protected themeService: IThemeService - ) { } - - renderTemplate(container: HTMLElement): RepositoryTemplateData { - const provider = append(container, $('.scm-provider')); - const name = append(provider, $('.name')); - const title = append(name, $('span.title')); - const type = append(name, $('span.type')); - const countContainer = append(provider, $('.count')); - const count = new CountBadge(countContainer); - const badgeStyler = attachBadgeStyler(count, this.themeService); - const actionBar = new ActionBar(provider, { actionViewItemProvider: a => new StatusBarActionViewItem(a as StatusBarAction) }); - const disposable = Disposable.None; - const templateDisposable = combinedDisposable(actionBar, badgeStyler); - - return { title, type, countContainer, count, actionBar, disposable, templateDisposable }; - } - - renderElement(repository: ISCMRepository, index: number, templateData: RepositoryTemplateData): void { - templateData.disposable.dispose(); - const disposables = new DisposableStore(); - - if (repository.provider.rootUri) { - templateData.title.textContent = basename(repository.provider.rootUri); - templateData.type.textContent = repository.provider.label; - } else { - templateData.title.textContent = repository.provider.label; - templateData.type.textContent = ''; - } - - const actions: IAction[] = []; - const disposeActions = () => dispose(actions); - disposables.add({ dispose: disposeActions }); - - const update = () => { - disposeActions(); - - const commands = repository.provider.statusBarCommands || []; - actions.splice(0, actions.length, ...commands.map(c => new StatusBarAction(c, this.commandService))); - templateData.actionBar.clear(); - templateData.actionBar.push(actions); - - const count = repository.provider.count || 0; - toggleClass(templateData.countContainer, 'hidden', count === 0); - templateData.count.setCount(count); - - this._onDidRenderElement.fire(repository); - }; - - disposables.add(repository.provider.onDidChange(update, null)); - update(); - - templateData.disposable = disposables; - } - - disposeTemplate(templateData: RepositoryTemplateData): void { - templateData.disposable.dispose(); - templateData.templateDisposable.dispose(); - } -} - -export class MainPanel extends ViewletPanel { - - static readonly ID = 'scm.mainPanel'; - static readonly TITLE = localize('scm providers', "Source Control Providers"); - - private list: List; - - constructor( - protected viewModel: IViewModel, - options: IViewletPanelOptions, - @IKeybindingService protected keybindingService: IKeybindingService, - @IContextMenuService protected contextMenuService: IContextMenuService, - @ISCMService protected scmService: ISCMService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IMenuService private readonly menuService: IMenuService, - @IConfigurationService configurationService: IConfigurationService - ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService); - } - - protected renderBody(container: HTMLElement): void { - const delegate = new ProvidersListDelegate(); - const renderer = this.instantiationService.createInstance(ProviderRenderer); - const identityProvider = { getId: (r: ISCMRepository) => r.provider.id }; - - this.list = this.instantiationService.createInstance(WorkbenchList, `SCM Main`, container, delegate, [renderer], { - identityProvider, - horizontalScrolling: false - }); - - this._register(renderer.onDidRenderElement(e => this.list.updateWidth(this.viewModel.repositories.indexOf(e)), null)); - this._register(this.list.onSelectionChange(this.onListSelectionChange, this)); - this._register(this.list.onFocusChange(this.onListFocusChange, this)); - this._register(this.list.onContextMenu(this.onListContextMenu, this)); - - this._register(this.viewModel.onDidChangeVisibleRepositories(this.updateListSelection, this)); - - this._register(this.viewModel.onDidSplice(({ index, deleteCount, elements }) => this.splice(index, deleteCount, elements), null)); - this.splice(0, 0, this.viewModel.repositories); - - this._register(this.list); - - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('scm.providers.visible')) { - this.updateBodySize(); - } - })); - - this.updateListSelection(); - } - - private splice(index: number, deleteCount: number, repositories: ISCMRepository[] = []): void { - this.list.splice(index, deleteCount, repositories); - - const empty = this.list.length === 0; - toggleClass(this.element, 'empty', empty); - - this.updateBodySize(); - } - - protected layoutBody(height: number, width: number): void { - this.list.layout(height, width); - } - - private updateBodySize(): void { - const visibleCount = this.configurationService.getValue('scm.providers.visible'); - const empty = this.list.length === 0; - const size = Math.min(this.viewModel.repositories.length, visibleCount) * 22; - - this.minimumBodySize = visibleCount === 0 ? 22 : size; - this.maximumBodySize = visibleCount === 0 ? Number.POSITIVE_INFINITY : empty ? Number.POSITIVE_INFINITY : size; - } - - private onListContextMenu(e: IListContextMenuEvent): void { - if (!e.element) { - return; - } - - const repository = e.element; - const contextKeyService = this.contextKeyService.createScoped(); - const scmProviderKey = contextKeyService.createKey('scmProvider', undefined); - scmProviderKey.set(repository.provider.contextValue); - - const menu = this.menuService.createMenu(MenuId.SCMSourceControl, contextKeyService); - const primary: IAction[] = []; - const secondary: IAction[] = []; - const result = { primary, secondary }; - - const disposable = createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => g === 'inline'); - - menu.dispose(); - contextKeyService.dispose(); - - if (secondary.length === 0) { - return; - } - - this.contextMenuService.showContextMenu({ - getAnchor: () => e.anchor, - getActions: () => secondary, - getActionsContext: () => repository.provider - }); - - disposable.dispose(); - } - - private onListSelectionChange(e: IListEvent): void { - if (e.browserEvent && e.elements.length > 0) { - const scrollTop = this.list.scrollTop; - this.viewModel.setVisibleRepositories(e.elements); - this.list.scrollTop = scrollTop; - } - } - - private onListFocusChange(e: IListEvent): void { - if (e.browserEvent && e.elements.length > 0) { - e.elements[0].focus(); - } - } - - private updateListSelection(): void { - const set = new Set(); - - for (const repository of this.viewModel.visibleRepositories) { - set.add(repository); - } - - const selection: number[] = []; - - for (let i = 0; i < this.list.length; i++) { - if (set.has(this.list.element(i))) { - selection.push(i); - } - } - - this.list.setSelection(selection); - - if (selection.length > 0) { - this.list.setFocus([selection[0]]); - } - } -} - -interface ResourceGroupTemplate { - name: HTMLElement; - count: CountBadge; - actionBar: ActionBar; - elementDisposable: IDisposable; - dispose: () => void; -} - -class ResourceGroupRenderer implements IListRenderer { - - static TEMPLATE_ID = 'resource group'; - get templateId(): string { return ResourceGroupRenderer.TEMPLATE_ID; } - - constructor( - private actionViewItemProvider: IActionViewItemProvider, - private themeService: IThemeService, - private menus: SCMMenus - ) { } - - renderTemplate(container: HTMLElement): ResourceGroupTemplate { - const element = append(container, $('.resource-group')); - const name = append(element, $('.name')); - const actionsContainer = append(element, $('.actions')); - const actionBar = new ActionBar(actionsContainer, { actionViewItemProvider: this.actionViewItemProvider }); - const countContainer = append(element, $('.count')); - const count = new CountBadge(countContainer); - const styler = attachBadgeStyler(count, this.themeService); - const elementDisposable = Disposable.None; - - return { - name, count, actionBar, elementDisposable, dispose: () => { - actionBar.dispose(); - styler.dispose(); - } - }; - } - - renderElement(group: ISCMResourceGroup, index: number, template: ResourceGroupTemplate): void { - template.elementDisposable.dispose(); - - template.name.textContent = group.label; - template.actionBar.clear(); - template.actionBar.context = group; - - const disposables = new DisposableStore(); - disposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceGroupMenu(group), template.actionBar)); - - const updateCount = () => template.count.setCount(group.elements.length); - disposables.add(group.onDidSplice(updateCount, null)); - updateCount(); - - template.elementDisposable = disposables; - } - - disposeElement(group: ISCMResourceGroup, index: number, template: ResourceGroupTemplate): void { - template.elementDisposable.dispose(); - } - - disposeTemplate(template: ResourceGroupTemplate): void { - template.dispose(); - } -} - -interface ResourceTemplate { - element: HTMLElement; - name: HTMLElement; - fileLabel: IResourceLabel; - decorationIcon: HTMLElement; - actionBar: ActionBar; - elementDisposable: IDisposable; - dispose: () => void; -} - -class MultipleSelectionActionRunner extends ActionRunner { - - constructor(private getSelectedResources: () => ISCMResource[]) { - super(); - } - - runAction(action: IAction, context: ISCMResource): Promise { - if (action instanceof MenuItemAction) { - const selection = this.getSelectedResources(); - const filteredSelection = selection.filter(s => s !== context); - - if (selection.length === filteredSelection.length || selection.length === 1) { - return action.run(context); - } - - return action.run(context, ...filteredSelection); - } - - return super.runAction(action, context); - } -} - -class ResourceRenderer implements IListRenderer { - - static TEMPLATE_ID = 'resource'; - get templateId(): string { return ResourceRenderer.TEMPLATE_ID; } - - constructor( - private labels: ResourceLabels, - private actionViewItemProvider: IActionViewItemProvider, - private getSelectedResources: () => ISCMResource[], - private themeService: IThemeService, - private menus: SCMMenus - ) { } - - renderTemplate(container: HTMLElement): ResourceTemplate { - const element = append(container, $('.resource')); - const name = append(element, $('.name')); - const fileLabel = this.labels.create(name); - const actionsContainer = append(fileLabel.element, $('.actions')); - const actionBar = new ActionBar(actionsContainer, { - actionViewItemProvider: this.actionViewItemProvider, - actionRunner: new MultipleSelectionActionRunner(this.getSelectedResources) - }); - - const decorationIcon = append(element, $('.decoration-icon')); - - return { - element, name, fileLabel, decorationIcon, actionBar, elementDisposable: Disposable.None, dispose: () => { - actionBar.dispose(); - fileLabel.dispose(); - } - }; - } - - renderElement(resource: ISCMResource, index: number, template: ResourceTemplate): void { - template.elementDisposable.dispose(); - - const theme = this.themeService.getTheme(); - const icon = theme.type === LIGHT ? resource.decorations.icon : resource.decorations.iconDark; - - template.fileLabel.setFile(resource.sourceUri, { fileDecorations: { colors: false, badges: !icon } }); - template.actionBar.clear(); - template.actionBar.context = resource; - - const disposables = new DisposableStore(); - disposables.add(connectPrimaryMenuToInlineActionBar(this.menus.getResourceMenu(resource.resourceGroup), template.actionBar)); - - toggleClass(template.name, 'strike-through', resource.decorations.strikeThrough); - toggleClass(template.element, 'faded', resource.decorations.faded); - - if (icon) { - template.decorationIcon.style.display = ''; - template.decorationIcon.style.backgroundImage = `url('${icon}')`; - template.decorationIcon.title = resource.decorations.tooltip || ''; - } else { - template.decorationIcon.style.display = 'none'; - template.decorationIcon.style.backgroundImage = ''; - } - - template.element.setAttribute('data-tooltip', resource.decorations.tooltip || ''); - template.elementDisposable = disposables; - } - - disposeElement(resource: ISCMResource, index: number, template: ResourceTemplate): void { - template.elementDisposable.dispose(); - } - - disposeTemplate(template: ResourceTemplate): void { - template.elementDisposable.dispose(); - template.dispose(); - } -} - -class ProviderListDelegate implements IListVirtualDelegate { - - getHeight() { return 22; } - - getTemplateId(element: ISCMResourceGroup | ISCMResource) { - return isSCMResource(element) ? ResourceRenderer.TEMPLATE_ID : ResourceGroupRenderer.TEMPLATE_ID; - } -} - -const scmResourceIdentityProvider = new class implements IIdentityProvider { - getId(r: ISCMResourceGroup | ISCMResource): string { - if (isSCMResource(r)) { - const group = r.resourceGroup; - const provider = group.provider; - return `${provider.contextValue}/${group.id}/${r.sourceUri.toString()}`; - } else { - const provider = r.provider; - return `${provider.contextValue}/${r.id}`; - } - } -}; - -const scmKeyboardNavigationLabelProvider = new class implements IKeyboardNavigationLabelProvider { - getKeyboardNavigationLabel(e: ISCMResourceGroup | ISCMResource) { - if (isSCMResource(e)) { - return basename(e.sourceUri); - } else { - return e.label; - } - } -}; - -function isGroupVisible(group: ISCMResourceGroup) { - return group.elements.length > 0 || !group.hideWhenEmpty; -} - -interface IGroupItem { - readonly group: ISCMResourceGroup; - visible: boolean; - readonly disposable: IDisposable; -} - -class ResourceGroupSplicer { - - private items: IGroupItem[] = []; - private disposables: IDisposable[] = []; - - constructor( - groupSequence: ISequence, - private spliceable: ISpliceable - ) { - groupSequence.onDidSplice(this.onDidSpliceGroups, this, this.disposables); - this.onDidSpliceGroups({ start: 0, deleteCount: 0, toInsert: groupSequence.elements }); - } - - private onDidSpliceGroups({ start, deleteCount, toInsert }: ISplice): void { - let absoluteStart = 0; - - for (let i = 0; i < start; i++) { - const item = this.items[i]; - absoluteStart += (item.visible ? 1 : 0) + item.group.elements.length; - } - - let absoluteDeleteCount = 0; - - for (let i = 0; i < deleteCount; i++) { - const item = this.items[start + i]; - absoluteDeleteCount += (item.visible ? 1 : 0) + item.group.elements.length; - } - - const itemsToInsert: IGroupItem[] = []; - const absoluteToInsert: Array = []; - - for (const group of toInsert) { - const visible = isGroupVisible(group); - - if (visible) { - absoluteToInsert.push(group); - } - - for (const element of group.elements) { - absoluteToInsert.push(element); - } - - const disposable = combinedDisposable( - group.onDidChange(() => this.onDidChangeGroup(group)), - group.onDidSplice(splice => this.onDidSpliceGroup(group, splice)) - ); - - itemsToInsert.push({ group, visible, disposable }); - } - - const itemsToDispose = this.items.splice(start, deleteCount, ...itemsToInsert); - - for (const item of itemsToDispose) { - item.disposable.dispose(); - } - - this.spliceable.splice(absoluteStart, absoluteDeleteCount, absoluteToInsert); - } - - private onDidChangeGroup(group: ISCMResourceGroup): void { - const itemIndex = firstIndex(this.items, item => item.group === group); - - if (itemIndex < 0) { - return; - } - - const item = this.items[itemIndex]; - const visible = isGroupVisible(group); - - if (item.visible === visible) { - return; - } - - let absoluteStart = 0; - - for (let i = 0; i < itemIndex; i++) { - const item = this.items[i]; - absoluteStart += (item.visible ? 1 : 0) + item.group.elements.length; - } - - if (visible) { - this.spliceable.splice(absoluteStart, 0, [group, ...group.elements]); - } else { - this.spliceable.splice(absoluteStart, 1 + group.elements.length, []); - } - - item.visible = visible; - } - - private onDidSpliceGroup(group: ISCMResourceGroup, { start, deleteCount, toInsert }: ISplice): void { - const itemIndex = firstIndex(this.items, item => item.group === group); - - if (itemIndex < 0) { - return; - } - - const item = this.items[itemIndex]; - const visible = isGroupVisible(group); - - if (!item.visible && !visible) { - return; - } - - let absoluteStart = start; - - for (let i = 0; i < itemIndex; i++) { - const item = this.items[i]; - absoluteStart += (item.visible ? 1 : 0) + item.group.elements.length; - } - - if (item.visible && !visible) { - this.spliceable.splice(absoluteStart, 1 + deleteCount, toInsert); - } else if (!item.visible && visible) { - this.spliceable.splice(absoluteStart, deleteCount, [group, ...toInsert]); - } else { - this.spliceable.splice(absoluteStart + 1, deleteCount, toInsert); - } - - item.visible = visible; - } - - dispose(): void { - this.onDidSpliceGroups({ start: 0, deleteCount: this.items.length, toInsert: [] }); - this.disposables = dispose(this.disposables); - } -} - -function convertValidationType(type: InputValidationType): MessageType { - switch (type) { - case InputValidationType.Information: return MessageType.INFO; - case InputValidationType.Warning: return MessageType.WARNING; - case InputValidationType.Error: return MessageType.ERROR; - } -} - -export class RepositoryPanel extends ViewletPanel { - - private cachedHeight: number | undefined = undefined; - private cachedWidth: number | undefined = undefined; - private cachedScrollTop: number | undefined = undefined; - private inputBoxContainer: HTMLElement; - private inputBox: InputBox; - private listContainer: HTMLElement; - private list: List; - private listLabels: ResourceLabels; - private menus: SCMMenus; - private visibilityDisposables: IDisposable[] = []; - protected contextKeyService: IContextKeyService; - - constructor( - readonly repository: ISCMRepository, - private readonly viewModel: IViewModel, - options: IViewletPanelOptions, - @IKeybindingService protected keybindingService: IKeybindingService, - @IThemeService protected themeService: IThemeService, - @IContextMenuService protected contextMenuService: IContextMenuService, - @IContextViewService protected contextViewService: IContextViewService, - @ICommandService protected commandService: ICommandService, - @INotificationService private readonly notificationService: INotificationService, - @IEditorService protected editorService: IEditorService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IConfigurationService protected configurationService: IConfigurationService, - @IContextKeyService contextKeyService: IContextKeyService, - @IMenuService protected menuService: IMenuService - ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService); - - this.menus = instantiationService.createInstance(SCMMenus, this.repository.provider); - this._register(this.menus); - this._register(this.menus.onDidChangeTitle(this._onDidChangeTitleArea.fire, this._onDidChangeTitleArea)); - - this.contextKeyService = contextKeyService.createScoped(this.element); - this.contextKeyService.createKey('scmRepository', this.repository); - } - - render(): void { - super.render(); - this._register(this.menus.onDidChangeTitle(this.updateActions, this)); - } - - protected renderHeaderTitle(container: HTMLElement): void { - let title: string; - let type: string; - - if (this.repository.provider.rootUri) { - title = basename(this.repository.provider.rootUri); - type = this.repository.provider.label; - } else { - title = this.repository.provider.label; - type = ''; - } - - super.renderHeaderTitle(container, title); - addClass(container, 'scm-provider'); - append(container, $('span.type', undefined, type)); - } - - protected renderBody(container: HTMLElement): void { - const focusTracker = trackFocus(container); - this._register(focusTracker.onDidFocus(() => this.repository.focus())); - this._register(focusTracker); - - // Input - this.inputBoxContainer = append(container, $('.scm-editor')); - - const updatePlaceholder = () => { - const binding = this.keybindingService.lookupKeybinding('scm.acceptInput'); - const label = binding ? binding.getLabel() : (platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter'); - const placeholder = format(this.repository.input.placeholder, label); - - this.inputBox.setPlaceHolder(placeholder); - }; - - const validationDelayer = new ThrottledDelayer(200); - const validate = () => { - return this.repository.input.validateInput(this.inputBox.value, this.inputBox.inputElement.selectionStart || 0).then(result => { - if (!result) { - this.inputBox.inputElement.removeAttribute('aria-invalid'); - this.inputBox.hideMessage(); - } else { - this.inputBox.inputElement.setAttribute('aria-invalid', 'true'); - this.inputBox.showMessage({ content: result.message, type: convertValidationType(result.type) }); - } - }); - }; - - const triggerValidation = () => validationDelayer.trigger(validate); - - this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { flexibleHeight: true, flexibleMaxHeight: 134 }); - this.inputBox.setEnabled(this.isBodyVisible()); - this._register(attachInputBoxStyler(this.inputBox, this.themeService)); - this._register(this.inputBox); - - this._register(this.inputBox.onDidChange(triggerValidation, null)); - - const onKeyUp = domEvent(this.inputBox.inputElement, 'keyup'); - const onMouseUp = domEvent(this.inputBox.inputElement, 'mouseup'); - this._register(Event.any(onKeyUp, onMouseUp)(triggerValidation, null)); - - this.inputBox.value = this.repository.input.value; - this._register(this.inputBox.onDidChange(value => this.repository.input.value = value, null)); - this._register(this.repository.input.onDidChange(value => this.inputBox.value = value, null)); - - updatePlaceholder(); - this._register(this.repository.input.onDidChangePlaceholder(updatePlaceholder, null)); - this._register(this.keybindingService.onDidUpdateKeybindings(updatePlaceholder, null)); - - this._register(this.inputBox.onDidHeightChange(() => this.layoutBody())); - - if (this.repository.provider.onDidChangeCommitTemplate) { - this._register(this.repository.provider.onDidChangeCommitTemplate(this.updateInputBox, this)); - } - - this.updateInputBox(); - - // Input box visibility - this._register(this.repository.input.onDidChangeVisibility(this.updateInputBoxVisibility, this)); - this.updateInputBoxVisibility(); - - // List - this.listContainer = append(container, $('.scm-status.show-file-icons')); - - const updateActionsVisibility = () => toggleClass(this.listContainer, 'show-actions', this.configurationService.getValue('scm.alwaysShowActions')); - Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowActions'))(updateActionsVisibility); - updateActionsVisibility(); - - const delegate = new ProviderListDelegate(); - - const actionViewItemProvider = (action: IAction) => this.getActionViewItem(action); - - this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility }); - this._register(this.listLabels); - - const renderers = [ - new ResourceGroupRenderer(actionViewItemProvider, this.themeService, this.menus), - new ResourceRenderer(this.listLabels, actionViewItemProvider, () => this.getSelectedResources(), this.themeService, this.menus) - ]; - - this.list = this.instantiationService.createInstance(WorkbenchList, `SCM Repo`, this.listContainer, delegate, renderers, { - identityProvider: scmResourceIdentityProvider, - keyboardNavigationLabelProvider: scmKeyboardNavigationLabelProvider, - horizontalScrolling: false - }); - - this._register(Event.chain(this.list.onDidOpen) - .map(e => e.elements[0]) - .filter(e => !!e && isSCMResource(e)) - .on(this.open, this)); - - this._register(Event.chain(this.list.onPin) - .map(e => e.elements[0]) - .filter(e => !!e && isSCMResource(e)) - .on(this.pin, this)); - - this._register(this.list.onContextMenu(this.onListContextMenu, this)); - this._register(this.list); - - this._register(this.viewModel.onDidChangeVisibility(this.onDidChangeVisibility, this)); - this.onDidChangeVisibility(this.viewModel.isVisible()); - this.onDidChangeBodyVisibility(visible => this.inputBox.setEnabled(visible)); - } - - private onDidChangeVisibility(visible: boolean): void { - if (visible) { - const listSplicer = new ResourceGroupSplicer(this.repository.provider.groups, this.list); - this.visibilityDisposables.push(listSplicer); - } else { - this.cachedScrollTop = this.list.scrollTop; - this.visibilityDisposables = dispose(this.visibilityDisposables); - } - } - - layoutBody(height: number | undefined = this.cachedHeight, width: number | undefined = this.cachedWidth): void { - if (height === undefined) { - return; - } - - this.cachedHeight = height; - - if (this.repository.input.visible) { - removeClass(this.inputBoxContainer, 'hidden'); - this.inputBox.layout(); - - const editorHeight = this.inputBox.height; - const listHeight = height - (editorHeight + 12 /* margin */); - this.listContainer.style.height = `${listHeight}px`; - this.list.layout(listHeight, width); - } else { - addClass(this.inputBoxContainer, 'hidden'); - - this.listContainer.style.height = `${height}px`; - this.list.layout(height, width); - } - - if (this.cachedScrollTop !== undefined && this.list.scrollTop !== this.cachedScrollTop) { - this.list.scrollTop = Math.min(this.cachedScrollTop, this.list.scrollHeight); - // Applying the cached scroll position just once until the next leave. - // This, also, avoids the scrollbar to flicker when resizing the sidebar. - this.cachedScrollTop = undefined; - } - } - - focus(): void { - super.focus(); - - if (this.isExpanded()) { - if (this.repository.input.visible) { - this.inputBox.focus(); - } else { - this.list.domFocus(); - } - - this.repository.focus(); - } - } - - getActions(): IAction[] { - return this.menus.getTitleActions(); - } - - getSecondaryActions(): IAction[] { - return this.menus.getTitleSecondaryActions(); - } - - getActionViewItem(action: IAction): IActionViewItem | undefined { - if (!(action instanceof MenuItemAction)) { - return undefined; - } - - return new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); - } - - getActionsContext(): any { - return this.repository.provider; - } - - private open(e: ISCMResource): void { - e.open(); - } - - private pin(): void { - const activeControl = this.editorService.activeControl; - if (activeControl) { - activeControl.group.pinEditor(activeControl.input); - } - } - - private onListContextMenu(e: IListContextMenuEvent): void { - if (!e.element) { - return; - } - - const element = e.element; - let actions: IAction[]; - - if (isSCMResource(element)) { - actions = this.menus.getResourceContextActions(element); - } else { - actions = this.menus.getResourceGroupContextActions(element); - } - - this.contextMenuService.showContextMenu({ - getAnchor: () => e.anchor, - getActions: () => actions, - getActionsContext: () => element, - actionRunner: new MultipleSelectionActionRunner(() => this.getSelectedResources()) - }); - } - - private getSelectedResources(): ISCMResource[] { - return this.list.getSelectedElements() - .filter(r => isSCMResource(r)) as ISCMResource[]; - } - - private updateInputBox(): void { - if (typeof this.repository.provider.commitTemplate === 'undefined' || !this.repository.input.visible || this.inputBox.value) { - return; - } - - this.inputBox.value = this.repository.provider.commitTemplate; - } - - private updateInputBoxVisibility(): void { - if (this.cachedHeight) { - this.layoutBody(this.cachedHeight); - } - } - - dispose(): void { - this.visibilityDisposables = dispose(this.visibilityDisposables); - super.dispose(); - } -} - -class RepositoryViewDescriptor implements IViewDescriptor { - - private static counter = 0; - - readonly id: string; - readonly name: string; - readonly ctorDescriptor: { ctor: any, arguments?: any[] }; - readonly canToggleVisibility = true; - readonly order = -500; - readonly workspace = true; - - constructor(readonly repository: ISCMRepository, viewModel: IViewModel, readonly hideByDefault: boolean) { - const repoId = repository.provider.rootUri ? repository.provider.rootUri.toString() : `#${RepositoryViewDescriptor.counter++}`; - this.id = `scm:repository:${repository.provider.label}:${repoId}`; - this.name = repository.provider.rootUri ? basename(repository.provider.rootUri) : repository.provider.label; - - this.ctorDescriptor = { ctor: RepositoryPanel, arguments: [repository, viewModel] }; - } -} - -class MainPanelDescriptor implements IViewDescriptor { - - readonly id = MainPanel.ID; - readonly name = MainPanel.TITLE; - readonly ctorDescriptor: { ctor: any, arguments?: any[] }; - readonly canToggleVisibility = true; - readonly hideByDefault = false; - readonly order = -1000; - readonly workspace = true; - readonly when = ContextKeyExpr.or(ContextKeyExpr.equals('config.scm.alwaysShowProviders', true), ContextKeyExpr.and(ContextKeyExpr.notEquals('scm.providerCount', 0), ContextKeyExpr.notEquals('scm.providerCount', 1))); - - constructor(viewModel: IViewModel) { - this.ctorDescriptor = { ctor: MainPanel, arguments: [viewModel] }; - } -} - export class SCMViewlet extends ViewContainerViewlet implements IViewModel { private static readonly STATE_KEY = 'workbench.scm.views.state'; @@ -1052,7 +62,7 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { private repositoryCountKey: IContextKey; private viewDescriptors: RepositoryViewDescriptor[] = []; - private _onDidSplice = new Emitter>(); + private readonly _onDidSplice = new Emitter>(); readonly onDidSplice: Event> = this._onDidSplice.event; private _height: number | undefined = undefined; @@ -1125,7 +135,7 @@ export class SCMViewlet extends ViewContainerViewlet implements IViewModel { const index = this._repositories.length; this._repositories.push(repository); - const viewDescriptor = new RepositoryViewDescriptor(repository, this, false); + const viewDescriptor = new RepositoryViewDescriptor(repository, false); Registry.as(Extensions.ViewsRegistry).registerViews([viewDescriptor], VIEW_CONTAINER); this.viewDescriptors.push(viewDescriptor); diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts new file mode 100644 index 0000000000..fc2a921687 --- /dev/null +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ISCMResource, ISCMRepository, ISCMResourceGroup } from 'vs/workbench/contrib/scm/common/scm'; +import { IMenu } from 'vs/platform/actions/common/actions'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IDisposable, Disposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IAction } from 'vs/base/common/actions'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { equals } from 'vs/base/common/arrays'; + +export function isSCMRepository(element: any): element is ISCMRepository { + return !!(element as ISCMRepository).provider && typeof (element as ISCMRepository).setSelected === 'function'; +} + +export function isSCMResourceGroup(element: any): element is ISCMResourceGroup { + return !!(element as ISCMResourceGroup).provider && !!(element as ISCMResourceGroup).elements; +} + +export function isSCMResource(element: any): element is ISCMResource { + return !!(element as ISCMResource).sourceUri && isSCMResourceGroup((element as ISCMResource).resourceGroup); +} + +export function connectPrimaryMenuToInlineActionBar(menu: IMenu, actionBar: ActionBar): IDisposable { + let cachedDisposable: IDisposable = Disposable.None; + let cachedPrimary: IAction[] = []; + + const updateActions = () => { + const primary: IAction[] = []; + const secondary: IAction[] = []; + + const disposable = createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, { primary, secondary }, g => /^inline/.test(g)); + + if (equals(cachedPrimary, primary, (a, b) => a.id === b.id)) { + disposable.dispose(); + return; + } + + cachedDisposable = disposable; + cachedPrimary = primary; + + actionBar.clear(); + actionBar.push(primary, { icon: true, label: false }); + }; + + updateActions(); + + return combinedDisposable(menu.onDidChange(updateActions), toDisposable(() => { + cachedDisposable.dispose(); + })); +} diff --git a/src/vs/workbench/contrib/scm/common/scmService.ts b/src/vs/workbench/contrib/scm/common/scmService.ts index 02996342d9..bab77b8aba 100644 --- a/src/vs/workbench/contrib/scm/common/scmService.ts +++ b/src/vs/workbench/contrib/scm/common/scmService.ts @@ -22,7 +22,7 @@ class SCMInput implements ISCMInput { this._onDidChange.fire(value); } - private _onDidChange = new Emitter(); + private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; private _placeholder = ''; @@ -36,7 +36,7 @@ class SCMInput implements ISCMInput { this._onDidChangePlaceholder.fire(placeholder); } - private _onDidChangePlaceholder = new Emitter(); + private readonly _onDidChangePlaceholder = new Emitter(); readonly onDidChangePlaceholder: Event = this._onDidChangePlaceholder.event; private _visible = true; @@ -50,7 +50,7 @@ class SCMInput implements ISCMInput { this._onDidChangeVisibility.fire(visible); } - private _onDidChangeVisibility = new Emitter(); + private readonly _onDidChangeVisibility = new Emitter(); readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; private _validateInput: IInputValidator = () => Promise.resolve(undefined); @@ -64,13 +64,13 @@ class SCMInput implements ISCMInput { this._onDidChangeValidateInput.fire(); } - private _onDidChangeValidateInput = new Emitter(); + private readonly _onDidChangeValidateInput = new Emitter(); readonly onDidChangeValidateInput: Event = this._onDidChangeValidateInput.event; } class SCMRepository implements ISCMRepository { - private _onDidFocus = new Emitter(); + private readonly _onDidFocus = new Emitter(); readonly onDidFocus: Event = this._onDidFocus.event; private _selected = false; @@ -78,7 +78,7 @@ class SCMRepository implements ISCMRepository { return this._selected; } - private _onDidChangeSelection = new Emitter(); + private readonly _onDidChangeSelection = new Emitter(); readonly onDidChangeSelection: Event = this._onDidChangeSelection.event; readonly input: ISCMInput = new SCMInput(); @@ -114,13 +114,13 @@ export class SCMService implements ISCMService { private _selectedRepositories: ISCMRepository[] = []; get selectedRepositories(): ISCMRepository[] { return [...this._selectedRepositories]; } - private _onDidChangeSelectedRepositories = new Emitter(); + private readonly _onDidChangeSelectedRepositories = new Emitter(); readonly onDidChangeSelectedRepositories: Event = this._onDidChangeSelectedRepositories.event; - private _onDidAddProvider = new Emitter(); + private readonly _onDidAddProvider = new Emitter(); readonly onDidAddRepository: Event = this._onDidAddProvider.event; - private _onDidRemoveProvider = new Emitter(); + private readonly _onDidRemoveProvider = new Emitter(); readonly onDidRemoveRepository: Event = this._onDidRemoveProvider.event; constructor(@ILogService private readonly logService: ILogService) { } diff --git a/src/vs/workbench/contrib/search/browser/media/clear-dark.svg b/src/vs/workbench/contrib/search/browser/media/clear-dark.svg deleted file mode 100644 index 7a95ac0646..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/clear-dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/search/browser/media/clear-hc.svg b/src/vs/workbench/contrib/search/browser/media/clear-hc.svg deleted file mode 100644 index 6709e070c6..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/clear-hc.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/search/browser/media/clear-light.svg b/src/vs/workbench/contrib/search/browser/media/clear-light.svg deleted file mode 100644 index b024caa805..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/clear-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/search/browser/media/collapse-all-dark.svg b/src/vs/workbench/contrib/search/browser/media/collapse-all-dark.svg deleted file mode 100644 index 4862c55dbe..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/collapse-all-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/search/browser/media/collapse-all-hc.svg b/src/vs/workbench/contrib/search/browser/media/collapse-all-hc.svg deleted file mode 100644 index 05f920b29b..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/collapse-all-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/search/browser/media/collapse-all-light.svg b/src/vs/workbench/contrib/search/browser/media/collapse-all-light.svg deleted file mode 100644 index 6359b42e62..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/collapse-all-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/search/browser/media/ellipsis-dark.svg b/src/vs/workbench/contrib/search/browser/media/ellipsis-dark.svg deleted file mode 100644 index 2c52e359f6..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/ellipsis-dark.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/contrib/search/browser/media/ellipsis-hc.svg b/src/vs/workbench/contrib/search/browser/media/ellipsis-hc.svg deleted file mode 100644 index 3d7068f6b4..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/ellipsis-hc.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/contrib/search/browser/media/ellipsis-light.svg b/src/vs/workbench/contrib/search/browser/media/ellipsis-light.svg deleted file mode 100644 index 883d2722ce..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/ellipsis-light.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/vs/workbench/contrib/search/browser/media/exclude-settings-dark.svg b/src/vs/workbench/contrib/search/browser/media/exclude-settings-dark.svg deleted file mode 100644 index 0b1694dc2f..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/exclude-settings-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/exclude-settings-hc.svg b/src/vs/workbench/contrib/search/browser/media/exclude-settings-hc.svg deleted file mode 100644 index ba88235419..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/exclude-settings-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/exclude-settings-light.svg b/src/vs/workbench/contrib/search/browser/media/exclude-settings-light.svg deleted file mode 100644 index 114ec3f0fe..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/exclude-settings-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/refresh-dark.svg b/src/vs/workbench/contrib/search/browser/media/refresh-dark.svg deleted file mode 100644 index e1f05aadee..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/refresh-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/search/browser/media/refresh-hc.svg b/src/vs/workbench/contrib/search/browser/media/refresh-hc.svg deleted file mode 100644 index 48fc30f2c4..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/refresh-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/search/browser/media/refresh-light.svg b/src/vs/workbench/contrib/search/browser/media/refresh-light.svg deleted file mode 100644 index 9b1d910840..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/refresh-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/contrib/search/browser/media/remove-dark.svg b/src/vs/workbench/contrib/search/browser/media/remove-dark.svg deleted file mode 100644 index 6d16d212ae..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/remove-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/remove-hc.svg b/src/vs/workbench/contrib/search/browser/media/remove-hc.svg deleted file mode 100644 index fa205f4ee1..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/remove-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/remove-light.svg b/src/vs/workbench/contrib/search/browser/media/remove-light.svg deleted file mode 100644 index 742fcae4ae..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/remove-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/replace-all-dark.svg b/src/vs/workbench/contrib/search/browser/media/replace-all-dark.svg deleted file mode 100644 index 07bd41a789..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/replace-all-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/replace-all-hc.svg b/src/vs/workbench/contrib/search/browser/media/replace-all-hc.svg deleted file mode 100644 index e375cf395e..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/replace-all-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/replace-all-light.svg b/src/vs/workbench/contrib/search/browser/media/replace-all-light.svg deleted file mode 100644 index cd3974fae7..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/replace-all-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/replace-dark.svg b/src/vs/workbench/contrib/search/browser/media/replace-dark.svg deleted file mode 100644 index 5882b22c58..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/replace-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/replace-hc.svg b/src/vs/workbench/contrib/search/browser/media/replace-hc.svg deleted file mode 100644 index 7b419b1130..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/replace-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/replace-light.svg b/src/vs/workbench/contrib/search/browser/media/replace-light.svg deleted file mode 100644 index 220f2aba40..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/replace-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index b125d79b91..0a613548f8 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -18,6 +18,9 @@ background-position: center center; background-repeat: no-repeat; cursor: pointer; + display: flex; + align-items: center; + justify-content: center; } .search-view .search-widget .search-container, @@ -104,22 +107,10 @@ right: 0; cursor: pointer; width: 16px; - height: 13px; + height: 16px; z-index: 2; /* Force it above the search results message, which has a negative top margin */ } -.vs-dark .monaco-workbench .search-view .query-details .more { - background: url('ellipsis-dark.svg') center no-repeat; -} - -.hc-black .monaco-workbench .search-view .query-details .more { - background: url('ellipsis-hc.svg') center no-repeat; -} - -.vs .monaco-workbench .search-view .query-details .more { - background: url('ellipsis-light.svg') center no-repeat; -} - .search-view .query-details .file-types { display: none; } @@ -261,30 +252,6 @@ margin-top: 2px; } -.search-view .action-replace { - background-image: url('replace-light.svg'); -} - -.vs-dark .search-view .action-replace { - background-image: url('replace-dark.svg'); -} - -.hc-black .search-view .action-replace { - background-image: url('replace-hc.svg'); -} - -.search-view .action-replace-all { - background: url('replace-all-light.svg') center center no-repeat; -} - -.vs-dark .search-view .action-replace-all { - background: url('replace-all-dark.svg') center center no-repeat; -} - -.hc-black .search-view .action-replace-all { - background: url('replace-all-hc.svg') center center no-repeat; -} - .search-view .monaco-count-badge { margin-right: 12px; } @@ -298,67 +265,6 @@ display: none; } -.monaco-workbench .search-action.refresh { - background: url('refresh-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .search-action.refresh { - background: url('refresh-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .search-action.refresh { - background: url('refresh-hc.svg') center center no-repeat; -} - -.monaco-workbench .search-action.collapse { - background: url('collapse-all-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .search-action.collapse, -.hc-black .monaco-workbench .search-action.collapse { - background: url('collapse-all-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .search-action.collapse { - background: url('collapse-all-hc.svg') center center no-repeat; -} - -.monaco-workbench .search-action.clear-search-results { - background: url('clear-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .search-action.clear-search-results { - background: url('clear-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .search-action.clear-search-results { - background: url('clear-hc.svg') center center no-repeat; -} - -.monaco-workbench .search-action.cancel-search { - background: url('stop-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .search-action.cancel-search { - background: url('stop-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .search-action.cancel-search { - background: url('stop-hc.svg') center center no-repeat; -} - -.vs .monaco-workbench .search-view .query-details .file-types .controls>.monaco-custom-checkbox.useExcludesAndIgnoreFiles { - background: url('exclude-settings-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .search-view .query-details .file-types .controls>.monaco-custom-checkbox.useExcludesAndIgnoreFiles { - background: url('exclude-settings-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .search-view .query-details .file-types .controls>.monaco-custom-checkbox.useExcludesAndIgnoreFiles { - background: url('exclude-settings-hc.svg') center center no-repeat; -} - .search-view .replace.findInFileMatch { text-decoration: line-through; } @@ -388,18 +294,6 @@ background-color: rgba(255, 255, 255, 0.1) !important; } -.search-view .action-remove { - background: url("remove-light.svg") center center no-repeat; -} - -.vs-dark .search-view .action-remove { - background: url("remove-dark.svg") center center no-repeat; -} - -.hc-black .search-view .action-remove { - background: url("remove-hc.svg") center center no-repeat; -} - .vs-dark .search-view .message { opacity: .5; } @@ -409,30 +303,6 @@ padding: 0; } -.vs .search-view .search-widget .toggle-replace-button.expand { - background-image: url('tree-expanded-light.svg'); -} - -.vs-dark .search-view .search-widget .toggle-replace-button.expand { - background-image: url('tree-expanded-dark.svg'); -} - -.hc-black .search-view .search-widget .toggle-replace-button.expand { - background-image: url('tree-expanded-hc.svg'); -} - -.vs .search-view .search-widget .toggle-replace-button.collapse { - background-image: url('tree-collapsed-light.svg'); -} - -.vs-dark .search-view .search-widget .toggle-replace-button.collapse { - background-image: url('tree-collapsed-dark.svg'); -} - -.hc-black .search-view .search-widget .toggle-replace-button.collapse { - background-image: url('tree-collapsed-hc.svg'); -} - /* High Contrast Theming */ .hc-black .monaco-workbench .search-view .foldermatch, diff --git a/src/vs/workbench/contrib/search/browser/media/stop-dark.svg b/src/vs/workbench/contrib/search/browser/media/stop-dark.svg deleted file mode 100644 index 890af29835..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/stop-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/stop-hc.svg b/src/vs/workbench/contrib/search/browser/media/stop-hc.svg deleted file mode 100644 index 1c88dfb60a..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/stop-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/stop-light.svg b/src/vs/workbench/contrib/search/browser/media/stop-light.svg deleted file mode 100644 index 7e41aeff58..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/stop-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/tree-collapsed-dark.svg b/src/vs/workbench/contrib/search/browser/media/tree-collapsed-dark.svg deleted file mode 100644 index f518fc1632..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/tree-collapsed-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/tree-collapsed-hc.svg b/src/vs/workbench/contrib/search/browser/media/tree-collapsed-hc.svg deleted file mode 100644 index 40ba72b708..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/tree-collapsed-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/tree-collapsed-light.svg b/src/vs/workbench/contrib/search/browser/media/tree-collapsed-light.svg deleted file mode 100644 index 0d746558a4..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/tree-collapsed-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/tree-expanded-dark.svg b/src/vs/workbench/contrib/search/browser/media/tree-expanded-dark.svg deleted file mode 100644 index a1df6a8d44..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/tree-expanded-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/tree-expanded-hc.svg b/src/vs/workbench/contrib/search/browser/media/tree-expanded-hc.svg deleted file mode 100644 index 4f2ec14692..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/tree-expanded-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/media/tree-expanded-light.svg b/src/vs/workbench/contrib/search/browser/media/tree-expanded-light.svg deleted file mode 100644 index e60e357f57..0000000000 --- a/src/vs/workbench/contrib/search/browser/media/tree-expanded-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index c63fe21d82..29fa19f8c5 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -195,7 +195,7 @@ export class ExcludePatternInputWidget extends PatternInputWidget { protected renderSubcontrols(controlsDiv: HTMLDivElement): void { this.useExcludesAndIgnoreFilesBox = this._register(new Checkbox({ - actionClassName: 'useExcludesAndIgnoreFiles', + actionClassName: 'useExcludesAndIgnoreFiles codicon-exclude', title: nls.localize('useExcludesAndIgnoreFilesDescription', "Use Exclude Settings and Ignore Files"), isChecked: true, })); diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index e92897f62e..6659de4325 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -260,7 +260,7 @@ export class RefreshAction extends Action { @IViewletService private readonly viewletService: IViewletService, @IPanelService private readonly panelService: IPanelService ) { - super(id, label, 'search-action refresh'); + super(id, label, 'search-action codicon-refresh'); } get enabled(): boolean { @@ -291,7 +291,7 @@ export class CollapseDeepestExpandedLevelAction extends Action { @IViewletService private readonly viewletService: IViewletService, @IPanelService private readonly panelService: IPanelService ) { - super(id, label, 'search-action collapse'); + super(id, label, 'search-action codicon-collapse-all'); this.update(); } @@ -348,7 +348,7 @@ export class ClearSearchResultsAction extends Action { @IViewletService private readonly viewletService: IViewletService, @IPanelService private readonly panelService: IPanelService ) { - super(id, label, 'search-action clear-search-results'); + super(id, label, 'search-action codicon-clear-all'); this.update(); } @@ -375,7 +375,7 @@ export class CancelSearchAction extends Action { @IViewletService private readonly viewletService: IViewletService, @IPanelService private readonly panelService: IPanelService ) { - super(id, label, 'search-action cancel-search'); + super(id, label, 'search-action codicon-search-stop'); this.update(); } @@ -500,7 +500,7 @@ export class RemoveAction extends AbstractSearchAndReplaceAction { private viewer: WorkbenchObjectTree, private element: RenderableMatch ) { - super('remove', RemoveAction.LABEL, 'action-remove'); + super('remove', RemoveAction.LABEL, 'codicon-close'); } run(): Promise { @@ -540,7 +540,7 @@ export class ReplaceAllAction extends AbstractSearchAndReplaceAction { private fileMatch: FileMatch, @IKeybindingService keyBindingService: IKeybindingService ) { - super(Constants.ReplaceAllInFileActionId, appendKeyBindingLabel(ReplaceAllAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFileActionId), keyBindingService), 'action-replace-all'); + super(Constants.ReplaceAllInFileActionId, appendKeyBindingLabel(ReplaceAllAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFileActionId), keyBindingService), 'codicon-replace-all'); } run(): Promise { @@ -564,7 +564,7 @@ export class ReplaceAllInFolderAction extends AbstractSearchAndReplaceAction { constructor(private viewer: WorkbenchObjectTree, private folderMatch: FolderMatch, @IKeybindingService keyBindingService: IKeybindingService ) { - super(Constants.ReplaceAllInFolderActionId, appendKeyBindingLabel(ReplaceAllInFolderAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFolderActionId), keyBindingService), 'action-replace-all'); + super(Constants.ReplaceAllInFolderActionId, appendKeyBindingLabel(ReplaceAllInFolderAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceAllInFolderActionId), keyBindingService), 'codicon-replace-all'); } run(): Promise { @@ -587,7 +587,7 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { @IKeybindingService keyBindingService: IKeybindingService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService private readonly configurationService: IConfigurationService) { - super(Constants.ReplaceActionId, appendKeyBindingLabel(ReplaceAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceActionId), keyBindingService), 'action-replace'); + super(Constants.ReplaceActionId, appendKeyBindingLabel(ReplaceAction.LABEL, keyBindingService.lookupKeybinding(Constants.ReplaceActionId), keyBindingService), 'codicon-replace'); } run(): Promise { diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index eab2974059..5f7bcaf007 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -224,7 +224,7 @@ export class SearchView extends ViewletPanel { // Toggle query details button this.toggleQueryDetailsButton = dom.append(this.queryDetails, - $('.more', { tabindex: 0, role: 'button', title: nls.localize('moreSearch', "Toggle Search Details") })); + $('.more.codicon.codicon-ellipsis', { tabindex: 0, role: 'button', title: nls.localize('moreSearch', "Toggle Search Details") })); this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.CLICK, e => { dom.EventHelper.stop(e); @@ -298,7 +298,7 @@ export class SearchView extends ViewletPanel { this._register(this.viewModel.searchResult.onChange((event) => this.onSearchResultsChanged(event))); this._register(this.searchWidget.searchInput.onInput(() => this.updateActions())); - this._register(this.searchWidget.replaceInput.onDidChange(() => this.updateActions())); + this._register(this.searchWidget.replaceInput.onInput(() => this.updateActions())); this._register(this.onDidFocus(() => this.viewletFocused.set(true))); this._register(this.onDidBlur(() => this.viewletFocused.set(false))); @@ -1048,10 +1048,10 @@ export class SearchView extends ViewletPanel { this.searchWidget.searchInput.setValue(args.query); } if (typeof args.replace === 'string') { - this.searchWidget.replaceInput.value = args.replace; + this.searchWidget.replaceInput.setValue(args.replace); } else { - if (this.searchWidget.replaceInput.value !== '') { - this.searchWidget.replaceInput.value = ''; + if (this.searchWidget.replaceInput.getValue() !== '') { + this.searchWidget.replaceInput.setValue(''); } } if (typeof args.triggerSearch === 'boolean' && args.triggerSearch) { diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index a98e4f2d9c..8440e1d6b5 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -8,7 +8,8 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button, IButtonOptions } from 'vs/base/browser/ui/button/button'; import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput'; -import { HistoryInputBox, IMessage } from 'vs/base/browser/ui/inputbox/inputBox'; +import { ReplaceInput } from 'vs/base/browser/ui/findinput/replaceInput'; +import { IMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { Action } from 'vs/base/common/actions'; import { Delayer } from 'vs/base/common/async'; @@ -24,16 +25,15 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; -import { attachFindInputBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; +import { attachFindReplaceInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ContextScopedFindInput, ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; +import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; import { appendKeyBindingLabel, isSearchViewFocused } from 'vs/workbench/contrib/search/browser/searchActions'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { isMacintosh } from 'vs/base/common/platform'; export interface ISearchWidgetOptions { @@ -62,7 +62,7 @@ class ReplaceAllAction extends Action { private _searchWidget: SearchWidget | null = null; constructor() { - super(ReplaceAllAction.ID, '', 'action-replace-all', false); + super(ReplaceAllAction.ID, '', 'codicon-replace-all', false); } set searchWidget(searchWidget: SearchWidget) { @@ -79,6 +79,22 @@ class ReplaceAllAction extends Action { const ctrlKeyMod = (isMacintosh ? KeyMod.WinCtrl : KeyMod.CtrlCmd); +function stopPropagationForMultiLineUpwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) { + const isMultiline = !!value.match(/\n/); + if (textarea && isMultiline && textarea.selectionStart > 0) { + event.stopPropagation(); + return; + } +} + +function stopPropagationForMultiLineDownwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) { + const isMultiline = !!value.match(/\n/); + if (textarea && isMultiline && textarea.selectionEnd < textarea.value.length) { + event.stopPropagation(); + return; + } +} + export class SearchWidget extends Widget { private static readonly REPLACE_ALL_DISABLED_LABEL = nls.localize('search.action.replaceAll.disabled.label', "Replace All (Submit Search to Enable)"); @@ -94,15 +110,14 @@ export class SearchWidget extends Widget { private searchInputBoxFocused: IContextKey; private replaceContainer!: HTMLElement; - replaceInput!: HistoryInputBox; + replaceInput!: ReplaceInput; + replaceInputFocusTracker!: dom.IFocusTracker; + private replaceInputBoxFocused: IContextKey; private toggleReplaceButton!: Button; private replaceAllAction!: ReplaceAllAction; private replaceActive: IContextKey; private replaceActionBar!: ActionBar; - replaceInputFocusTracker!: dom.IFocusTracker; - private replaceInputBoxFocused: IContextKey; private _replaceHistoryDelayer: Delayer; - private _preserveCase!: Checkbox; private ignoreGlobalFindBufferOnNextFocus = false; private previousGlobalFindBufferValue: string | null = null; @@ -180,12 +195,12 @@ export class SearchWidget extends Widget { setWidth(width: number) { this.searchInput.inputBox.layout(); this.replaceInput.width = width - 28; - this.replaceInput.layout(); + this.replaceInput.inputBox.layout(); } clear() { this.searchInput.clear(); - this.replaceInput.value = ''; + this.replaceInput.setValue(''); this.setReplaceAllActionState(false); } @@ -198,7 +213,7 @@ export class SearchWidget extends Widget { } getReplaceValue(): string { - return this.replaceInput.value; + return this.replaceInput.getValue(); } toggleReplace(show?: boolean): void { @@ -212,7 +227,7 @@ export class SearchWidget extends Widget { } getReplaceHistory(): string[] { - return this.replaceInput.getHistory(); + return this.replaceInput.inputBox.getHistory(); } clearHistory(): void { @@ -228,11 +243,11 @@ export class SearchWidget extends Widget { } showNextReplaceTerm() { - this.replaceInput.showNextValue(); + this.replaceInput.inputBox.showNextValue(); } showPreviousReplaceTerm() { - this.replaceInput.showPreviousValue(); + this.replaceInput.inputBox.showPreviousValue(); } searchInputHasFocus(): boolean { @@ -240,7 +255,7 @@ export class SearchWidget extends Widget { } replaceInputHasFocus(): boolean { - return this.replaceInput.hasFocus(); + return this.replaceInput.inputBox.hasFocus(); } focusReplaceAllAction(): void { @@ -280,7 +295,8 @@ export class SearchWidget extends Widget { }; this.toggleReplaceButton = this._register(new Button(parent, opts)); this.toggleReplaceButton.element.setAttribute('aria-expanded', 'false'); - this.toggleReplaceButton.element.classList.add('collapse'); + this.toggleReplaceButton.element.classList.add('codicon'); + this.toggleReplaceButton.element.classList.add('codicon-chevron-right'); this.toggleReplaceButton.icon = 'toggle-replace-button'; // TODO@joh need to dispose this listener eventually this.toggleReplaceButton.onDidClick(() => this.onToggleReplaceButton()); @@ -301,7 +317,7 @@ export class SearchWidget extends Widget { const searchInputContainer = dom.append(parent, dom.$('.search-container.input-box')); this.searchInput = this._register(new ContextScopedFindInput(searchInputContainer, this.contextViewService, inputOptions, this.contextKeyService, true)); - this._register(attachFindInputBoxStyler(this.searchInput, this.themeService)); + this._register(attachFindReplaceInputBoxStyler(this.searchInput, this.themeService)); this.searchInput.onKeyDown((keyboardEvent: IKeyboardEvent) => this.onSearchInputKeyDown(keyboardEvent)); this.searchInput.setValue(options.value || ''); this.searchInput.setRegex(!!options.isRegex); @@ -316,7 +332,7 @@ export class SearchWidget extends Widget { this._register(this.searchInput.inputBox.onDidHeightChange(() => this._onDidHeightChange.fire())); this._register(this.onReplaceValueChanged(() => { - this._replaceHistoryDelayer.trigger(() => this.replaceInput.addToHistory()); + this._replaceHistoryDelayer.trigger(() => this.replaceInput.inputBox.addToHistory()); })); this.searchInputFocusTracker = this._register(dom.trackFocus(this.searchInput.inputBox.inputElement)); @@ -344,38 +360,24 @@ export class SearchWidget extends Widget { this.replaceContainer = dom.append(parent, dom.$('.replace-container.disabled')); const replaceBox = dom.append(this.replaceContainer, dom.$('.replace-input')); - this.replaceInput = this._register(new ContextScopedHistoryInputBox(replaceBox, this.contextViewService, { - ariaLabel: nls.localize('label.Replace', 'Replace: Type replace term and press Enter to preview or Escape to cancel'), + this.replaceInput = this._register(new ContextScopedReplaceInput(replaceBox, this.contextViewService, { + label: nls.localize('label.Replace', 'Replace: Type replace term and press Enter to preview or Escape to cancel'), placeholder: nls.localize('search.replace.placeHolder', "Replace"), - history: options.replaceHistory || [], + history: options.replaceHistory, flexibleHeight: true - }, this.contextKeyService)); + }, this.contextKeyService, true)); - this._preserveCase = this._register(new Checkbox({ - actionClassName: 'monaco-preserve-case', - title: nls.localize('label.preserveCaseCheckbox', "Preserve Case"), - isChecked: !!options.preserveCase, - })); - - this._register(this._preserveCase.onChange(viaKeyboard => { + this._register(this.replaceInput.onDidOptionChange(viaKeyboard => { if (!viaKeyboard) { - this.replaceInput.focus(); - this._onPreserveCaseChange.fire(this._preserveCase.checked); + this._onPreserveCaseChange.fire(this.replaceInput.getPreserveCase()); } })); - 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)); - this.replaceInput.value = options.replaceValue || ''; - this._register(this.replaceInput.onDidChange(() => this._onReplaceValueChanged.fire())); - this._register(this.replaceInput.onDidHeightChange(() => this._onDidHeightChange.fire())); + this._register(attachFindReplaceInputBoxStyler(this.replaceInput, this.themeService)); + this.replaceInput.onKeyDown((keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent)); + this.replaceInput.setValue(options.replaceValue || ''); + this._register(this.replaceInput.inputBox.onDidChange(() => this._onReplaceValueChanged.fire())); + this._register(this.replaceInput.inputBox.onDidHeightChange(() => this._onDidHeightChange.fire())); this.replaceAllAction = ReplaceAllAction.INSTANCE; this.replaceAllAction.searchWidget = this; @@ -384,7 +386,7 @@ export class SearchWidget extends Widget { this.replaceActionBar.push([this.replaceAllAction], { icon: true, label: false }); this.onkeydown(this.replaceActionBar.domNode, (keyboardEvent) => this.onReplaceActionbarKeyDown(keyboardEvent)); - this.replaceInputFocusTracker = this._register(dom.trackFocus(this.replaceInput.inputElement)); + this.replaceInputFocusTracker = this._register(dom.trackFocus(this.replaceInput.inputBox.inputElement)); this._register(this.replaceInputFocusTracker.onDidFocus(() => this.replaceInputBoxFocused.set(true))); this._register(this.replaceInputFocusTracker.onDidBlur(() => this.replaceInputBoxFocused.set(false))); } @@ -396,8 +398,8 @@ export class SearchWidget extends Widget { private onToggleReplaceButton(): void { dom.toggleClass(this.replaceContainer, 'disabled'); - dom.toggleClass(this.toggleReplaceButton.element, 'collapse'); - dom.toggleClass(this.toggleReplaceButton.element, 'expand'); + dom.toggleClass(this.toggleReplaceButton.element, 'codicon-chevron-right'); + dom.toggleClass(this.toggleReplaceButton.element, 'codicon-chevron-down'); this.toggleReplaceButton.element.setAttribute('aria-expanded', this.isReplaceShown() ? 'true' : 'false'); this.updateReplaceActiveState(); this._onReplaceToggled.fire(); @@ -417,7 +419,7 @@ export class SearchWidget extends Widget { if (currentState !== newState) { this.replaceActive.set(newState); this._onReplaceStateChange.fire(newState); - this.replaceInput.layout(); + this.replaceInput.inputBox.layout(); } } @@ -475,19 +477,11 @@ export class SearchWidget extends Widget { } else if (keyboardEvent.equals(KeyCode.UpArrow)) { - const ta = this.searchInput.domNode.querySelector('textarea'); - const isMultiline = !!this.searchInput.getValue().match(/\n/); - if (ta && isMultiline && ta.selectionStart > 0) { - keyboardEvent.stopPropagation(); - } + stopPropagationForMultiLineUpwards(keyboardEvent, this.searchInput.getValue(), this.searchInput.domNode.querySelector('textarea')); } else if (keyboardEvent.equals(KeyCode.DownArrow)) { - const ta = this.searchInput.domNode.querySelector('textarea'); - const isMultiline = !!this.searchInput.getValue().match(/\n/); - if (ta && isMultiline && ta.selectionEnd < ta.value.length) { - keyboardEvent.stopPropagation(); - } + stopPropagationForMultiLineDownwards(keyboardEvent, this.searchInput.getValue(), this.searchInput.domNode.querySelector('textarea')); } } @@ -513,7 +507,7 @@ export class SearchWidget extends Widget { private onReplaceInputKeyDown(keyboardEvent: IKeyboardEvent) { if (keyboardEvent.equals(ctrlKeyMod | KeyCode.Enter)) { - this.searchInput.inputBox.insertAtCursor('\n'); + this.replaceInput.inputBox.insertAtCursor('\n'); keyboardEvent.preventDefault(); } @@ -533,17 +527,11 @@ export class SearchWidget extends Widget { } else if (keyboardEvent.equals(KeyCode.UpArrow)) { - const ta = this.searchInput.domNode.querySelector('textarea'); - if (ta && ta.selectionStart > 0) { - keyboardEvent.stopPropagation(); - } + stopPropagationForMultiLineUpwards(keyboardEvent, this.replaceInput.getValue(), this.replaceInput.domNode.querySelector('textarea')); } else if (keyboardEvent.equals(KeyCode.DownArrow)) { - const ta = this.searchInput.domNode.querySelector('textarea'); - if (ta && ta.selectionEnd < ta.value.length) { - keyboardEvent.stopPropagation(); - } + stopPropagationForMultiLineDownwards(keyboardEvent, this.replaceInput.getValue(), this.replaceInput.domNode.querySelector('textarea')); } } diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index a1d28e658a..fe63960947 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -107,12 +107,12 @@ export class Match { // If match string is not matching then regex pattern has a lookahead expression if (replaceString === null) { - const fullMatchTextWithTrailingContent = this.fullMatchText(true); - replaceString = searchModel.replacePattern.getReplaceString(fullMatchTextWithTrailingContent, searchModel.preserveCase); + const fullMatchTextWithSurroundingContent = this.fullMatchText(true); + replaceString = searchModel.replacePattern.getReplaceString(fullMatchTextWithSurroundingContent, searchModel.preserveCase); // Search/find normalize line endings - check whether \r prevents regex from matching if (replaceString === null) { - const fullMatchTextWithoutCR = fullMatchTextWithTrailingContent.replace(/\r\n/g, '\n'); + const fullMatchTextWithoutCR = fullMatchTextWithSurroundingContent.replace(/\r\n/g, '\n'); replaceString = searchModel.replacePattern.getReplaceString(fullMatchTextWithoutCR, searchModel.preserveCase); } } @@ -125,17 +125,16 @@ export class Match { return replaceString; } - fullMatchText(includeTrailing = false): string { + fullMatchText(includeSurrounding = false): string { let thisMatchPreviewLines: string[]; - if (includeTrailing) { - thisMatchPreviewLines = this._fullPreviewLines.slice(this._fullPreviewRange.startLineNumber); + if (includeSurrounding) { + thisMatchPreviewLines = this._fullPreviewLines; } else { thisMatchPreviewLines = this._fullPreviewLines.slice(this._fullPreviewRange.startLineNumber, this._fullPreviewRange.endLineNumber + 1); thisMatchPreviewLines[thisMatchPreviewLines.length - 1] = thisMatchPreviewLines[thisMatchPreviewLines.length - 1].slice(0, this._fullPreviewRange.endColumn); - + thisMatchPreviewLines[0] = thisMatchPreviewLines[0].slice(this._fullPreviewRange.startColumn); } - thisMatchPreviewLines[0] = thisMatchPreviewLines[0].slice(this._fullPreviewRange.startColumn); return thisMatchPreviewLines.join('\n'); } diff --git a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts index 754d3f6399..e0b4841f7e 100644 --- a/src/vs/workbench/contrib/search/test/common/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts @@ -33,16 +33,16 @@ suite('SearchResult', () => { test('Line Match', function () { const fileMatch = aFileMatch('folder/file.txt', null!); - const lineMatch = new Match(fileMatch, ['foo bar'], new OneLineRange(0, 0, 3), new OneLineRange(1, 0, 3)); - assert.equal(lineMatch.text(), 'foo bar'); + const lineMatch = new Match(fileMatch, ['0 foo bar'], new OneLineRange(0, 2, 5), new OneLineRange(1, 0, 5)); + assert.equal(lineMatch.text(), '0 foo bar'); assert.equal(lineMatch.range().startLineNumber, 2); assert.equal(lineMatch.range().endLineNumber, 2); assert.equal(lineMatch.range().startColumn, 1); - assert.equal(lineMatch.range().endColumn, 4); - assert.equal('file:///folder/file.txt>[2,1 -> 2,4]foo', lineMatch.id()); + assert.equal(lineMatch.range().endColumn, 6); + assert.equal(lineMatch.id(), 'file:///folder/file.txt>[2,1 -> 2,6]foo'); assert.equal(lineMatch.fullMatchText(), 'foo'); - assert.equal(lineMatch.fullMatchText(true), 'foo bar'); + assert.equal(lineMatch.fullMatchText(true), '0 foo bar'); }); test('Line Match - Remove', function () { diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index 29eb6077b4..0e691e7f0c 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -28,7 +28,7 @@ const languageScopeSchemaId = 'vscode://schemas/snippets'; const languageScopeSchema: IJSONSchema = { id: languageScopeSchemaId, allowComments: true, - allowsTrailingCommas: true, + allowTrailingCommas: true, defaultSnippets: [{ label: nls.localize('snippetSchema.json.default', "Empty snippet"), body: { '${1:snippetName}': { 'prefix': '${2:prefix}', 'body': '${3:snippet}', 'description': '${4:description}' } } @@ -64,7 +64,7 @@ const globalSchemaId = 'vscode://schemas/global-snippets'; const globalSchema: IJSONSchema = { id: globalSchemaId, allowComments: true, - allowsTrailingCommas: true, + allowTrailingCommas: true, defaultSnippets: [{ label: nls.localize('snippetSchema.json.default', "Empty snippet"), body: { '${1:snippetName}': { 'scope': '${2:scope}', 'prefix': '${3:prefix}', 'body': '${4:snippet}', 'description': '${5:description}' } } diff --git a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts index 10d9bb432f..2ce600a0b1 100644 --- a/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/electron-browser/workspaceStats.ts @@ -9,13 +9,13 @@ import { URI } from 'vs/base/common/uri'; import { IFileService, IFileStat } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { endsWith } from 'vs/base/common/strings'; import { ITextFileService, } from 'vs/workbench/services/textfile/common/textfiles'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { IWorkspaceStatsService, Tags } from 'vs/workbench/contrib/stats/common/workspaceStats'; import { IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics'; +import { IRequestService } from 'vs/platform/request/common/request'; const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/; const SshUrlMatcher = /^([^@:]+@)?([^:]+):(.+)$/; @@ -142,7 +142,7 @@ export class WorkspaceStats implements IWorkbenchContribution { @IFileService private readonly fileService: IFileService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IWindowService private readonly windowService: IWindowService, + @IRequestService private readonly requestService: IRequestService, @ITextFileService private readonly textFileService: ITextFileService, @ISharedProcessService private readonly sharedProcessService: ISharedProcessService, @IWorkspaceStatsService private readonly workspaceStatsService: IWorkspaceStatsService @@ -312,7 +312,7 @@ export class WorkspaceStats implements IWorkbenchContribution { } private reportProxyStats() { - this.windowService.resolveProxy('https://www.example.com/') + this.requestService.resolveProxy('https://www.example.com/') .then(proxy => { let type = proxy ? String(proxy).trim().split(/\s+/, 1)[0] : 'EMPTY'; if (['DIRECT', 'PROXY', 'HTTPS', 'SOCKS', 'EMPTY'].indexOf(type) === -1) { diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index aba32f8947..e911936645 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -33,7 +33,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IProgressService, IProgressOptions, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IDialogService, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs'; @@ -129,9 +129,6 @@ interface TaskCustomizationTelemetryEvent { class TaskMap { private _store: Map = new Map(); - constructor() { - } - public forEach(callback: (value: Task[], folder: string) => void): void { this._store.forEach(callback); } @@ -218,7 +215,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer @IStorageService private readonly storageService: IStorageService, @IProgressService private readonly progressService: IProgressService, @IOpenerService private readonly openerService: IOpenerService, - @IWindowService private readonly _windowService: IWindowService, + @IHostService private readonly _hostService: IHostService, @IDialogService private readonly dialogService: IDialogService, @INotificationService private readonly notificationService: INotificationService, @IContextKeyService contextKeyService: IContextKeyService, @@ -252,7 +249,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer ), [{ label: nls.localize('reloadWindow', "Reload Window"), - run: () => this._windowService.reloadWindow() + run: () => this._hostService.reload() }], { sticky: true } ); diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 4f2f507216..db600903c5 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -260,7 +260,7 @@ let schema: IJSONSchema = { id: schemaId, description: 'Task definition file', type: 'object', - allowsTrailingCommas: true, + allowTrailingCommas: true, allowComments: true, default: { version: '2.0.0', diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts index 6e70b59b24..abe55620d4 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts @@ -74,7 +74,8 @@ const dependsOn: IJSONSchema = { ] } } - ] + ], + description: nls.localize('JsonSchema.tasks.dependsOn', 'Either a string representing another task or an array of other tasks that this task depends on.') }; const dependsOrder: IJSONSchema = { diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index 84b3d451bf..8c01a4fb63 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -1239,7 +1239,8 @@ namespace ConfigurationProperties { { property: 'name' }, { property: 'identifier' }, { property: 'group' }, { property: 'isBackground' }, { property: 'promptOnClose' }, { property: 'dependsOn' }, - { property: 'presentation', type: CommandConfiguration.PresentationOptions }, { property: 'problemMatchers' } + { property: 'presentation', type: CommandConfiguration.PresentationOptions }, { property: 'problemMatchers' }, + { property: 'options' } ]; export function from(this: void, external: ConfigurationProperties & { [key: string]: any; }, context: ParseContext, includeCommandOptions: boolean, properties?: IJSONSchemaMap): Tasks.ConfigurationProperties | undefined { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index c5544ead33..35a66859a9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -297,7 +297,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { ], { sticky: true, - neverShowAgain: { id: 'terminalConfigHelper/launchRecommendationsIgnore', scope: NeverShowAgainScope.WORKSPACE }, + neverShowAgain: { id: 'terminalConfigHelper/launchRecommendationsIgnore', scope: NeverShowAgainScope.GLOBAL }, onCancel: () => { /* __GDPR__ "terminalLaunchRecommendation:popup" : { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index 9e15c88582..0ba736bd57 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -22,8 +22,6 @@ export class TerminalInstanceService implements ITerminalInstanceService { private readonly _onRequestDefaultShellAndArgs = new Emitter(); public get onRequestDefaultShellAndArgs(): Event { return this._onRequestDefaultShellAndArgs.event; } - constructor() { } - public async getXtermConstructor(): Promise { if (!Terminal) { Terminal = (await import('xterm')).Terminal; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index 63c065ff2a..151c0ededf 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -238,7 +238,7 @@ export class TerminalLinkHandler { private _handleHypertextLink(url: string): void { const uri = URI.parse(url); - this._openerService.open(uri); + this._openerService.open(uri, { allowTunneling: !!(this._processManager && this._processManager.remoteAuthority) }); } private _isLinkActivationModifierDown(event: MouseEvent): boolean { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts b/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts index c93133c43c..d737d477bc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalNativeService.ts @@ -18,8 +18,6 @@ export class TerminalNativeService implements ITerminalNativeService { private readonly _onOsResume = new Emitter(); public get onOsResume(): Event { return this._onOsResume.event; } - constructor() { } - public whenFileDeleted(): Promise { throw new Error('Not implemented'); } diff --git a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts index 3946d19993..66445b15a4 100644 --- a/src/vs/workbench/contrib/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts @@ -82,7 +82,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } }); - const exectuableVerification = stat(shellLaunchConfig.executable!).then(async stat => { + const executableVerification = stat(shellLaunchConfig.executable!).then(async stat => { if (!stat.isFile() && !stat.isSymbolicLink()) { return Promise.reject(stat.isDirectory() ? SHELL_PATH_DIRECTORY_EXIT_CODE : SHELL_PATH_INVALID_EXIT_CODE); } @@ -98,7 +98,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } }); - Promise.all([cwdVerification, exectuableVerification]).then(() => { + Promise.all([cwdVerification, executableVerification]).then(() => { this.setupPtyProcess(shellLaunchConfig, options); }).catch((exitCode: number) => { return this._launchFailed(exitCode); diff --git a/src/vs/workbench/contrib/update/browser/media/markdown.css b/src/vs/workbench/contrib/update/browser/media/markdown.css index bcd5ae17e3..5fbef60ceb 100644 --- a/src/vs/workbench/contrib/update/browser/media/markdown.css +++ b/src/vs/workbench/contrib/update/browser/media/markdown.css @@ -81,11 +81,6 @@ code { line-height: 19px; } -.mac code { - font-size: 12px; - line-height: 18px; -} - code > div { padding: 16px; border-radius: 3px; @@ -98,9 +93,6 @@ code > div { /** Theming */ - - - .vscode-light code > div { background-color: rgba(220, 220, 220, 0.4); } diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index 6b419e2537..638bfb73d7 100644 --- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @@ -4,12 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { onUnexpectedError } from 'vs/base/common/errors'; -import * as marked from 'vs/base/common/marked/marked'; import { OS } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { TokenizationRegistry, ITokenizationSupport } from 'vs/editor/common/modes'; +import { TokenizationRegistry } from 'vs/editor/common/modes'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; -import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { IModeService } from 'vs/editor/common/services/modeService'; import * as nls from 'vs/nls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -27,6 +25,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { generateUuid } from 'vs/base/common/uuid'; +import { renderMarkdownDocument } from 'vs/workbench/common/markdownDocumentRenderer'; export class ReleaseNotesManager { @@ -179,7 +178,7 @@ export class ReleaseNotesManager { } private async renderBody(text: string) { - const content = await this.renderContent(text); + const content = await renderMarkdownDocument(text, this._extensionService, this._modeService); const colorMap = TokenizationRegistry.getColorMap(); const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-resource://'); @@ -195,33 +194,4 @@ export class ReleaseNotesManager { ${content} `; } - - private async renderContent(text: string): Promise { - const renderer = await this.getRenderer(text); - return marked(text, { renderer }); - } - - private async getRenderer(text: string): Promise { - let result: Promise[] = []; - const renderer = new marked.Renderer(); - renderer.code = (_code, lang) => { - const modeId = this._modeService.getModeIdForLanguageName(lang); - if (modeId) { - result.push(this._extensionService.whenInstalledExtensionsRegistered().then(() => { - this._modeService.triggerMode(modeId); - return TokenizationRegistry.getPromise(modeId); - })); - } - return ''; - }; - - marked(text, { renderer }); - await Promise.all(result); - - renderer.code = (code, lang) => { - const modeId = this._modeService.getModeIdForLanguageName(lang); - return `${tokenizeToString(code, modeId ? TokenizationRegistry.get(modeId)! : undefined)}`; - }; - return renderer; - } } diff --git a/src/vs/workbench/contrib/url/common/externalUriResolver.ts b/src/vs/workbench/contrib/url/common/externalUriResolver.ts index 1a86f56aa2..bcab1149e9 100644 --- a/src/vs/workbench/contrib/url/common/externalUriResolver.ts +++ b/src/vs/workbench/contrib/url/common/externalUriResolver.ts @@ -18,7 +18,12 @@ export class ExternalUriResolverContribution extends Disposable implements IWork if (_workbenchEnvironmentService.options && _workbenchEnvironmentService.options.resolveExternalUri) { this._register(_openerService.registerExternalUriResolver({ resolveExternalUri: async (resource) => { - return _workbenchEnvironmentService.options!.resolveExternalUri!(resource); + return { + resolved: await _workbenchEnvironmentService.options!.resolveExternalUri!(resource), + dispose: () => { + // TODO + } + }; } })); } diff --git a/src/vs/workbench/contrib/url/common/trustedDomains.ts b/src/vs/workbench/contrib/url/common/trustedDomains.ts index c66f207570..1cb1b7d428 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomains.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomains.ts @@ -13,10 +13,10 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic const TRUSTED_DOMAINS_URI = URI.parse('trustedDomains:/Trusted Domains'); -export const configureTrustedDomainSettingsCommand = { - id: 'workbench.action.configureTrustedDomain', +export const manageTrustedDomainSettingsCommand = { + id: 'workbench.action.manageTrustedDomain', description: { - description: localize('trustedDomain.configureTrustedDomain', 'Configure Trusted Domains'), + description: localize('trustedDomain.manageTrustedDomain', 'Manage Trusted Domains'), args: [] }, handler: async (accessor: ServicesAccessor) => { @@ -34,42 +34,41 @@ export async function configureOpenerTrustedDomainsHandler( editorService: IEditorService ) { const parsedDomainToConfigure = URI.parse(domainToConfigure); - const toplevelDomainSegements = domainToConfigure.split('.'); + const toplevelDomainSegements = parsedDomainToConfigure.authority.split('.'); const domainEnd = toplevelDomainSegements.slice(toplevelDomainSegements.length - 2).join('.'); - const topLevelDomain = parsedDomainToConfigure.scheme + '://' + '*.' + domainEnd; + const topLevelDomain = '*.' + domainEnd; const trustDomainAndOpenLinkItem: IQuickPickItem = { type: 'item', - label: localize('trustedDomain.trustDomainAndOpenLink', 'Trust {0} and open link', domainToConfigure), + label: localize('trustedDomain.trustDomain', 'Trust {0}', domainToConfigure), id: domainToConfigure, picked: true }; const trustSubDomainAndOpenLinkItem: IQuickPickItem = { type: 'item', - label: localize('trustedDomain.trustSubDomainAndOpenLink', 'Trust all domains ending in {0} and open link', domainEnd), + label: localize('trustedDomain.trustSubDomain', 'Trust {0} and all its subdomains', domainEnd), id: topLevelDomain }; const openAllLinksItem: IQuickPickItem = { type: 'item', - label: localize('trustedDomain.trustAllAndOpenLink', 'Disable Link Protection and open link'), - id: '*', - picked: trustedDomains.indexOf('*') !== -1 + label: localize('trustedDomain.trustAllDomains', 'Trust all domains (disables link protection)'), + id: '*' }; - const configureTrustedDomainItem: IQuickPickItem = { + const manageTrustedDomainItem: IQuickPickItem = { type: 'item', - label: localize('trustedDomain.configureTrustedDomains', 'Configure Trusted Domains'), - id: 'configure' + label: localize('trustedDomain.manageTrustedDomains', 'Manage Trusted Domains'), + id: 'manage' }; const pickedResult = await quickInputService.pick( - [trustDomainAndOpenLinkItem, trustSubDomainAndOpenLinkItem, openAllLinksItem, configureTrustedDomainItem], + [trustDomainAndOpenLinkItem, trustSubDomainAndOpenLinkItem, openAllLinksItem, manageTrustedDomainItem], { activeItem: trustDomainAndOpenLinkItem } ); if (pickedResult) { - if (pickedResult.id === 'configure') { + if (pickedResult.id === 'manage') { editorService.openEditor({ resource: TRUSTED_DOMAINS_URI, mode: 'jsonc' @@ -91,10 +90,11 @@ export async function configureOpenerTrustedDomainsHandler( } export function readTrustedDomains(storageService: IStorageService, productService: IProductService) { - let trustedDomains: string[] = productService.linkProtectionTrustedDomains + const defaultTrustedDomains: string[] = productService.linkProtectionTrustedDomains ? [...productService.linkProtectionTrustedDomains] : []; + let trustedDomains: string[] = []; try { const trustedDomainsSrc = storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); if (trustedDomainsSrc) { @@ -102,5 +102,8 @@ export function readTrustedDomains(storageService: IStorageService, productServi } } catch (err) { } - return trustedDomains; + return { + defaultTrustedDomains, + trustedDomains + }; } diff --git a/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts b/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts index 0be5c4830e..57e3928061 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider.ts @@ -21,6 +21,8 @@ import { import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { VSBuffer } from 'vs/base/common/buffer'; +import { readTrustedDomains } from 'vs/workbench/contrib/url/common/trustedDomains'; +import { IProductService } from 'vs/platform/product/common/productService'; const TRUSTED_DOMAINS_SCHEMA = 'trustedDomains'; @@ -31,28 +33,47 @@ const TRUSTED_DOMAINS_STAT: IStat = { 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. +const CONFIG_HELP_TEXT_PRE = + `// Links matching one or more entries in the list below can be opened without link protection. +// The following examples show what entries can look like: +// - "https://microsoft.com": Matches this specific domain using https +// - "https://*.microsoft.com": Match all domains ending in "microsoft.com" using https +// - "microsoft.com": Match this specific domain using either http or https +// - "*.microsoft.com": Match all domains ending in "microsoft.com" using either http or https +// - "*": Match all domains using either http or https // -// 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; +const CONFIG_HELP_TEXT_AFTER = `// +// You can use the "Manage Trusted Domains" command to open this file. +// Save this file to apply the trusted domains rules. +`; + +const CONFIG_PLACEHOLDER_TEXT = `[ + // "https://microsoft.com" +]`; + +function computeTrustedDomainContent(defaultTrustedDomains: string[], trustedDomains: string[]) { + let content = CONFIG_HELP_TEXT_PRE; + + if (defaultTrustedDomains.length > 0) { + content += `// By default, VS Code trusts "localhost" as well as the following domains:\n`; + defaultTrustedDomains.forEach(d => { + content += `// - "${d}"\n`; + }); + } else { + content += `// By default, VS Code trusts "localhost".\n`; } - return CONFIG_HELP_TEXT + JSON.stringify(trustedDomains, null, 2); + content += CONFIG_HELP_TEXT_AFTER; + + if (trustedDomains.length === 0) { + content += CONFIG_PLACEHOLDER_TEXT; + } else { + content += JSON.stringify(trustedDomains, null, 2); + } + + return content; } export class TrustedDomainsFileSystemProvider implements IFileSystemProvider, IWorkbenchContribution { @@ -63,7 +84,8 @@ export class TrustedDomainsFileSystemProvider implements IFileSystemProvider, IW constructor( @IFileService private readonly fileService: IFileService, - @IStorageService private readonly storageService: IStorageService + @IStorageService private readonly storageService: IStorageService, + @IProductService private readonly productService: IProductService ) { this.fileService.registerProvider(TRUSTED_DOMAINS_SCHEMA, this); } @@ -73,33 +95,24 @@ export class TrustedDomainsFileSystemProvider implements IFileSystemProvider, IW } readFile(resource: URI): Promise { - let trustedDomains: string[] = []; + const { defaultTrustedDomains, trustedDomains } = readTrustedDomains(this.storageService, this.productService); - try { - const trustedDomainsSrc = this.storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); - if (trustedDomainsSrc) { - trustedDomains = JSON.parse(trustedDomainsSrc); - } - } catch (err) { } - - - const trustedDomainsContent = computeTrustedDomainContent(trustedDomains); + const trustedDomainsContent = computeTrustedDomainContent(defaultTrustedDomains, 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) { } + const trustedDomains = parse(content.toString()); - this.storageService.store( - 'http.linkProtectionTrustedDomains', - JSON.stringify(trustedDomainsd), - StorageScope.GLOBAL - ); + this.storageService.store( + 'http.linkProtectionTrustedDomains', + JSON.stringify(trustedDomains), + StorageScope.GLOBAL + ); + + } catch (err) { } return Promise.resolve(); } diff --git a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts index 28901eb85c..f774c2a940 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts @@ -37,9 +37,10 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { } const domainToOpen = `${scheme}://${authority}`; - const trustedDomains = readTrustedDomains(this._storageService, this._productService); + const { defaultTrustedDomains, trustedDomains } = readTrustedDomains(this._storageService, this._productService); + const allTrustedDomains = [...defaultTrustedDomains, ...trustedDomains]; - if (isURLDomainTrusted(resource, trustedDomains)) { + if (isURLDomainTrusted(resource, allTrustedDomains)) { return true; } else { const { choice } = await this._dialogService.show( @@ -78,7 +79,7 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { return true; } // Trust current domain - if (pickedDomains.indexOf(domainToOpen) !== -1) { + if (isURLDomainTrusted(resource, pickedDomains)) { return true; } return false; @@ -120,25 +121,37 @@ export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { 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); - } + let parsedTrustedDomain; + if (/^https?:\/\//.test(trustedDomains[i])) { + parsedTrustedDomain = URI.parse(trustedDomains[i]); + if (url.scheme !== parsedTrustedDomain.scheme) { + continue; + } + } else { + parsedTrustedDomain = URI.parse('https://' + trustedDomains[i]); + } - if ( - reversedAuthoritySegments.every((val, i) => { - return reversedTrustedDomainAuthoritySegments[i] === '*' || val === reversedTrustedDomainAuthoritySegments[i]; - }) - ) { - return true; - } + if (url.authority === parsedTrustedDomain.authority) { + return true; + } + + if (trustedDomains[i].indexOf('*') !== -1) { + 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; } } } diff --git a/src/vs/workbench/contrib/url/common/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts index 1e65e36da2..546e032f8d 100644 --- a/src/vs/workbench/contrib/url/common/url.contribution.ts +++ b/src/vs/workbench/contrib/url/common/url.contribution.ts @@ -15,7 +15,7 @@ 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 { manageTrustedDomainSettingsCommand } 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'; @@ -50,11 +50,11 @@ Registry.as(ActionExtensions.WorkbenchActions).registe * Trusted Domains Contribution */ -CommandsRegistry.registerCommand(configureTrustedDomainSettingsCommand); +CommandsRegistry.registerCommand(manageTrustedDomainSettingsCommand); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { - id: configureTrustedDomainSettingsCommand.id, - title: configureTrustedDomainSettingsCommand.description.description + id: manageTrustedDomainSettingsCommand.id, + title: manageTrustedDomainSettingsCommand.description.description } }); diff --git a/src/vs/workbench/contrib/userDataSync/browser/media/check-dark.svg b/src/vs/workbench/contrib/userDataSync/browser/media/check-dark.svg new file mode 100644 index 0000000000..2d16f39007 --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/media/check-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/userDataSync/browser/media/check-light.svg b/src/vs/workbench/contrib/userDataSync/browser/media/check-light.svg new file mode 100644 index 0000000000..a9f8aa131b --- /dev/null +++ b/src/vs/workbench/contrib/userDataSync/browser/media/check-light.svg @@ -0,0 +1,3 @@ + + + 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 deleted file mode 100644 index 72695bb2e5..0000000000 --- a/src/vs/workbench/contrib/userDataSync/browser/media/sync-push-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - 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 deleted file mode 100644 index 82cbddecbc..0000000000 --- a/src/vs/workbench/contrib/userDataSync/browser/media/sync-push-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts index 5cc05ba395..3533ba433c 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.contribution.ts @@ -4,16 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IUserDataSyncService, SyncStatus, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, registerConfiguration } 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 { 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'; @@ -29,24 +28,18 @@ 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'; +import { IProductService } from 'vs/platform/product/common/productService'; -const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized); +class UserDataSyncConfigurationContribution implements IWorkbenchContribution { -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 - } + constructor( + @IProductService productService: IProductService + ) { + if (productService.settingsSyncStoreUrl) { + registerConfiguration(); } - }); + } +} class UserDataAutoSyncContribution extends Disposable implements IWorkbenchContribution { @@ -60,8 +53,8 @@ class UserDataAutoSyncContribution extends Disposable implements IWorkbenchContr } } -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`)); +const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-light.svg`)); +const SYNC_PUSH_DARK_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-dark.svg`)); class SyncActionsContribution extends Disposable implements IWorkbenchContribution { private readonly syncEnablementContext: IContextKey; @@ -231,5 +224,6 @@ class SyncActionsContribution extends Disposable implements IWorkbenchContributi } const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(SyncActionsContribution, LifecyclePhase.Starting); +workbenchRegistry.registerWorkbenchContribution(UserDataSyncConfigurationContribution, LifecyclePhase.Starting); +workbenchRegistry.registerWorkbenchContribution(SyncActionsContribution, LifecyclePhase.Restored); workbenchRegistry.registerWorkbenchContribution(UserDataAutoSyncContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts index 4ff6dea781..e7a6fe17ed 100644 --- a/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts +++ b/src/vs/workbench/contrib/webview/browser/dynamicWebviewEditorOverlay.ts @@ -10,6 +10,7 @@ import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IWebviewService, Webview, WebviewContentOptions, WebviewEditorOverlay, WebviewElement, WebviewOptions } from 'vs/workbench/contrib/webview/browser/webview'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { Dimension } from 'vs/base/browser/dom'; /** * Webview editor overlay that creates and destroys the underlying webview as needed. @@ -62,6 +63,19 @@ export class DynamicWebviewEditorOverlay extends Disposable implements WebviewEd } } + public layoutWebviewOverElement(element: HTMLElement, dimension?: Dimension) { + if (!this.container || !this.container.parentElement) { + return; + } + const frameRect = element.getBoundingClientRect(); + const containerRect = this.container.parentElement.getBoundingClientRect(); + this.container.style.position = 'absolute'; + this.container.style.top = `${frameRect.top - containerRect.top}px`; + this.container.style.left = `${frameRect.left - containerRect.left}px`; + this.container.style.width = `${dimension ? dimension.width : frameRect.width}px`; + this.container.style.height = `${dimension ? dimension.height : frameRect.height}px`; + } + private show() { if (!this._webview.value) { const webview = this._webviewService.createWebview(this.id, this.options, this._contentOptions); diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 9c4e2fc873..437b18ea51 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Dimension } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -100,6 +101,8 @@ export interface WebviewEditorOverlay extends Webview { release(owner: any): void; getInnerWebview(): Webview | undefined; + + layoutWebviewOverElement(element: HTMLElement, dimension?: Dimension): void; } export const webviewDeveloperCategory = nls.localize('developer', "Developer"); diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts index fb779c4e98..0e78c9008a 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditor.ts @@ -182,15 +182,8 @@ export class WebviewEditor extends BaseEditor { } private synchronizeWebviewContainerDimensions(webview: WebviewEditorOverlay, dimension?: DOM.Dimension) { - const webviewContainer = webview.container; - if (webviewContainer && webviewContainer.parentElement && this._editorFrame) { - const frameRect = this._editorFrame.getBoundingClientRect(); - const containerRect = webviewContainer.parentElement.getBoundingClientRect(); - webviewContainer.style.position = 'absolute'; - webviewContainer.style.top = `${frameRect.top - containerRect.top}px`; - webviewContainer.style.left = `${frameRect.left - containerRect.left}px`; - webviewContainer.style.width = `${dimension ? dimension.width : frameRect.width}px`; - webviewContainer.style.height = `${dimension ? dimension.height : frameRect.height}px`; + if (this._editorFrame) { + webview.layoutWebviewOverElement(this._editorFrame, dimension); } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts index 4f789df57d..45395faf09 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInputFactory.ts @@ -17,6 +17,7 @@ interface SerializedIconPath { } interface SerializedWebview { + readonly id?: string; readonly viewType: string; readonly title: string; readonly options: WebviewInputOptions; @@ -53,7 +54,7 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { serializedEditorInput: string ): WebviewInput { const data = this.fromJson(serializedEditorInput); - return this._webviewService.reviveWebview(generateUuid(), data.viewType, data.title, data.iconPath, data.state, data.options, data.extensionLocation ? { + return this._webviewService.reviveWebview(data.id || generateUuid(), data.viewType, data.title, data.iconPath, data.state, data.options, data.extensionLocation ? { location: data.extensionLocation, id: data.extensionId } : undefined, data.group); @@ -72,6 +73,7 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { protected toJson(input: WebviewInput): SerializedWebview { return { + id: input.id, viewType: input.viewType, title: input.getName(), options: { ...input.webview.options, ...input.webview.contentOptions }, @@ -109,20 +111,6 @@ function reviveUri(data: string | UriComponents | undefined): URI | undefined { } } - function reviveState(state: unknown | undefined): undefined | string { - if (!state) { - return undefined; - } - - if (typeof state === 'string') { - return state; - } - - // Likely an old style state. Unwrap to a simple state object - // Remove after 1.37 - if ('state' in (state as any) && typeof (state as any).state === 'string') { - return (state as any).state; - } - return undefined; + return typeof state === 'string' ? state : undefined; } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts index d4c223849f..16798e07b3 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorService.ts @@ -232,11 +232,6 @@ export class WebviewEditorService implements IWebviewEditorService { public shouldPersist( webview: WebviewInput ): boolean { - // Has no state, don't persist - if (!webview.webview.state) { - return false; - } - if (values(this._revivers).some(reviver => canRevive(reviver, webview))) { return true; } diff --git a/src/vs/workbench/contrib/webview/common/portMapping.ts b/src/vs/workbench/contrib/webview/common/portMapping.ts index 371837a259..a50e4e552b 100644 --- a/src/vs/workbench/contrib/webview/common/portMapping.ts +++ b/src/vs/workbench/contrib/webview/common/portMapping.ts @@ -7,21 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -import { ITunnelService, RemoteTunnel } from 'vs/platform/remote/common/tunnel'; - -export function extractLocalHostUriMetaDataForPortMapping(uri: URI): { address: string, port: number } | undefined { - if (uri.scheme !== 'http' && uri.scheme !== 'https') { - return undefined; - } - const localhostMatch = /^(localhost|127\.0\.0\.1):(\d+)$/.exec(uri.authority); - if (!localhostMatch) { - return undefined; - } - return { - address: localhostMatch[1], - port: +localhostMatch[2], - }; -} +import { ITunnelService, RemoteTunnel, extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/remote/common/tunnel'; export class WebviewPortMappingManager extends Disposable { diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index 7cc1ef28bf..7cfb3cd277 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -10,6 +10,7 @@ import { WebviewEditor } from 'vs/workbench/contrib/webview/browser/webviewEdito import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ElectronWebviewBasedWebview } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewTag } from 'electron'; export class OpenWebviewDeveloperToolsAction extends Action { static readonly ID = 'workbench.action.webview.openDeveloperTools'; @@ -24,7 +25,7 @@ export class OpenWebviewDeveloperToolsAction extends Action { const elements = document.querySelectorAll('webview.ready'); for (let i = 0; i < elements.length; i++) { try { - (elements.item(i) as Electron.WebviewTag).openDevTools(); + (elements.item(i) as WebviewTag).openDevTools(); } catch (e) { console.error(e); } diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index e8eef6536e..cf248edcb5 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OnBeforeRequestDetails, OnHeadersReceivedDetails, Response } from 'electron'; +import { OnBeforeRequestDetails, OnHeadersReceivedDetails, Response, WebviewTag, WebContents, FindInPageOptions } from 'electron'; import { addClass, addDisposableListener } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; @@ -46,7 +46,7 @@ class WebviewSession extends Disposable { private readonly _onHeadersReceivedDelegates: Array = []; public constructor( - webview: Electron.WebviewTag, + webview: WebviewTag, ) { super(); @@ -91,7 +91,7 @@ class WebviewSession extends Disposable { class WebviewProtocolProvider extends Disposable { constructor( - webview: Electron.WebviewTag, + webview: WebviewTag, private readonly _extensionLocation: URI | undefined, private readonly _getLocalResourceRoots: () => ReadonlyArray, private readonly _fileService: IFileService, @@ -106,7 +106,7 @@ class WebviewProtocolProvider extends Disposable { }))); } - private registerProtocols(contents: Electron.WebContents) { + private registerProtocols(contents: WebContents) { if (contents.isDestroyed()) { return; } @@ -142,7 +142,7 @@ class WebviewKeyboardHandler extends Disposable { private _ignoreMenuShortcut = false; constructor( - private readonly _webview: Electron.WebviewTag + private readonly _webview: WebviewTag ) { super(); @@ -194,7 +194,7 @@ class WebviewKeyboardHandler extends Disposable { } } - private getWebContents(): Electron.WebContents | undefined { + private getWebContents(): WebContents | undefined { const contents = this._webview.getWebContents(); if (contents && !contents.isDestroyed()) { return contents; @@ -221,7 +221,7 @@ interface WebviewContent { } export class ElectronWebviewBasedWebview extends Disposable implements Webview, WebviewFindDelegate { - private _webview: Electron.WebviewTag | undefined; + private _webview: WebviewTag | undefined; private _ready: Promise; private _webviewFindWidget: WebviewFindWidget | undefined; @@ -584,7 +584,7 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview, private readonly _hasFindResult = this._register(new Emitter()); public readonly hasFindResult: Event = this._hasFindResult.event; - public startFind(value: string, options?: Electron.FindInPageOptions) { + public startFind(value: string, options?: FindInPageOptions) { if (!value || !this._webview) { return; } @@ -593,7 +593,7 @@ export class ElectronWebviewBasedWebview extends Disposable implements Webview, options = options || {}; // FindNext must be false for a first request - const findOptions: Electron.FindInPageOptions = { + const findOptions: FindInPageOptions = { forward: options.forward, findNext: false, matchCase: options.matchCase, diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts index dd87d038e1..89380ee15d 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import { GettingStarted } from './gettingStarted'; import { TelemetryOptOut } from './telemetryOptOut'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -16,10 +15,6 @@ import { EnablePreviewFeatures } from 'sql/workbench/common/enablePreviewFeature // .as(WorkbenchExtensions.Workbench) // .registerWorkbenchContribution(GettingStarted, LifecyclePhase.Running); -Registry - .as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(GettingStarted, LifecyclePhase.Restored); - Registry .as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(TelemetryOptOut, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts index 43a4709709..fbde988eca 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts @@ -11,7 +11,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { URI } from 'vs/base/common/uri'; 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 { IExperimentService, ExperimentState } from 'vs/workbench/contrib/experiments/common/experimentService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { language, locale } from 'vs/base/common/platform'; @@ -30,7 +30,6 @@ export class TelemetryOptOut implements IWorkbenchContribution { @IOpenerService openerService: IOpenerService, @INotificationService private readonly notificationService: INotificationService, @IWindowService windowService: IWindowService, - @IWindowsService windowsService: IWindowsService, @IHostService hostService: IHostService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IExperimentService private readonly experimentService: IExperimentService, diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/openWebsite.contribution.ts b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/openWebsite.contribution.ts new file mode 100644 index 0000000000..a25d14b701 --- /dev/null +++ b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/openWebsite.contribution.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/registry/common/platform'; +import { OpenWelcomePageInBrowser } from './openWebsite'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; + +Registry + .as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(OpenWelcomePageInBrowser, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/openWebsite.ts similarity index 89% rename from src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts rename to src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/openWebsite.ts index 2e09732554..2409dbeeb4 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/openWebsite.ts @@ -12,7 +12,7 @@ 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 { +export class OpenWelcomePageInBrowser implements IWorkbenchContribution { private static readonly hideWelcomeSettingskey = 'workbench.hide.welcome'; @@ -59,13 +59,13 @@ export class GettingStarted implements IWorkbenchContribution { return; } - let firstStartup = !this.storageService.get(GettingStarted.hideWelcomeSettingskey, StorageScope.GLOBAL); + let firstStartup = !this.storageService.get(OpenWelcomePageInBrowser.hideWelcomeSettingskey, StorageScope.GLOBAL); if (firstStartup && this.welcomePageURL) { this.telemetryService.getTelemetryInfo().then(info => { let url = this.getUrl(info); this.openExternal(url); - this.storageService.store(GettingStarted.hideWelcomeSettingskey, true, StorageScope.GLOBAL); + this.storageService.store(OpenWelcomePageInBrowser.hideWelcomeSettingskey, true, StorageScope.GLOBAL); }); } } diff --git a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 14df4ed1cc..4712610f47 100644 --- a/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -44,6 +44,7 @@ import { IRecentlyOpened, isRecentWorkspace, IRecentWorkspace, IRecentFolder, is import { CancellationToken } from 'vs/base/common/cancellation'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; // {{SQL CARBON EDIT}} import { setProductQuality } from 'sql/workbench/contrib/welcome/page/browser/az_data_welcome_page'; // {{SQL CARBON EDIT}} +import { IHostService } from 'vs/workbench/services/host/browser/host'; const configurationKey = 'workbench.startupEditor'; const oldConfigurationKey = 'workbench.welcome.enabled'; @@ -265,7 +266,8 @@ class WelcomePage extends Disposable { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @ILifecycleService lifecycleService: ILifecycleService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IEnvironmentService private readonly environmentService: IEnvironmentService // {{SQL CARBON EDIT}} + @IEnvironmentService private readonly environmentService: IEnvironmentService, // {{SQL CARBON EDIT}} + @IHostService private readonly hostService: IHostService ) { super(); this._register(lifecycleService.onShutdown(() => this.dispose())); @@ -495,7 +497,7 @@ class WelcomePage extends Disposable { extensionId: extensionSuggestion.id, outcome: installedExtension ? 'enabled' : 'installed', }); - return this.windowService.reloadWindow(); + return this.hostService.reload(); }); } else { /* __GDPR__FRAGMENT__ diff --git a/src/vs/workbench/electron-browser/actions/developerActions.ts b/src/vs/workbench/electron-browser/actions/developerActions.ts index d851036c61..8a91a1f085 100644 --- a/src/vs/workbench/electron-browser/actions/developerActions.ts +++ b/src/vs/workbench/electron-browser/actions/developerActions.ts @@ -4,20 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import { Action } from 'vs/base/common/actions'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import * as nls from 'vs/nls'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; export class ToggleDevToolsAction extends Action { static readonly ID = 'workbench.action.toggleDevTools'; static LABEL = nls.localize('toggleDevTools', "Toggle Developer Tools"); - constructor(id: string, label: string, @IWindowService private readonly windowsService: IWindowService) { + constructor(id: string, label: string, @IElectronService private readonly electronService: IElectronService) { super(id, label); } run(): Promise { - return this.windowsService.toggleDevTools(); + return this.electronService.toggleDevTools(); } } @@ -26,11 +27,11 @@ export class ToggleSharedProcessAction extends Action { static readonly ID = 'workbench.action.toggleSharedProcess'; static LABEL = nls.localize('toggleSharedProcess', "Toggle Shared Process"); - constructor(id: string, label: string, @IWindowsService private readonly windowsService: IWindowsService) { + constructor(id: string, label: string, @ISharedProcessService private readonly sharedProcessService: ISharedProcessService) { super(id, label); } run(): Promise { - return this.windowsService.toggleSharedProcess(); + return this.sharedProcessService.toggleSharedProcessWindow(); } } diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index 48afbd50ad..01ff746f5e 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -18,38 +18,25 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IElectronService } from 'vs/platform/electron/node/electron'; export class CloseCurrentWindowAction extends Action { static readonly ID = 'workbench.action.closeWindow'; static readonly LABEL = nls.localize('closeWindow', "Close Window"); - constructor(id: string, label: string, @IWindowService private readonly windowService: IWindowService) { - super(id, label); - } - - run(): Promise { - this.windowService.closeWindow(); - - return Promise.resolve(true); - } -} - -export class NewWindowAction extends Action { - - static readonly ID = 'workbench.action.newWindow'; - static LABEL = nls.localize('newWindow', "New Window"); - constructor( id: string, label: string, - @IWindowsService private readonly windowsService: IWindowsService + @IElectronService private readonly electronService: IElectronService ) { super(id, label); } - run(): Promise { - return this.windowsService.openNewWindow(); + run(): Promise { + this.electronService.closeWindow(); + + return Promise.resolve(true); } } @@ -150,21 +137,21 @@ export class ZoomResetAction extends BaseZoomAction { } } -export class ReloadWindowWithExtensionsDisabledAction extends Action { +export class RestartWithExtensionsDisabledAction extends Action { - static readonly ID = 'workbench.action.reloadWindowWithExtensionsDisabled'; - static LABEL = nls.localize('reloadWindowWithExntesionsDisabled', "Reload Window With Extensions Disabled"); + static readonly ID = 'workbench.action.restartWithExtensionsDisabled'; + static LABEL = nls.localize('restartWithExtensionsDisabled', "Restart With Extensions Disabled"); constructor( id: string, label: string, - @IWindowService private readonly windowService: IWindowService + @IElectronService private readonly electronService: IElectronService ) { super(id, label); } async run(): Promise { - await this.windowService.reloadWindow({ _: [], 'disable-extensions': true }); + await this.electronService.relaunch({ addArgs: ['--disable-extensions'] }); return true; } @@ -186,6 +173,7 @@ export abstract class BaseSwitchWindow extends Action { private keybindingService: IKeybindingService, private modelService: IModelService, private modeService: IModeService, + private electronService: IElectronService ) { super(id, label); @@ -217,7 +205,7 @@ export abstract class BaseSwitchWindow extends Action { placeHolder, quickNavigate: this.isQuickNavigate() ? { keybindings: this.keybindingService.lookupKeybindings(this.id) } : undefined, onDidTriggerItemButton: async context => { - await this.windowsService.closeWindow(context.item.payload); + await this.electronService.closeWindow(); context.removeItem(); } }); @@ -242,8 +230,9 @@ export class SwitchWindow extends BaseSwitchWindow { @IKeybindingService keybindingService: IKeybindingService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, + @IElectronService electronService: IElectronService ) { - super(id, label, windowsService, windowService, quickInputService, keybindingService, modelService, modeService); + super(id, label, windowsService, windowService, quickInputService, keybindingService, modelService, modeService, electronService); } protected isQuickNavigate(): boolean { @@ -265,8 +254,9 @@ export class QuickSwitchWindow extends BaseSwitchWindow { @IKeybindingService keybindingService: IKeybindingService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, + @IElectronService electronService: IElectronService ) { - super(id, label, windowsService, windowService, quickInputService, keybindingService, modelService, modeService); + super(id, label, windowsService, windowService, quickInputService, keybindingService, modelService, modeService, electronService); } protected isQuickNavigate(): boolean { @@ -275,25 +265,25 @@ export class QuickSwitchWindow extends BaseSwitchWindow { } export const NewWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) { - return accessor.get(IWindowsService).newWindowTab(); + return accessor.get(IElectronService).newWindowTab(); }; export const ShowPreviousWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) { - return accessor.get(IWindowsService).showPreviousWindowTab(); + return accessor.get(IElectronService).showPreviousWindowTab(); }; export const ShowNextWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) { - return accessor.get(IWindowsService).showNextWindowTab(); + return accessor.get(IElectronService).showNextWindowTab(); }; export const MoveWindowTabToNewWindowHandler: ICommandHandler = function (accessor: ServicesAccessor) { - return accessor.get(IWindowsService).moveWindowTabToNewWindow(); + return accessor.get(IElectronService).moveWindowTabToNewWindow(); }; export const MergeWindowTabsHandlerHandler: ICommandHandler = function (accessor: ServicesAccessor) { - return accessor.get(IWindowsService).mergeAllWindowTabs(); + return accessor.get(IElectronService).mergeAllWindowTabs(); }; export const ToggleWindowTabsBarHandler: ICommandHandler = function (accessor: ServicesAccessor) { - return accessor.get(IWindowsService).toggleWindowTabsBar(); + return accessor.get(IElectronService).toggleWindowTabsBar(); }; diff --git a/src/vs/workbench/electron-browser/actions/workspaceActions.ts b/src/vs/workbench/electron-browser/actions/workspaceActions.ts index 097be36376..47bd64b356 100644 --- a/src/vs/workbench/electron-browser/actions/workspaceActions.ts +++ b/src/vs/workbench/electron-browser/actions/workspaceActions.ts @@ -10,7 +10,6 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/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 { @@ -69,29 +68,3 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { 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 aa3da69af5..324554cedf 100644 --- a/src/vs/workbench/electron-browser/desktop.contribution.ts +++ b/src/vs/workbench/electron-browser/desktop.contribution.ts @@ -9,18 +9,18 @@ import * as os from 'os'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; +import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; 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 { SaveWorkspaceAsAction, DuplicateWorkspaceInNewWindowAction, CloseWorkspaceAction } from 'vs/workbench/electron-browser/actions/workspaceActions'; +import { ZoomResetAction, ZoomOutAction, ZoomInAction, CloseCurrentWindowAction, SwitchWindow, QuickSwitchWindow, RestartWithExtensionsDisabledAction, NewWindowTabHandler, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions'; +import { SaveWorkspaceAsAction, DuplicateWorkspaceInNewWindowAction } 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 { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext, WorkbenchStateContext, WorkspaceFolderCountContext } from 'vs/workbench/browser/contextkeys'; +import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext } from 'vs/workbench/browser/contextkeys'; import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IElectronService } from 'vs/platform/electron/node/electron'; import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; // {{SQL CARBON EDIT}} add import @@ -28,13 +28,6 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten (function registerActions(): void { const registry = Registry.as(Extensions.WorkbenchActions); - // Actions: File - (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, SupportsWorkspacesContext); - })(); - // Actions: View (function registerViewActions(): void { const viewCategory = nls.localize('view', "View"); @@ -46,7 +39,6 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten // Actions: Window (function registerWindowActions(): void { - registry.registerWorkbenchAction(new SyncActionDescriptor(NewWindowAction, NewWindowAction.ID, NewWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window'); registry.registerWorkbenchAction(new SyncActionDescriptor(CloseCurrentWindowAction, CloseCurrentWindowAction.ID, CloseCurrentWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W }), 'Close Window'); registry.registerWorkbenchAction(new SyncActionDescriptor(SwitchWindow, SwitchWindow.ID, SwitchWindow.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_W } }), 'Switch Window...'); registry.registerWorkbenchAction(new SyncActionDescriptor(QuickSwitchWindow, QuickSwitchWindow.ID, QuickSwitchWindow.LABEL), 'Quick Switch Window...'); @@ -57,8 +49,8 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten when: ContextKeyExpr.and(NoEditorsVisibleContext, SingleEditorGroupsContext), primary: KeyMod.CtrlCmd | KeyCode.KEY_W, handler: accessor => { - const windowService = accessor.get(IWindowService); - windowService.closeWindow(); + const electronService = accessor.get(IElectronService); + electronService.closeWindow(); } }); @@ -66,8 +58,8 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten id: 'workbench.action.quit', weight: KeybindingWeight.WorkbenchContrib, handler(accessor: ServicesAccessor) { - const windowsService = accessor.get(IWindowsService); - windowsService.quit(); + const electronService = accessor.get(IElectronService); + electronService.quit(); }, when: undefined, mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_Q }, @@ -108,7 +100,7 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten (function registerDeveloperActions(): void { const developerCategory = nls.localize('developer', "Developer"); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSharedProcessAction, ToggleSharedProcessAction.ID, ToggleSharedProcessAction.LABEL), 'Developer: Toggle Shared Process', developerCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowWithExtensionsDisabledAction, ReloadWindowWithExtensionsDisabledAction.ID, ReloadWindowWithExtensionsDisabledAction.LABEL), 'Developer: Reload Window With Extensions Disabled', developerCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(RestartWithExtensionsDisabledAction, RestartWithExtensionsDisabledAction.ID, RestartWithExtensionsDisabledAction.LABEL), 'Developer: Restart With Extensions Disabled', developerCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleDevToolsAction, ToggleDevToolsAction.ID, ToggleDevToolsAction.LABEL), 'Developer: Toggle Developer Tools', developerCategory); KeybindingsRegistry.registerKeybindingRule({ @@ -123,24 +115,6 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten // Menu (function registerMenu(): void { - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '1_new', - command: { - id: NewWindowAction.ID, - title: nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window") - }, - order: 2 - }); - - // {{SQL CARBON EDIT}} - Add install VSIX menu item - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '5.1_installExtension', - command: { - id: InstallVSIXAction.ID, - title: nls.localize({ key: 'miinstallVsix', comment: ['&& denotes a mnemonic'] }, "Install Extension from VSIX Package") - } - }); - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '3_workspace', command: { @@ -151,25 +125,13 @@ import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/browser/exten when: SupportsWorkspacesContext }); + // {{SQL CARBON EDIT}} - Add install VSIX menu item MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', + group: '5.1_installExtension', command: { - id: CloseWorkspaceAction.ID, - title: nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), - precondition: WorkspaceFolderCountContext.notEqualsTo('0') - }, - order: 3, - when: WorkbenchStateContext.notEqualsTo('workspace') - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', - command: { - id: CloseWorkspaceAction.ID, - title: nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace") - }, - order: 3, - when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), SupportsWorkspacesContext) + id: InstallVSIXAction.ID, + title: nls.localize({ key: 'miinstallVsix', comment: ['&& denotes a mnemonic'] }, "Install Extension from VSIX Package") + } }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 5b5ae4db09..0b2976c4f8 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -53,11 +53,13 @@ import { IPreferencesService } from '../services/preferences/common/preferences' import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IMenubarService, IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenubarMenuItemSubmenu, IMenubarMenuItemAction, MenubarMenuItem } from 'vs/platform/menubar/node/menubar'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IOpenerService, OpenOptions } 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'; +import { ITunnelService, extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/remote/common/tunnel'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), undefined, true, () => Promise.resolve(document.execCommand('undo'))), @@ -87,7 +89,6 @@ export class ElectronWindow extends Disposable { constructor( @IEditorService private readonly editorService: EditorServiceImpl, - @IWindowsService private readonly windowsService: IWindowsService, @IWindowService private readonly windowService: IWindowService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITitleService private readonly titleService: ITitleService, @@ -108,7 +109,9 @@ export class ElectronWindow extends Disposable { @ITextFileService private readonly textFileService: ITextFileService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IOpenerService private readonly openerService: IOpenerService, - @IElectronService private readonly electronService: IElectronService + @IElectronService private readonly electronService: IElectronService, + @ITunnelService private readonly tunnelService: ITunnelService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(); @@ -250,9 +253,28 @@ export class ElectronWindow extends Disposable { this._register(this.trackClosedWaitFiles(waitMarkerFile, resourcesToWaitFor)); } - // macOS custom title menu + // macOS OS integration if (isMacintosh) { - this._register(this.editorService.onDidActiveEditorChange(() => this.provideCustomTitleContextMenu())); + this._register(this.editorService.onDidActiveEditorChange(() => { + const file = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file }); + + // Represented Filename + this.updateRepresentedFilename(file ? file.fsPath : undefined); + + // Custom title menu + this.provideCustomTitleContextMenu(file ? file.fsPath : undefined); + })); + } + + // Maximize/Restore on doubleclick (for macOS custom title) + if (isMacintosh && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { + const titlePart = this.layoutService.getContainer(Parts.TITLEBAR_PART); + + this._register(DOM.addDisposableListener(titlePart, DOM.EventType.DBLCLICK, e => { + DOM.EventHelper.stop(e); + + this.electronService.handleTitleDoubleClick(); + })); } } @@ -273,7 +295,7 @@ export class ElectronWindow extends Disposable { private onAllEditorsClosed(): void { const visibleEditors = this.editorService.visibleControls.length; if (visibleEditors === 0) { - this.windowService.closeWindow(); + this.electronService.closeWindow(); } } @@ -317,19 +339,21 @@ export class ElectronWindow extends Disposable { } } - private provideCustomTitleContextMenu(): void { + private updateRepresentedFilename(filePath: string | undefined): void { + this.electronService.setRepresentedFilename(filePath ? filePath : ''); + } + + private provideCustomTitleContextMenu(filePath: string | undefined): 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') { + if (!filePath || 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); @@ -400,26 +424,24 @@ export class ElectronWindow extends Disposable { private setupOpenHandlers(): void { // Block window.open() calls - const $this = this; window.open = function (): Window | null { throw new Error('Prevented call to window.open(). Use IOpenerService instead!'); }; // Handle internal open() calls this.openerService.registerOpener({ - async open(resource: URI, options?: { openToSide?: boolean; openExternal?: boolean; } | undefined): Promise { + open: async (resource: URI, options?: OpenOptions): Promise => { // If either the caller wants to open externally or the // scheme is one where we prefer to open externally // we handle this resource by delegating the opening to // the main process to prevent window focus issues. - const scheme = resource.scheme.toLowerCase(); - const preferOpenExternal = (scheme === Schemas.mailto || scheme === Schemas.http || scheme === Schemas.https); - if ((options && options.openExternal) || preferOpenExternal) { - const success = await $this.windowsService.openExternal(encodeURI(resource.toString(true))); - if (!success && resource.scheme === Schemas.file) { + if (this.shouldOpenExternal(resource, options)) { + const { resolved } = await this.openerService.resolveExternalUri(resource, options); + const success = await this.electronService.openExternal(encodeURI(resolved.toString(true))); + if (!success && resolved.scheme === Schemas.file) { // if opening failed, and this is a file, we can still try to reveal it - await $this.electronService.showItemInFolder(resource.fsPath); + await this.electronService.showItemInFolder(resolved.fsPath); } return true; @@ -428,6 +450,30 @@ export class ElectronWindow extends Disposable { return false; // not handled by us } }); + + this.openerService.registerExternalUriResolver({ + resolveExternalUri: async (uri: URI, options?: OpenOptions) => { + if (options && options.allowTunneling) { + const portMappingRequest = extractLocalHostUriMetaDataForPortMapping(uri); + if (portMappingRequest) { + const tunnel = await this.tunnelService.openTunnel(portMappingRequest.port); + if (tunnel) { + return { + resolved: uri.with({ authority: `127.0.0.1:${tunnel.tunnelLocalPort}` }), + dispose: () => tunnel.dispose(), + }; + } + } + } + return undefined; + } + }); + } + + private shouldOpenExternal(resource: URI, options?: OpenOptions) { + const scheme = resource.scheme.toLowerCase(); + const preferOpenExternal = (scheme === Schemas.mailto || scheme === Schemas.http || scheme === Schemas.https); + return (options && options.openExternal) || preferOpenExternal; } private updateTouchbarMenu(): void { @@ -492,7 +538,7 @@ export class ElectronWindow extends Disposable { // Only update if the actions have changed if (!equals(this.lastInstalledTouchedBar, items)) { this.lastInstalledTouchedBar = items; - this.windowService.updateTouchBar(items); + this.electronService.updateTouchBar(items); } } @@ -520,7 +566,7 @@ export class ElectronWindow extends Disposable { crashReporter.start(deepClone(options)); // start crash reporter in the main process - return this.windowsService.startCrashReporter(options); + return this.electronService.startCrashReporter(options); } private onAddFoldersRequest(request: IAddFoldersRequest): void { diff --git a/src/vs/workbench/services/configuration/browser/configurationCache.ts b/src/vs/workbench/services/configuration/browser/configurationCache.ts index 4089b4d5cb..bb24dbe39a 100644 --- a/src/vs/workbench/services/configuration/browser/configurationCache.ts +++ b/src/vs/workbench/services/configuration/browser/configurationCache.ts @@ -7,9 +7,6 @@ import { IConfigurationCache, ConfigurationKey } from 'vs/workbench/services/con export class ConfigurationCache implements IConfigurationCache { - constructor() { - } - async read(key: ConfigurationKey): Promise { return ''; } @@ -19,4 +16,4 @@ export class ConfigurationCache implements IConfigurationCache { async remove(key: ConfigurationKey): Promise { } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts index 4fe01635bc..bd23d55cc8 100644 --- a/src/vs/workbench/services/configuration/browser/configurationService.ts +++ b/src/vs/workbench/services/configuration/browser/configurationService.ts @@ -507,17 +507,17 @@ export class WorkspaceService extends Disposable implements IConfigurationServic private registerConfigurationSchemas(): void { if (this.workspace) { const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); - const allSettingsSchema: IJSONSchema = { properties: allSettings.properties, patternProperties: allSettings.patternProperties, additionalProperties: true, allowsTrailingCommas: true, allowComments: true }; - const userSettingsSchema: IJSONSchema = this.remoteUserConfiguration ? { properties: { ...applicationSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowsTrailingCommas: true, allowComments: true } : allSettingsSchema; - const machineSettingsSchema: IJSONSchema = { properties: { ...machineSettings.properties, ...machineOverridableSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowsTrailingCommas: true, allowComments: true }; - const workspaceSettingsSchema: IJSONSchema = { properties: { ...machineOverridableSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowsTrailingCommas: true, allowComments: true }; + const allSettingsSchema: IJSONSchema = { properties: allSettings.properties, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true }; + const userSettingsSchema: IJSONSchema = this.remoteUserConfiguration ? { properties: { ...applicationSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true } : allSettingsSchema; + const machineSettingsSchema: IJSONSchema = { properties: { ...machineSettings.properties, ...machineOverridableSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true }; + const workspaceSettingsSchema: IJSONSchema = { properties: { ...machineOverridableSettings.properties, ...windowSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true }; jsonRegistry.registerSchema(defaultSettingsSchemaId, allSettingsSchema); jsonRegistry.registerSchema(userSettingsSchemaId, userSettingsSchema); jsonRegistry.registerSchema(machineSettingsSchemaId, machineSettingsSchema); if (WorkbenchState.WORKSPACE === this.getWorkbenchState()) { - const folderSettingsSchema: IJSONSchema = { properties: { ...machineOverridableSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowsTrailingCommas: true, allowComments: true }; + const folderSettingsSchema: IJSONSchema = { properties: { ...machineOverridableSettings.properties, ...resourceSettings.properties }, patternProperties: allSettings.patternProperties, additionalProperties: true, allowTrailingCommas: true, allowComments: true }; jsonRegistry.registerSchema(workspaceSettingsSchemaId, workspaceSettingsSchema); jsonRegistry.registerSchema(folderSettingsSchemaId, folderSettingsSchema); } else { diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts new file mode 100644 index 0000000000..bf265e130a --- /dev/null +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -0,0 +1,194 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IWindowService, IURIToOpen, FileFilter } from 'vs/platform/windows/common/windows'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import * as resources from 'vs/base/common/resources'; +import { IInstantiationService, } from 'vs/platform/instantiation/common/instantiation'; +import { SimpleFileDialog } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; +import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IFileService } from 'vs/platform/files/common/files'; +import { isWeb } from 'vs/base/common/platform'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; + +export class AbstractFileDialogService { + + _serviceBrand: undefined; + + constructor( + @IWindowService protected readonly windowService: IWindowService, + @IWorkspaceContextService protected readonly contextService: IWorkspaceContextService, + @IHistoryService protected readonly historyService: IHistoryService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IConfigurationService protected readonly configurationService: IConfigurationService, + @IFileService protected readonly fileService: IFileService, + @IOpenerService protected readonly openerService: IOpenerService, + ) { } + + defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { + + // Check for last active file first... + let candidate = this.historyService.getLastActiveFile(schemeFilter); + + // ...then for last active file root + if (!candidate) { + candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); + } else { + candidate = candidate && resources.dirname(candidate); + } + + return candidate || undefined; + } + + defaultFolderPath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { + + // Check for last active file root first... + let candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); + + // ...then for last active file + if (!candidate) { + candidate = this.historyService.getLastActiveFile(schemeFilter); + } + + return candidate && resources.dirname(candidate) || undefined; + } + + defaultWorkspacePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { + + // Check for current workspace config file first... + if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { + const configuration = this.contextService.getWorkspace().configuration; + if (configuration && !isUntitledWorkspace(configuration, this.environmentService)) { + return resources.dirname(configuration) || undefined; + } + } + + // ...then fallback to default file path + return this.defaultFilePath(schemeFilter); + } + + protected addFileSchemaIfNeeded(schema: string): string[] { + // Include File schema unless the schema is web + // Don't allow untitled schema through. + if (isWeb) { + return schema === Schemas.untitled ? [Schemas.file] : [schema]; + } else { + return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); + } + } + + protected async pickFileFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions, preferNewWindow: boolean): Promise { + const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder'); + const availableFileSystems = this.addFileSchemaIfNeeded(schema); + + const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + + if (uri) { + const stat = await this.fileService.resolve(uri); + + const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri }; + if (stat.isDirectory || options.forceNewWindow || preferNewWindow) { + return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); + } else { + return this.openerService.open(uri); + } + } + } + + protected async pickFileAndOpenSimplified(schema: string, options: IPickAndOpenOptions, preferNewWindow: boolean): Promise { + const title = nls.localize('openFile.title', 'Open File'); + const availableFileSystems = this.addFileSchemaIfNeeded(schema); + + const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + if (uri) { + if (options.forceNewWindow || preferNewWindow) { + return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); + } else { + return this.openerService.open(uri); + } + } + } + + protected async pickFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions): Promise { + const title = nls.localize('openFolder.title', 'Open Folder'); + const availableFileSystems = this.addFileSchemaIfNeeded(schema); + + const uri = await this.pickResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); + if (uri) { + return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow }); + } + } + + protected async pickWorkspaceAndOpenSimplified(schema: string, options: IPickAndOpenOptions): Promise { + const title = nls.localize('openWorkspace.title', 'Open Workspace'); + const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }]; + const availableFileSystems = this.addFileSchemaIfNeeded(schema); + + const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }); + if (uri) { + return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow }); + } + } + + protected async pickFileToSaveSimplified(schema: string, options: ISaveDialogOptions): Promise { + if (!options.availableFileSystems) { + options.availableFileSystems = this.addFileSchemaIfNeeded(schema); + } + + options.title = nls.localize('saveFileAs.title', 'Save As'); + return this.saveRemoteResource(options); + } + + protected async showSaveDialogSimplified(schema: string, options: ISaveDialogOptions): Promise { + if (!options.availableFileSystems) { + options.availableFileSystems = this.addFileSchemaIfNeeded(schema); + } + + return this.saveRemoteResource(options); + } + + protected async showOpenDialogSimplified(schema: string, options: IOpenDialogOptions): Promise { + if (!options.availableFileSystems) { + options.availableFileSystems = this.addFileSchemaIfNeeded(schema); + } + + const uri = await this.pickResource(options); + + return uri ? [uri] : undefined; + } + + private pickResource(options: IOpenDialogOptions): Promise { + const simpleFileDialog = this.instantiationService.createInstance(SimpleFileDialog); + + return simpleFileDialog.showOpenDialog(options); + } + + private saveRemoteResource(options: ISaveDialogOptions): Promise { + const remoteFileDialog = this.instantiationService.createInstance(SimpleFileDialog); + + return remoteFileDialog.showSaveDialog(options); + } + + protected getSchemeFilterForWindow(): string { + return !this.environmentService.configuration.remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME; + } + + protected getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string { + return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow(); + } +} + +function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean { + return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome); +} diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index 1e566b2f47..72366bcf71 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -3,105 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import { IWindowService, INativeOpenDialogOptions, OpenDialogOptions, IURIToOpen, FileFilter } from 'vs/platform/windows/common/windows'; import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; -import * as resources from 'vs/base/common/resources'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { RemoteFileDialog } from 'vs/workbench/services/dialogs/browser/remoteFileDialog'; -import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IFileService } from 'vs/platform/files/common/files'; -import { isWeb } from 'vs/base/common/platform'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService'; -export class FileDialogService implements IFileDialogService { - - _serviceBrand: undefined; - - constructor( - @IWindowService private readonly windowService: IWindowService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IHistoryService private readonly historyService: IHistoryService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IFileService private readonly fileService: IFileService, - @IOpenerService private readonly openerService: IOpenerService - ) { } - - defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { - - // Check for last active file first... - let candidate = this.historyService.getLastActiveFile(schemeFilter); - - // ...then for last active file root - if (!candidate) { - candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); - } else { - candidate = candidate && resources.dirname(candidate); - } - - return candidate || undefined; - } - - defaultFolderPath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { - - // Check for last active file root first... - let candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); - - // ...then for last active file - if (!candidate) { - candidate = this.historyService.getLastActiveFile(schemeFilter); - } - - return candidate && resources.dirname(candidate) || undefined; - } - - defaultWorkspacePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { - - // Check for current workspace config file first... - if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { - const configuration = this.contextService.getWorkspace().configuration; - if (configuration && !isUntitledWorkspace(configuration, this.environmentService)) { - return resources.dirname(configuration) || undefined; - } - } - - // ...then fallback to default file path - return this.defaultFilePath(schemeFilter); - } - - private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { - return { - forceNewWindow: options.forceNewWindow, - telemetryExtraData: options.telemetryExtraData, - defaultPath: options.defaultUri && options.defaultUri.fsPath - }; - } - - private shouldUseSimplified(schema: string): { useSimplified: boolean, isSetting: boolean } { - const setting = (this.configurationService.getValue('files.simpleDialog.enable') === true); - - return { useSimplified: (schema !== Schemas.file) || setting, isSetting: (schema === Schemas.file) && setting }; - } - - private addFileSchemaIfNeeded(schema: string): string[] { - // Include File schema unless the schema is web - // Don't allow untitled schema through. - if (isWeb) { - return schema === Schemas.untitled ? [Schemas.file] : [schema]; - } else { - return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); - } - } +export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { const schema = this.getFileSystemSchema(options); @@ -110,28 +17,7 @@ export class FileDialogService implements IFileDialogService { options.defaultUri = this.defaultFilePath(schema); } - const shouldUseSimplified = this.shouldUseSimplified(schema); - if (shouldUseSimplified.useSimplified) { - const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder'); - const availableFileSystems = this.addFileSchemaIfNeeded(schema); - - const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); - - if (uri) { - const stat = await this.fileService.resolve(uri); - - const toOpen: IURIToOpen = stat.isDirectory ? { folderUri: uri } : { fileUri: uri }; - if (stat.isDirectory || options.forceNewWindow || shouldUseSimplified.isSetting) { - return this.windowService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow }); - } else { - return this.openerService.open(uri); - } - } - - return; - } - - return this.windowService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options)); + return this.pickFileFolderAndOpenSimplified(schema, options, false); } async pickFileAndOpen(options: IPickAndOpenOptions): Promise { @@ -141,24 +27,7 @@ export class FileDialogService implements IFileDialogService { options.defaultUri = this.defaultFilePath(schema); } - const shouldUseSimplified = this.shouldUseSimplified(schema); - if (shouldUseSimplified.useSimplified) { - const title = nls.localize('openFile.title', 'Open File'); - const availableFileSystems = this.addFileSchemaIfNeeded(schema); - - const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); - if (uri) { - if (options.forceNewWindow || shouldUseSimplified.isSetting) { - return this.windowService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow }); - } else { - return this.openerService.open(uri); - } - } - - return; - } - - return this.windowService.pickFileAndOpen(this.toNativeOpenDialogOptions(options)); + return this.pickFileAndOpenSimplified(schema, options, false); } async pickFolderAndOpen(options: IPickAndOpenOptions): Promise { @@ -168,19 +37,7 @@ export class FileDialogService implements IFileDialogService { options.defaultUri = this.defaultFolderPath(schema); } - if (this.shouldUseSimplified(schema).useSimplified) { - const title = nls.localize('openFolder.title', 'Open Folder'); - const availableFileSystems = this.addFileSchemaIfNeeded(schema); - - const uri = await this.pickRemoteResource({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }); - if (uri) { - return this.windowService.openWindow([{ folderUri: uri }], { forceNewWindow: options.forceNewWindow }); - } - - return; - } - - return this.windowService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options)); + return this.pickFolderAndOpenSimplified(schema, options); } async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise { @@ -190,133 +47,23 @@ export class FileDialogService implements IFileDialogService { options.defaultUri = this.defaultWorkspacePath(schema); } - if (this.shouldUseSimplified(schema).useSimplified) { - const title = nls.localize('openWorkspace.title', 'Open Workspace'); - const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }]; - const availableFileSystems = this.addFileSchemaIfNeeded(schema); - - const uri = await this.pickRemoteResource({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }); - if (uri) { - return this.windowService.openWindow([{ workspaceUri: uri }], { forceNewWindow: options.forceNewWindow }); - } - - return; - } - - return this.windowService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options)); + return this.pickWorkspaceAndOpenSimplified(schema, options); } async pickFileToSave(options: ISaveDialogOptions): Promise { const schema = this.getFileSystemSchema(options); - if (this.shouldUseSimplified(schema).useSimplified) { - if (!options.availableFileSystems) { - options.availableFileSystems = this.addFileSchemaIfNeeded(schema); - } - - options.title = nls.localize('saveFileAs.title', 'Save As'); - return this.saveRemoteResource(options); - } - - const result = await this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)); - if (result) { - return URI.file(result); - } - - return undefined; // {{SQL CARBON EDIT}} strict-null-check - } - - private toNativeSaveDialogOptions(options: ISaveDialogOptions): Electron.SaveDialogOptions { - options.defaultUri = options.defaultUri ? URI.file(options.defaultUri.path) : undefined; - return { - defaultPath: options.defaultUri && options.defaultUri.fsPath, - buttonLabel: options.saveLabel, - filters: options.filters, - title: options.title - }; + return this.pickFileToSaveSimplified(schema, options); } async showSaveDialog(options: ISaveDialogOptions): Promise { const schema = this.getFileSystemSchema(options); - if (this.shouldUseSimplified(schema).useSimplified) { - if (!options.availableFileSystems) { - options.availableFileSystems = this.addFileSchemaIfNeeded(schema); - } - - return this.saveRemoteResource(options); - } - - const result = await this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)); - if (result) { - return URI.file(result); - } - - return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check + return this.showSaveDialogSimplified(schema, options); } async showOpenDialog(options: IOpenDialogOptions): Promise { const schema = this.getFileSystemSchema(options); - if (this.shouldUseSimplified(schema).useSimplified) { - if (!options.availableFileSystems) { - options.availableFileSystems = this.addFileSchemaIfNeeded(schema); - } - - const uri = await this.pickRemoteResource(options); - - return uri ? [uri] : undefined; - } - - const defaultUri = options.defaultUri; - - const newOptions: OpenDialogOptions = { - title: options.title, - defaultPath: defaultUri && defaultUri.fsPath, - buttonLabel: options.openLabel, - filters: options.filters, - properties: [] - }; - - newOptions.properties!.push('createDirectory'); - - if (options.canSelectFiles) { - newOptions.properties!.push('openFile'); - } - - if (options.canSelectFolders) { - newOptions.properties!.push('openDirectory'); - } - - if (options.canSelectMany) { - newOptions.properties!.push('multiSelections'); - } - - const result = await this.windowService.showOpenDialog(newOptions); - - return result ? result.map(URI.file) : undefined; + return this.showOpenDialogSimplified(schema, options); } - - private pickRemoteResource(options: IOpenDialogOptions): Promise { - const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); - - return remoteFileDialog.showOpenDialog(options); - } - - private saveRemoteResource(options: ISaveDialogOptions): Promise { - const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); - - return remoteFileDialog.showSaveDialog(options); - } - - private getSchemeFilterForWindow(): string { - return !this.environmentService.configuration.remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME; - } - - private getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string { - return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow(); - } -} - -function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean { - return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome); } registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts similarity index 99% rename from src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts rename to src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 798daf5773..178c90d279 100644 --- a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -98,7 +98,7 @@ enum UpdateResult { InvalidPath } -export class RemoteFileDialog { +export class SimpleFileDialog { private options!: IOpenDialogOptions; private currentFolder!: URI; private filePickBox!: IQuickPick; @@ -117,7 +117,7 @@ export class RemoteFileDialog { private badPath: string | undefined; private remoteAgentEnvironment: IRemoteAgentEnvironment | null | undefined; private separator: string = '/'; - private onBusyChangeEmitter = new Emitter(); + private readonly onBusyChangeEmitter = new Emitter(); private updatingPromise: CancelablePromise | undefined; protected disposables: IDisposable[] = [ @@ -303,7 +303,7 @@ export class RemoteFileDialog { this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length]; this.filePickBox.items = []; - function doResolve(dialog: RemoteFileDialog, uri: URI | undefined) { + function doResolve(dialog: SimpleFileDialog, uri: URI | undefined) { if (uri) { uri = resources.removeTrailingPathSeparator(uri); } @@ -335,7 +335,7 @@ export class RemoteFileDialog { } }); - function handleAccept(dialog: RemoteFileDialog) { + function handleAccept(dialog: SimpleFileDialog) { if (dialog.busy) { // Save the accept until the file picker is not busy. dialog.onBusyChangeEmitter.event((busy: boolean) => { diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts index 982ba620c0..4660bde819 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts @@ -8,7 +8,6 @@ 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/workbench/services/dialogs/browser/dialogService'; @@ -23,13 +22,14 @@ 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'; +import { MessageBoxOptions } from 'electron'; interface IMassagedMessageBoxOptions { /** * OS massaged message box options. */ - options: Electron.MessageBoxOptions; + options: MessageBoxOptions; /** * Since the massaged result of the message box options potentially @@ -50,7 +50,6 @@ export class DialogService implements IDialogService { @ILogService logService: ILogService, @ILayoutService layoutService: ILayoutService, @IThemeService themeService: IThemeService, - @IWindowService windowService: IWindowService, @ISharedProcessService sharedProcessService: ISharedProcessService, @IKeybindingService keybindingService: IKeybindingService, @IProductService productService: IProductService, @@ -64,7 +63,7 @@ export class DialogService implements IDialogService { } // Electron dialog service else { - this.impl = new NativeDialogService(windowService, logService, sharedProcessService, electronService, clipboardService); + this.impl = new NativeDialogService(logService, sharedProcessService, electronService, clipboardService); } } @@ -86,7 +85,6 @@ class NativeDialogService implements IDialogService { _serviceBrand: undefined; constructor( - @IWindowService private readonly windowService: IWindowService, @ILogService private readonly logService: ILogService, @ISharedProcessService sharedProcessService: ISharedProcessService, @IElectronService private readonly electronService: IElectronService, @@ -100,14 +98,14 @@ class NativeDialogService implements IDialogService { const { options, buttonIndexMap } = this.massageMessageBoxOptions(this.getConfirmOptions(confirmation)); - const result = await this.windowService.showMessageBox(options); + const result = await this.electronService.showMessageBox(options); return { - confirmed: buttonIndexMap[result.button] === 0 ? true : false, + confirmed: buttonIndexMap[result.response] === 0 ? true : false, checkboxChecked: result.checkboxChecked }; } - private getConfirmOptions(confirmation: IConfirmation): Electron.MessageBoxOptions { + private getConfirmOptions(confirmation: IConfirmation): MessageBoxOptions { const buttons: string[] = []; if (confirmation.primaryButton) { buttons.push(confirmation.primaryButton); @@ -121,7 +119,7 @@ class NativeDialogService implements IDialogService { buttons.push(nls.localize('cancelButton', "Cancel")); } - const opts: Electron.MessageBoxOptions = { + const opts: MessageBoxOptions = { title: confirmation.title, message: confirmation.message, buttons, @@ -157,11 +155,11 @@ class NativeDialogService implements IDialogService { checkboxChecked: dialogOptions && dialogOptions.checkbox ? dialogOptions.checkbox.checked : undefined }); - const result = await this.windowService.showMessageBox(options); - return { choice: buttonIndexMap[result.button], checkboxChecked: result.checkboxChecked }; + const result = await this.electronService.showMessageBox(options); + return { choice: buttonIndexMap[result.response], checkboxChecked: result.checkboxChecked }; } - private massageMessageBoxOptions(options: Electron.MessageBoxOptions): IMassagedMessageBoxOptions { + private massageMessageBoxOptions(options: MessageBoxOptions): IMassagedMessageBoxOptions { let buttonIndexMap = (options.buttons || []).map((button, index) => index); let buttons = (options.buttons || []).map(button => mnemonicButtonLabel(button)); let cancelId = options.cancelId; diff --git a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts new file mode 100644 index 0000000000..d5df5579d1 --- /dev/null +++ b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts @@ -0,0 +1,177 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IWindowService, OpenDialogOptions, SaveDialogOptions, INativeOpenDialogOptions } from 'vs/platform/windows/common/windows'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { URI } from 'vs/base/common/uri'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService'; +import { Schemas } from 'vs/base/common/network'; + +export class FileDialogService extends AbstractFileDialogService implements IFileDialogService { + + _serviceBrand: undefined; + + constructor( + @IWindowService windowService: IWindowService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IHistoryService historyService: IHistoryService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @IFileService fileService: IFileService, + @IOpenerService openerService: IOpenerService, + @IElectronService private readonly electronService: IElectronService + ) { super(windowService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService); } + + private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { + return { + forceNewWindow: options.forceNewWindow, + telemetryExtraData: options.telemetryExtraData, + defaultPath: options.defaultUri && options.defaultUri.fsPath + }; + } + + private shouldUseSimplified(schema: string): { useSimplified: boolean, isSetting: boolean } { + const setting = (this.configurationService.getValue('files.simpleDialog.enable') === true); + + return { useSimplified: (schema !== Schemas.file) || setting, isSetting: (schema === Schemas.file) && setting }; + } + + async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultFilePath(schema); + } + + const shouldUseSimplified = this.shouldUseSimplified(schema); + if (shouldUseSimplified.useSimplified) { + return this.pickFileFolderAndOpenSimplified(schema, options, shouldUseSimplified.isSetting); + } + return this.electronService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options)); + } + + async pickFileAndOpen(options: IPickAndOpenOptions): Promise { + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultFilePath(schema); + } + + const shouldUseSimplified = this.shouldUseSimplified(schema); + if (shouldUseSimplified.useSimplified) { + return this.pickFileAndOpenSimplified(schema, options, shouldUseSimplified.isSetting); + } + return this.electronService.pickFileAndOpen(this.toNativeOpenDialogOptions(options)); + } + + async pickFolderAndOpen(options: IPickAndOpenOptions): Promise { + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultFolderPath(schema); + } + + if (this.shouldUseSimplified(schema).useSimplified) { + return this.pickFolderAndOpenSimplified(schema, options); + } + return this.electronService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options)); + } + + async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise { + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultWorkspacePath(schema); + } + + if (this.shouldUseSimplified(schema).useSimplified) { + return this.pickWorkspaceAndOpenSimplified(schema, options); + } + return this.electronService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options)); + } + + async pickFileToSave(options: ISaveDialogOptions): Promise { + const schema = this.getFileSystemSchema(options); + if (this.shouldUseSimplified(schema).useSimplified) { + return this.pickFileToSaveSimplified(schema, options); + } else { + const result = await this.electronService.showSaveDialog(this.toNativeSaveDialogOptions(options)); + if (result && !result.canceled && result.filePath) { + return URI.file(result.filePath); + } + } + return undefined; // {{SQL CARBON EDIT}} strict-null-check + } + + private toNativeSaveDialogOptions(options: ISaveDialogOptions): SaveDialogOptions { + options.defaultUri = options.defaultUri ? URI.file(options.defaultUri.path) : undefined; + return { + defaultPath: options.defaultUri && options.defaultUri.fsPath, + buttonLabel: options.saveLabel, + filters: options.filters, + title: options.title + }; + } + + async showSaveDialog(options: ISaveDialogOptions): Promise { + const schema = this.getFileSystemSchema(options); + if (this.shouldUseSimplified(schema).useSimplified) { + return this.showSaveDialogSimplified(schema, options); + } + + const result = await this.electronService.showSaveDialog(this.toNativeSaveDialogOptions(options)); + if (result && !result.canceled && result.filePath) { + return URI.file(result.filePath); + } + + return undefined; // {{SQL CARBON EDIT}} strict-null-check + } + + async showOpenDialog(options: IOpenDialogOptions): Promise { + const schema = this.getFileSystemSchema(options); + if (this.shouldUseSimplified(schema).useSimplified) { + return this.showOpenDialogSimplified(schema, options); + } + + const defaultUri = options.defaultUri; + + const newOptions: OpenDialogOptions = { + title: options.title, + defaultPath: defaultUri && defaultUri.fsPath, + buttonLabel: options.openLabel, + filters: options.filters, + properties: [] + }; + + newOptions.properties!.push('createDirectory'); + + if (options.canSelectFiles) { + newOptions.properties!.push('openFile'); + } + + if (options.canSelectFolders) { + newOptions.properties!.push('openDirectory'); + } + + if (options.canSelectMany) { + newOptions.properties!.push('multiSelections'); + } + + const result = await this.electronService.showOpenDialog(newOptions); + return result && Array.isArray(result.filePaths) && result.filePaths.length > 0 ? result.filePaths.map(URI.file) : undefined; + } +} + +registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/services/editor/browser/codeEditorService.ts b/src/vs/workbench/services/editor/browser/codeEditorService.ts index 75e4c9416c..4ac259e09a 100644 --- a/src/vs/workbench/services/editor/browser/codeEditorService.ts +++ b/src/vs/workbench/services/editor/browser/codeEditorService.ts @@ -22,7 +22,7 @@ export class CodeEditorService extends CodeEditorServiceImpl { super(themeService); } - getActiveCodeEditor(): ICodeEditor | null { + getActiveCodeEditor(): ICodeEditor | undefined { const activeTextEditorWidget = this.editorService.activeTextEditorWidget; if (isCodeEditor(activeTextEditorWidget)) { return activeTextEditorWidget; @@ -32,10 +32,10 @@ export class CodeEditorService extends CodeEditorServiceImpl { return activeTextEditorWidget.getModifiedEditor(); } - return null; + return undefined; } - openCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + openCodeEditor(input: IResourceInput, source: ICodeEditor | undefined, sideBySide?: boolean): Promise { // Special case: If the active editor is a diff editor and the request to open originates and // targets the modified side of it, we just apply the request there to prevent opening the modified @@ -62,7 +62,7 @@ export class CodeEditorService extends CodeEditorServiceImpl { return this.doOpenCodeEditor(input, source, sideBySide); } - private async doOpenCodeEditor(input: IResourceInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { + private async doOpenCodeEditor(input: IResourceInput, _source: ICodeEditor | undefined, sideBySide?: boolean): Promise { const control = await this.editorService.openEditor(input, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); if (control) { const widget = control.getControl(); @@ -71,8 +71,8 @@ export class CodeEditorService extends CodeEditorServiceImpl { } } - return null; + return undefined; } } -registerSingleton(ICodeEditorService, CodeEditorService, true); \ No newline at end of file +registerSingleton(ICodeEditorService, CodeEditorService, true); diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index 50462ce3fa..a4154d82b6 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -61,8 +61,6 @@ suite.skip('EditorGroupsService', () => { // {{SQL CARBON EDIT}} skip suite class TestEditorInputFactory implements IEditorInputFactory { - constructor() { } - serialize(editorInput: EditorInput): string { const testEditorInput = editorInput; const testInput: ISerializedTestEditorInput = { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts index 1ede8267a2..31b56a759f 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionEnablementService.ts @@ -26,7 +26,7 @@ export class ExtensionEnablementService extends Disposable implements IExtension _serviceBrand: undefined; - private _onEnablementChanged = new Emitter(); + private readonly _onEnablementChanged = new Emitter(); public readonly onEnablementChanged: Event = this._onEnablementChanged.event; private readonly storageManger: StorageManager; @@ -148,8 +148,11 @@ export class ExtensionEnablementService extends Disposable implements IExtension private _isDisabledByExtensionKind(extension: IExtension): boolean { if (this.extensionManagementServerService.localExtensionManagementServer && this.extensionManagementServerService.remoteExtensionManagementServer) { - const server = isUIExtension(extension.manifest, this.productService, this.configurationService) ? this.extensionManagementServerService.localExtensionManagementServer : this.extensionManagementServerService.remoteExtensionManagementServer; - return this.extensionManagementServerService.getExtensionManagementServer(extension.location) !== server; + if (!isUIExtension(extension.manifest, this.productService, this.configurationService)) { + // workspace extensions must run on the remote, but UI extensions can run on either side + const server = this.extensionManagementServerService.remoteExtensionManagementServer; + return this.extensionManagementServerService.getExtensionManagementServer(extension.location) !== server; + } } return false; } diff --git a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts index 23c3dcbcde..38f5510b81 100644 --- a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts @@ -520,8 +520,8 @@ suite('ExtensionEnablementService Test', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); - assert.ok(!testObject.isEnabled(localWorkspaceExtension)); - assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.DisabledByExtensionKind); + assert.ok(testObject.isEnabled(localWorkspaceExtension)); + assert.deepEqual(testObject.getEnablementState(localWorkspaceExtension), EnablementState.EnabledGlobally); }); test('test remote workspace extension is not disabled by kind', async () => { @@ -536,7 +536,7 @@ suite('ExtensionEnablementService Test', () => { instantiationService.stub(IExtensionManagementServerService, aMultiExtensionManagementServerService(instantiationService)); const localWorkspaceExtension = aLocalExtension2('pub.a', { extensionKind: 'ui' }, { location: URI.file(`pub.a`).with({ scheme: Schemas.vscodeRemote }) }); testObject = new TestExtensionEnablementService(instantiationService); - assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), false); + assert.equal(testObject.canChangeEnablement(localWorkspaceExtension), true); }); test('test canChangeEnablement return true for remote workspace extension', () => { diff --git a/src/vs/workbench/services/extensions/common/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts similarity index 98% rename from src/vs/workbench/services/extensions/common/extensionUrlHandler.ts rename to src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index f2c54678e6..605abf1987 100644 --- a/src/vs/workbench/services/extensions/common/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -16,7 +16,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -65,7 +65,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { @INotificationService private readonly notificationService: INotificationService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, - @IWindowService private readonly windowService: IWindowService, + @IHostService private readonly hostService: IHostService, @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, @IStorageService private readonly storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService @@ -277,7 +277,7 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { private async reloadAndHandle(url: URI): Promise { this.storageService.store(URL_TO_HANDLE, JSON.stringify(url.toJSON()), StorageScope.WORKSPACE); - await this.windowService.reloadWindow(); + await this.hostService.reload(); } // forget about all uris buffered more than 5 minutes ago diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 8ab0417334..d289d072af 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -24,6 +24,7 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio import { IFileService } from 'vs/platform/files/common/files'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; import { IProductService } from 'vs/platform/product/common/productService'; +import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; const hasOwnProperty = Object.hasOwnProperty; const NO_OP_VOID_PROMISE = Promise.resolve(undefined); @@ -290,17 +291,21 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } protected _isEnabled(extension: IExtensionDescription): boolean { + return !this._isDisabled(extension); + } + + protected _isDisabled(extension: IExtensionDescription): boolean { if (this._isExtensionUnderDevelopment(extension)) { // Never disable extensions under development - return true; + return false; } if (ExtensionIdentifier.equals(extension.identifier, BetterMergeId)) { // Check if this is the better merge extension which was migrated to a built-in extension - return false; + return true; } - return this._extensionEnablementService.isEnabled(toExtension(extension)); + return !this._extensionEnablementService.isEnabled(toExtension(extension)); } protected _doHandleExtensionPoints(affectedExtensions: IExtensionDescription[]): void { @@ -406,9 +411,9 @@ export abstract class AbstractExtensionService extends Disposable implements IEx } } - public async _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise { + public async _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { const results = await Promise.all( - this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, activationEvent)) + this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, reason)) ); const activated = results.some(e => e); if (!activated) { @@ -420,8 +425,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId); } - public _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void { - this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(startup, codeLoadingTime, activateCallTime, activateResolvedTime, activationEvent)); + public _onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void { + this._extensionHostProcessActivationTimes.set(ExtensionIdentifier.toKey(extensionId), new ActivationTimes(codeLoadingTime, activateCallTime, activateResolvedTime, activationReason)); this._onDidChangeExtensionsStatus.fire([extensionId]); } diff --git a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts index 70e78b96a7..ae28def4da 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProcessManager.ts @@ -26,6 +26,7 @@ import { IUntitledResourceInput } from 'vs/workbench/common/editor'; import { StopWatch } from 'vs/base/common/stopwatch'; import { VSBuffer } from 'vs/base/common/buffer'; import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; // Enable to see detailed message communication between window and extension host const LOG_EXTENSION_HOST_COMMUNICATION = false; @@ -215,12 +216,12 @@ export class ExtensionHostProcessManager extends Disposable { return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService); } - public async activate(extension: ExtensionIdentifier, activationEvent: string): Promise { + public async activate(extension: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { const proxy = await this._getExtensionHostProcessProxy(); if (!proxy) { return false; } - return proxy.$activate(extension, activationEvent); + return proxy.$activate(extension, reason); } public activateByEvent(activationEvent: string): Promise { diff --git a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts index b06566e887..f530ef39f9 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostProtocol.ts @@ -15,6 +15,10 @@ export interface IExtHostSocketMessage { skipWebSocketFrames: boolean; } +export interface IExtHostReduceGraceTimeMessage { + type: 'VSCODE_EXTHOST_IPC_REDUCE_GRACE_TIME'; +} + export const enum MessageType { Initialized, Ready, @@ -44,4 +48,4 @@ export function isMessageOfType(message: VSBuffer, type: MessageType): boolean { case 3: return type === MessageType.Terminate; default: return false; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 719d36a3b5..5aee0ade8e 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -11,6 +11,7 @@ import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensi import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; +import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator'; export const nullExtensionDescription = Object.freeze({ identifier: new ExtensionIdentifier('nullExtensionDescription'), @@ -99,11 +100,10 @@ export type ProfileSegmentId = string | 'idle' | 'program' | 'gc' | 'self'; export class ActivationTimes { constructor( - public readonly startup: boolean, public readonly codeLoadingTime: number, public readonly activateCallTime: number, public readonly activateResolvedTime: number, - public readonly activationEvent: string + public readonly activationReason: ExtensionActivationReason ) { } } @@ -226,9 +226,9 @@ export interface IExtensionService { setRemoteEnvironment(env: { [key: string]: string | null }): Promise; _logOrShowMessage(severity: Severity, msg: string): void; - _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise; + _activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; _onWillActivateExtension(extensionId: ExtensionIdentifier): void; - _onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void; + _onDidActivateExtension(extensionId: ExtensionIdentifier, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationReason: ExtensionActivationReason): void; _onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void; _onExtensionHostExit(code: number): void; } @@ -276,9 +276,9 @@ export class NullExtensionService implements IExtensionService { canAddExtension(): boolean { return false; } canRemoveExtension(): boolean { return false; } _logOrShowMessage(_severity: Severity, _msg: string): void { } - _activateById(_extensionId: ExtensionIdentifier, _activationEvent: string): Promise { return Promise.resolve(); } + _activateById(_extensionId: ExtensionIdentifier, _reason: ExtensionActivationReason): Promise { return Promise.resolve(); } _onWillActivateExtension(_extensionId: ExtensionIdentifier): void { } - _onDidActivateExtension(_extensionId: ExtensionIdentifier, _startup: boolean, _codeLoadingTime: number, _activateCallTime: number, _activateResolvedTime: number, _activationEvent: string): void { } + _onDidActivateExtension(_extensionId: ExtensionIdentifier, _codeLoadingTime: number, _activateCallTime: number, _activateResolvedTime: number, _activationReason: ExtensionActivationReason): void { } _onExtensionRuntimeError(_extensionId: ExtensionIdentifier, _err: Error): void { } _onExtensionHostExit(code: number): void { } } diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index 6cd22d2073..1d98e8e3f4 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -19,7 +19,7 @@ import { IExtensionEnablementService } from 'vs/workbench/services/extensionMana import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE, ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; 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 { IHostService } from 'vs/workbench/services/host/browser/host'; import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, IRelaxedExtensionDescription } from 'vs/workbench/services/extensions/node/extensionPoints'; import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints'; @@ -55,7 +55,7 @@ export class CachedExtensionScanner { @INotificationService private readonly _notificationService: INotificationService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @IExtensionEnablementService private readonly _extensionEnablementService: IExtensionEnablementService, - @IWindowService private readonly _windowService: IWindowService, + @IHostService private readonly _hostService: IHostService, ) { this.scannedExtensions = new Promise((resolve, reject) => { this._scannedExtensionsResolve = resolve; @@ -78,7 +78,7 @@ export class CachedExtensionScanner { public async startScanningExtensions(log: ILog): Promise { try { const translations = await this.translationConfig; - const { system, user, development } = await CachedExtensionScanner._scanInstalledExtensions(this._windowService, this._notificationService, this._environmentService, this._extensionEnablementService, log, translations); + const { system, user, development } = await CachedExtensionScanner._scanInstalledExtensions(this._hostService, this._notificationService, this._environmentService, this._extensionEnablementService, log, translations); let result = new Map(); system.forEach((systemExtension) => { @@ -111,7 +111,7 @@ export class CachedExtensionScanner { } } - private static async _validateExtensionsCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): Promise { + private static async _validateExtensionsCache(hostService: IHostService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput): Promise { const cacheFolder = path.join(environmentService.userDataPath, MANIFEST_CACHE_FOLDER); const cacheFile = path.join(cacheFolder, cacheKey); @@ -141,7 +141,7 @@ export class CachedExtensionScanner { nls.localize('extensionCache.invalid', "Extensions have been modified on disk. Please reload the window."), [{ label: nls.localize('reloadWindow', "Reload Window"), - run: () => windowService.reloadWindow() + run: () => hostService.reload() }] ); } @@ -177,7 +177,7 @@ export class CachedExtensionScanner { } } - private static async _scanExtensionsWithCache(windowService: IWindowService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): Promise { + private static async _scanExtensionsWithCache(hostService: IHostService, notificationService: INotificationService, environmentService: IEnvironmentService, cacheKey: string, input: ExtensionScannerInput, log: ILog): Promise { if (input.devMode) { // Do not cache when running out of sources... return ExtensionScanner.scanExtensions(input, log); @@ -195,7 +195,7 @@ export class CachedExtensionScanner { // Validate the cache asynchronously after 5s setTimeout(async () => { try { - await this._validateExtensionsCache(windowService, notificationService, environmentService, cacheKey, input); + await this._validateExtensionsCache(hostService, notificationService, environmentService, cacheKey, input); } catch (err) { errors.onUnexpectedError(err); } @@ -234,7 +234,7 @@ export class CachedExtensionScanner { } private static _scanInstalledExtensions( - windowService: IWindowService, + hostService: IHostService, notificationService: INotificationService, environmentService: IEnvironmentService, extensionEnablementService: IExtensionEnablementService, @@ -248,7 +248,7 @@ export class CachedExtensionScanner { const locale = platform.language; const builtinExtensions = this._scanExtensionsWithCache( - windowService, + hostService, notificationService, environmentService, BUILTIN_MANIFEST_CACHE_FILE, @@ -282,7 +282,7 @@ export class CachedExtensionScanner { extensionEnablementService.allUserExtensionsDisabled || !environmentService.extensionsPath ? Promise.resolve([]) : this._scanExtensionsWithCache( - windowService, + hostService, notificationService, environmentService, USER_MANIFEST_CACHE_FILE, diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index bd49669f73..da3f7f0c75 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -27,7 +27,7 @@ import { ILogService } from 'vs/platform/log/common/log'; 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 } from 'vs/platform/windows/common/windows'; +import { IElectronService } from 'vs/platform/electron/node/electron'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IInitData, UIKind } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; @@ -38,6 +38,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IExtensionHostStarter } from 'vs/workbench/services/extensions/common/extensions'; import { isEqualOrParent } from 'vs/base/common/resources'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class ExtensionHostProcessWorker implements IExtensionHostStarter { @@ -68,13 +69,14 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { private readonly _extensionHostLogsLocation: URI, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @INotificationService private readonly _notificationService: INotificationService, - @IWindowService private readonly _windowService: IWindowService, + @IElectronService private readonly _electronService: IElectronService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILogService private readonly _logService: ILogService, @ILabelService private readonly _labelService: ILabelService, - @IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService + @IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService, + @IHostService private readonly _hostService: IHostService ) { const devOpts = parseExtensionDevOptions(this._environmentService); this._isExtensionDevHost = devOpts.isExtensionDevHost; @@ -96,12 +98,12 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._toDispose.add(this._lifecycleService.onShutdown(reason => this.terminate())); this._toDispose.add(this._extensionHostDebugService.onClose(event => { if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) { - this._windowService.closeWindow(); + this._electronService.closeWindow(); } })); this._toDispose.add(this._extensionHostDebugService.onReload(event => { if (this._isExtensionDevHost && this._environmentService.debugExtensionHost.debugId === event.sessionId) { - this._windowService.reloadWindow(); + this._hostService.reload(); } })); @@ -234,7 +236,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._notificationService.prompt(Severity.Warning, msg, [{ label: nls.localize('reloadWindow', "Reload Window"), - run: () => this._windowService.reloadWindow() + run: () => this._hostService.reload() }], { sticky: true } ); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 3fe1fcdbcc..0b7112e80c 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -19,13 +19,14 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IInitDataProvider, RemoteExtensionHostClient } from 'vs/workbench/services/extensions/common/remoteExtensionHostClient'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { isUIExtension } from 'vs/workbench/services/extensions/common/extensionsUtil'; +import { isUIExtension as isUIExtensionFunc } from 'vs/workbench/services/extensions/common/extensionsUtil'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; 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 { IHostService } from 'vs/workbench/services/host/browser/host'; import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/common/extensionHostProcessManager'; import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -36,6 +37,7 @@ 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'; +import { IElectronService } from 'vs/platform/electron/node/electron'; class DeltaExtensionsQueueItem { constructor( @@ -67,6 +69,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IWindowService protected readonly _windowService: IWindowService, @IStaticExtensionsService private readonly _staticExtensions: IStaticExtensionsService, + @IElectronService private readonly _electronService: IElectronService, + @IHostService private readonly _hostService: IHostService ) { super( instantiationService, @@ -82,7 +86,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._notificationService.prompt(Severity.Info, nls.localize('extensionsDisabled', "All installed extensions are temporarily disabled. Reload the window to return to the previous state."), [{ label: nls.localize('Reload', "Reload"), run: () => { - this._windowService.reloadWindow(); + this._hostService.reload(); } }]); } @@ -326,7 +330,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten if (shouldActivate) { await Promise.all( - this._extensionHostProcessManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, shouldActivateReason!)) + this._extensionHostProcessManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: shouldActivateReason! })) ).then(() => { }); } } @@ -384,8 +388,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten label: nls.localize('relaunch', "Relaunch VS Code"), run: () => { this._instantiationService.invokeFunction((accessor) => { - const windowsService = accessor.get(IWindowsService); - windowsService.relaunch({}); + const hostService = accessor.get(IHostService); + hostService.restart(); }); } }] @@ -396,7 +400,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._notificationService.prompt(Severity.Error, nls.localize('extensionService.crash', "Extension host terminated unexpectedly."), [{ label: nls.localize('devTools', "Open Developer Tools"), - run: () => this._windowService.openDevTools() + run: () => this._electronService.openDevTools() }, { label: nls.localize('restart', "Restart Extension Host"), @@ -435,6 +439,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten } protected async _scanAndHandleExtensions(): Promise { + const isUIExtension = (extension: IExtensionDescription) => isUIExtensionFunc(extension, this._productService, this._configurationService); + this._extensionScanner.startScanningExtensions(this.createLogger()); const remoteAuthority = this._environmentService.configuration.remoteAuthority; @@ -446,7 +452,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._checkEnableProposedApi(localExtensions); // remove disabled extensions - localExtensions = localExtensions.filter(extension => this._isEnabled(extension)); + localExtensions = remove(localExtensions, extension => this._isDisabled(extension)); if (remoteAuthority) { let resolvedAuthority: ResolverResult; @@ -502,18 +508,16 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._checkEnableProposedApi(remoteEnv.extensions); // remove disabled extensions - remoteEnv.extensions = remoteEnv.extensions.filter(extension => this._isEnabled(extension)); - - // remove UI extensions from the remote extensions - remoteEnv.extensions = remoteEnv.extensions.filter(extension => !isUIExtension(extension, this._productService, this._configurationService)); + remoteEnv.extensions = remove(remoteEnv.extensions, extension => this._isDisabled(extension)); // remove non-UI extensions from the local extensions - localExtensions = localExtensions.filter(extension => extension.isBuiltin || isUIExtension(extension, this._productService, this._configurationService)); + localExtensions = remove(localExtensions, extension => !extension.isBuiltin && !isUIExtension(extension)); - // in case of overlap, the remote wins - const isRemoteExtension = new Set(); - remoteEnv.extensions.forEach(extension => isRemoteExtension.add(ExtensionIdentifier.toKey(extension.identifier))); - localExtensions = localExtensions.filter(extension => !isRemoteExtension.has(ExtensionIdentifier.toKey(extension.identifier))); + // in case of UI extensions overlap, the local extension wins + remoteEnv.extensions = remove(remoteEnv.extensions, localExtensions.filter(extension => isUIExtension(extension))); + + // in case of other extensions overlap, the remote extension wins + localExtensions = remove(localExtensions, remoteEnv.extensions); // save for remote extension's init data this._remoteExtensionsEnvironmentData.set(remoteAuthority, remoteEnv); @@ -548,7 +552,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten public _onExtensionHostExit(code: number): void { // Expected development extension termination: When the extension host goes down we also shutdown the window if (!this._isExtensionDevTestFromCli) { - this._windowService.closeWindow(); + this._electronService.closeWindow(); } // When CLI testing make sure to exit with proper exit code @@ -558,4 +562,23 @@ export class ExtensionService extends AbstractExtensionService implements IExten } } +function remove(arr: IExtensionDescription[], predicate: (item: IExtensionDescription) => boolean): IExtensionDescription[]; +function remove(arr: IExtensionDescription[], toRemove: IExtensionDescription[]): IExtensionDescription[]; +function remove(arr: IExtensionDescription[], arg2: ((item: IExtensionDescription) => boolean) | IExtensionDescription[]): IExtensionDescription[] { + if (typeof arg2 === 'function') { + return _removePredicate(arr, arg2); + } + return _removeSet(arr, arg2); +} + +function _removePredicate(arr: IExtensionDescription[], predicate: (item: IExtensionDescription) => boolean): IExtensionDescription[] { + return arr.filter(extension => !predicate(extension)); +} + +function _removeSet(arr: IExtensionDescription[], toRemove: IExtensionDescription[]): IExtensionDescription[] { + const toRemoveSet = new Set(); + toRemove.forEach(extension => toRemoveSet.add(ExtensionIdentifier.toKey(extension.identifier))); + return arr.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier))); +} + registerSingleton(IExtensionService, ExtensionService); diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 042f465e43..c140f52483 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -13,7 +13,7 @@ import { PersistentProtocol, ProtocolConstants, BufferedEmitter } from 'vs/base/ import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net'; 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 { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessage, IExtHostReadyMessage, IExtHostReduceGraceTimeMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; import { ExtensionHostMain, IExitFn } from 'vs/workbench/services/extensions/common/extensionHostMain'; import { VSBuffer } from 'vs/base/common/buffer'; import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/common/uriIpc'; @@ -21,6 +21,7 @@ import { exists } from 'vs/base/node/pfs'; import { realpath } from 'vs/base/node/extpath'; import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; import 'vs/workbench/api/node/extHost.services'; +import { RunOnceScheduler } from 'vs/base/common/async'; interface ParsedExtHostArgs { uriTransformerPath?: string; @@ -91,9 +92,12 @@ function _createExtHostProtocol(): Promise { reject(new Error('VSCODE_EXTHOST_IPC_SOCKET timeout')); }, 60000); - let disconnectWaitTimer: NodeJS.Timeout | null = null; + const reconnectionGraceTime = ProtocolConstants.ReconnectionGraceTime; + const reconnectionShortGraceTime = ProtocolConstants.ReconnectionShortGraceTime; + const disconnectRunner1 = new RunOnceScheduler(() => onTerminate(), reconnectionGraceTime); + const disconnectRunner2 = new RunOnceScheduler(() => onTerminate(), reconnectionShortGraceTime); - process.on('message', (msg: IExtHostSocketMessage, handle: net.Socket) => { + process.on('message', (msg: IExtHostSocketMessage | IExtHostReduceGraceTimeMessage, handle: net.Socket) => { if (msg && msg.type === 'VSCODE_EXTHOST_IPC_SOCKET') { const initialDataChunk = VSBuffer.wrap(Buffer.from(msg.initialDataChunk, 'base64')); let socket: NodeSocket | WebSocketNodeSocket; @@ -104,10 +108,8 @@ function _createExtHostProtocol(): Promise { } if (protocol) { // reconnection case - if (disconnectWaitTimer) { - clearTimeout(disconnectWaitTimer); - disconnectWaitTimer = null; - } + disconnectRunner1.cancel(); + disconnectRunner2.cancel(); protocol.beginAcceptReconnection(socket, initialDataChunk); protocol.endAcceptReconnection(); } else { @@ -116,21 +118,21 @@ function _createExtHostProtocol(): Promise { protocol.onClose(() => onTerminate()); resolve(protocol); - if (msg.skipWebSocketFrames) { - // Wait for rich client to reconnect - protocol.onSocketClose(() => { - // The socket has closed, let's give the renderer a certain amount of time to reconnect - disconnectWaitTimer = setTimeout(() => { - disconnectWaitTimer = null; - onTerminate(); - }, ProtocolConstants.ReconnectionGraceTime); - }); - } else { - // Do not wait for web companion to reconnect - protocol.onSocketClose(() => { - onTerminate(); - }); - } + // Wait for rich client to reconnect + protocol.onSocketClose(() => { + // The socket has closed, let's give the renderer a certain amount of time to reconnect + disconnectRunner1.schedule(); + }); + } + } + if (msg && msg.type === 'VSCODE_EXTHOST_IPC_REDUCE_GRACE_TIME') { + if (disconnectRunner2.isScheduled()) { + // we are disconnected and already running the short reconnection timer + return; + } + if (disconnectRunner1.isScheduled()) { + // we are disconnected and running the long reconnection timer + disconnectRunner2.schedule(); } } }); diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index 9b2ce6b2ad..710b9276bd 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -5,16 +5,75 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export class BrowserHostService implements IHostService { _serviceBrand: undefined; + constructor(@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService) { } + //#region Window readonly windowCount = Promise.resolve(1); + async openEmptyWindow(options?: { reuse?: boolean, remoteAuthority?: string }): Promise { + // TODO@Ben delegate to embedder + const targetHref = `${document.location.origin}${document.location.pathname}?ew=true`; + if (options && options.reuse) { + window.location.href = targetHref; + } else { + window.open(targetHref); + } + } + + async toggleFullScreen(): Promise { + const target = this.layoutService.getWorkbenchElement(); + + // Chromium + if (document.fullscreen !== undefined) { + if (!document.fullscreen) { + try { + return await target.requestFullscreen(); + } catch (error) { + console.warn('Toggle Full Screen failed'); // https://developer.mozilla.org/en-US/docs/Web/API/Element/requestFullscreen + } + } else { + try { + return await document.exitFullscreen(); + } catch (error) { + console.warn('Exit Full Screen failed'); + } + } + } + + // Safari and Edge 14 are all using webkit prefix + if ((document).webkitIsFullScreen !== undefined) { + try { + if (!(document).webkitIsFullScreen) { + (target).webkitRequestFullscreen(); // it's async, but doesn't return a real promise. + } else { + (document).webkitExitFullscreen(); // it's async, but doesn't return a real promise. + } + } catch { + console.warn('Enter/Exit Full Screen failed'); + } + } + } + //#endregion + + async restart(): Promise { + this.reload(); + } + + async reload(): Promise { + window.location.reload(); + } + + async closeWorkspace(): Promise { + return this.openEmptyWindow({ reuse: true }); + } } registerSingleton(IHostService, BrowserHostService, true); diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts index 02fa64f309..e99f2df0b1 100644 --- a/src/vs/workbench/services/host/browser/host.ts +++ b/src/vs/workbench/services/host/browser/host.ts @@ -18,5 +18,36 @@ export interface IHostService { */ readonly windowCount: Promise; + /** + * Opens an empty window. The optional parameter allows to define if + * a new window should open or the existing one change to an empty. + */ + openEmptyWindow(options?: { reuse?: boolean, remoteAuthority?: string }): Promise; + + /** + * Switch between fullscreen and normal window. + */ + toggleFullScreen(): Promise; + + //#endregion + + //#region Lifecycle + + /** + * Restart the entire application. + */ + restart(): Promise; + + /** + * Reload the currently active window. + */ + reload(): Promise; + + /** + * Closes the currently opened folder/workspace and returns to an empty + * window. + */ + closeWorkspace(): Promise; + //#endregion } diff --git a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts index e1f3c3b6a5..cdcb51a84b 100644 --- a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts +++ b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts @@ -17,7 +17,27 @@ export class DesktopHostService implements IHostService { get windowCount() { return this.electronService.windowCount(); } + openEmptyWindow(options?: { reuse?: boolean, remoteAuthority?: string }): Promise { + return this.electronService.openEmptyWindow(options); + } + + toggleFullScreen(): Promise { + return this.electronService.toggleFullScreen(); + } + //#endregion + + restart(): Promise { + return this.electronService.relaunch(); + } + + reload(): Promise { + return this.electronService.reload(); + } + + closeWorkspace(): Promise { + return this.electronService.closeWorkpsace(); + } } registerSingleton(IHostService, DesktopHostService, true); diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 9dfab8afcf..0ead211296 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -603,7 +603,7 @@ let schema: IJSONSchema = { id: schemaId, type: 'array', title: nls.localize('keybindings.json.title', "Keybindings configuration"), - allowsTrailingCommas: true, + allowTrailingCommas: true, allowComments: true, definitions: { 'editorGroupsSchema': { diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 019f7b0dae..8cec77e8d3 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -27,11 +27,6 @@ export const enum Position { BOTTOM } -export interface ILayoutOptions { - toggleMaximizedPanel?: boolean; - source?: Parts; -} - export interface IWorkbenchLayoutService extends ILayoutService { _serviceBrand: undefined; diff --git a/src/vs/workbench/services/progress/test/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/progressIndicator.test.ts index c498244f72..d7add3b9a1 100644 --- a/src/vs/workbench/services/progress/test/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/progressIndicator.test.ts @@ -44,8 +44,6 @@ class TestProgressBar { fInfinite: boolean = false; fDone: boolean = false; - constructor() { } - infinite() { this.fDone = null!; this.fInfinite = true; diff --git a/src/vs/workbench/services/remote/node/tunnelService.ts b/src/vs/workbench/services/remote/node/tunnelService.ts index e7e94e847f..d993c51c24 100644 --- a/src/vs/workbench/services/remote/node/tunnelService.ts +++ b/src/vs/workbench/services/remote/node/tunnelService.ts @@ -88,12 +88,24 @@ class NodeRemoteTunnel extends Disposable implements RemoteTunnel { export class TunnelService implements ITunnelService { _serviceBrand: undefined; + private readonly _tunnels = new Map }>(); + public constructor( @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, @ISignService private readonly signService: ISignService, - @ILogService private readonly logService: ILogService - ) { + @ILogService private readonly logService: ILogService, + ) { } + + public get tunnels(): Promise { + return Promise.all(Array.from(this._tunnels.values()).map(x => x.value)); + } + + dispose(): void { + for (const { value } of this._tunnels.values()) { + value.then(tunnel => tunnel.dispose()); + } + this._tunnels.clear(); } openTunnel(remotePort: number): Promise | undefined { @@ -102,6 +114,33 @@ export class TunnelService implements ITunnelService { return undefined; } + const resolvedTunnel = this.retainOrCreateTunnel(remoteAuthority, remotePort); + if (!resolvedTunnel) { + return resolvedTunnel; + } + + return resolvedTunnel.then(tunnel => ({ + tunnelRemotePort: tunnel.tunnelRemotePort, + tunnelLocalPort: tunnel.tunnelLocalPort, + dispose: () => { + const existing = this._tunnels.get(remotePort); + if (existing) { + if (--existing.refcount <= 0) { + existing.value.then(tunnel => tunnel.dispose()); + this._tunnels.delete(remotePort); + } + } + } + })); + } + + private retainOrCreateTunnel(remoteAuthority: string, remotePort: number): Promise | undefined { + const existing = this._tunnels.get(remotePort); + if (existing) { + ++existing.refcount; + return existing.value; + } + const options: IConnectionOptions = { commit: product.commit, socketFactory: nodeSocketFactory, @@ -114,7 +153,10 @@ export class TunnelService implements ITunnelService { signService: this.signService, logService: this.logService }; - return createRemoteTunnel(options, remotePort); + + const tunnel = createRemoteTunnel(options, remotePort); + this._tunnels.set(remotePort, { refcount: 1, value: tunnel }); + return tunnel; } } diff --git a/src/vs/workbench/services/request/browser/requestService.ts b/src/vs/workbench/services/request/browser/requestService.ts index b7a58e42dd..d226e6280b 100644 --- a/src/vs/workbench/services/request/browser/requestService.ts +++ b/src/vs/workbench/services/request/browser/requestService.ts @@ -9,9 +9,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ILogService } from 'vs/platform/log/common/log'; import { RequestChannelClient } from 'vs/platform/request/common/requestIpc'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { RequestService as BrowserRequestService } from 'vs/platform/request/browser/requestService'; +import { RequestService } from 'vs/platform/request/browser/requestService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IRequestService } from 'vs/platform/request/common/request'; -export class RequestService extends BrowserRequestService { +export class BrowserRequestService extends RequestService { private readonly remoteRequestChannel: RequestChannelClient | null; @@ -40,5 +42,6 @@ export class RequestService extends BrowserRequestService { throw error; } } - } + +registerSingleton(IRequestService, BrowserRequestService, true); diff --git a/src/vs/workbench/services/request/electron-browser/requestService.ts b/src/vs/workbench/services/request/electron-browser/requestService.ts new file mode 100644 index 0000000000..5307880516 --- /dev/null +++ b/src/vs/workbench/services/request/electron-browser/requestService.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 { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILogService } from 'vs/platform/log/common/log'; +import { RequestService } from 'vs/platform/request/browser/requestService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { IElectronService } from 'vs/platform/electron/node/electron'; + +export class NativeRequestService extends RequestService { + + constructor( + @IConfigurationService configurationService: IConfigurationService, + @ILogService logService: ILogService, + @IElectronService private electronService: IElectronService + ) { + super(configurationService, logService); + } + + async resolveProxy(url: string): Promise { + return this.electronService.resolveProxy(url); + } +} + +registerSingleton(IRequestService, NativeRequestService, true); diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 1033bf5dc0..ef871cc07a 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -170,8 +170,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } 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 @@ -182,7 +180,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex 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) { + } else if (await this.hostService.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 diff --git a/src/vs/workbench/services/themes/common/colorThemeSchema.ts b/src/vs/workbench/services/themes/common/colorThemeSchema.ts index 877f23fd43..ab5b3db0ae 100644 --- a/src/vs/workbench/services/themes/common/colorThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/colorThemeSchema.ts @@ -192,7 +192,7 @@ export const colorThemeSchemaId = 'vscode://schemas/color-theme'; const colorThemeSchema: IJSONSchema = { type: 'object', allowComments: true, - allowsTrailingCommas: true, + allowTrailingCommas: true, properties: { colors: { description: nls.localize('schema.workbenchColors', 'Colors in the workbench'), diff --git a/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts b/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts index 0409c6b1e9..db2032ceef 100644 --- a/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/fileIconThemeSchema.ts @@ -12,7 +12,7 @@ const schemaId = 'vscode://schemas/icon-theme'; const schema: IJSONSchema = { type: 'object', allowComments: true, - allowsTrailingCommas: true, + allowTrailingCommas: true, definitions: { folderExpanded: { type: 'string', diff --git a/src/vs/workbench/services/update/browser/updateService.ts b/src/vs/workbench/services/update/browser/updateService.ts index b1fe90e03a..79b5cba00b 100644 --- a/src/vs/workbench/services/update/browser/updateService.ts +++ b/src/vs/workbench/services/update/browser/updateService.ts @@ -7,7 +7,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IUpdateService, State, UpdateType } from 'vs/platform/update/common/update'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { Disposable } from 'vs/base/common/lifecycle'; export interface IUpdate { @@ -40,7 +40,7 @@ export class UpdateService extends Disposable implements IUpdateService { constructor( @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IWindowService private readonly windowService: IWindowService + @IHostService private readonly hostService: IHostService ) { super(); @@ -84,11 +84,11 @@ export class UpdateService extends Disposable implements IUpdateService { } async applyUpdate(): Promise { - this.windowService.reloadWindow(); + this.hostService.reload(); } async quitAndInstall(): Promise { - this.windowService.reloadWindow(); + this.hostService.reload(); } } diff --git a/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts index 1a1636da55..b9badb76c1 100644 --- a/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts +++ b/src/vs/workbench/services/userDataSync/common/settingsMergeService.ts @@ -15,6 +15,8 @@ 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'; +import { values } from 'vs/base/common/map'; +import { IStringDictionary } from 'vs/base/common/collections'; class SettingsMergeService implements ISettingsMergeService { @@ -22,27 +24,27 @@ class SettingsMergeService implements ISettingsMergeService { constructor( @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService + @IModeService private readonly modeService: IModeService, ) { } - async merge(localContent: string, remoteContent: string, baseContent: string | null): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> { + async merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: IStringDictionary): 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); + const localToRemote = this.compare(local, remote, ignoredSettings); 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 baseToLocal = base ? this.compare(base, local, ignoredSettings) : { 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, ignoredSettings) : { 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()) { + for (const key of values(baseToLocal.removed)) { // Got updated in remote if (baseToRemote.updated.has(key)) { conflicts.add(key); @@ -50,7 +52,7 @@ class SettingsMergeService implements ISettingsMergeService { } // Removed settings in Remote - for (const key of baseToRemote.removed.keys()) { + for (const key of values(baseToRemote.removed)) { if (conflicts.has(key)) { continue; } @@ -63,7 +65,7 @@ class SettingsMergeService implements ISettingsMergeService { } // Added settings in Local - for (const key of baseToLocal.added.keys()) { + for (const key of values(baseToLocal.added)) { if (conflicts.has(key)) { continue; } @@ -77,7 +79,7 @@ class SettingsMergeService implements ISettingsMergeService { } // Added settings in remote - for (const key of baseToRemote.added.keys()) { + for (const key of values(baseToRemote.added)) { if (conflicts.has(key)) { continue; } @@ -93,7 +95,7 @@ class SettingsMergeService implements ISettingsMergeService { } // Updated settings in Local - for (const key of baseToLocal.updated.keys()) { + for (const key of values(baseToLocal.updated)) { if (conflicts.has(key)) { continue; } @@ -107,7 +109,7 @@ class SettingsMergeService implements ISettingsMergeService { } // Updated settings in Remote - for (const key of baseToRemote.updated.keys()) { + for (const key of values(baseToRemote.updated)) { if (conflicts.has(key)) { continue; } @@ -122,7 +124,7 @@ class SettingsMergeService implements ISettingsMergeService { } } - for (const key of conflicts.keys()) { + for (const key of values(conflicts)) { const tree = parseTree(settingsPreviewModel.getValue()); const valueNode = findNodeAtLocation(tree, [key]); const eol = settingsPreviewModel.getEOL(); @@ -149,6 +151,18 @@ class SettingsMergeService implements ISettingsMergeService { return { mergeContent: settingsPreviewModel.getValue(), hasChanges: true, hasConflicts: conflicts.size > 0 }; } + async computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: IStringDictionary): Promise { + const remote = parse(remoteContent); + const remoteModel = this.modelService.createModel(localContent, this.modeService.create('jsonc')); + for (const key of Object.keys(ignoredSettings)) { + if (ignoredSettings[key]) { + this.editSetting(remoteModel, key, undefined); + this.editSetting(remoteModel, key, remote[key]); + } + } + return remoteModel.getValue(); + } + private editSetting(model: ITextModel, key: string, value: any | undefined): void { const insertSpaces = false; const tabSize = 4; @@ -166,9 +180,9 @@ class SettingsMergeService implements ISettingsMergeService { } } - 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); + private compare(from: IStringDictionary, to: IStringDictionary, ignored: IStringDictionary): { added: Set, removed: Set, updated: Set } { + const fromKeys = Object.keys(from).filter(key => !ignored[key]); + const toKeys = Object.keys(to).filter(key => !ignored[key]); 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(); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index 830ac59b59..918fd7f6a6 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -3,12 +3,13 @@ * 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 { SyncStatus, SyncSource, IUserDataSyncService, ISyncExtension } 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'; +import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -31,13 +32,24 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ ) { super(); this.channel = sharedProcessService.getChannel('userDataSync'); - this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); + this.channel.call('_getInitialStatus').then(status => { + this.updateStatus(status); + this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); + }); } sync(_continue?: boolean): Promise { return this.channel.call('sync', [_continue]); } + getRemoteExtensions(): Promise { + return this.channel.call('getRemoteExtensions'); + } + + removeExtension(identifier: IExtensionIdentifier): Promise { + return this.channel.call('removeExtension', [identifier]); + } + private async updateStatus(status: SyncStatus): Promise { this._conflictsSource = await this.channel.call('getConflictsSource'); this._status = status; diff --git a/src/vs/workbench/services/window/electron-browser/windowService.ts b/src/vs/workbench/services/window/electron-browser/windowService.ts index c62f97a26c..1cb8651eb6 100644 --- a/src/vs/workbench/services/window/electron-browser/windowService.ts +++ b/src/vs/workbench/services/window/electron-browser/windowService.ts @@ -4,10 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IWindowService, IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IDevToolsOptions, IOpenSettings, IURIToOpen, isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/windows'; +import { IWindowService, IWindowsService, IOpenSettings, IURIToOpen, isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/windows'; import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; -import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; import { Disposable } from 'vs/base/common/lifecycle'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -53,50 +51,6 @@ export class WindowService extends Disposable implements IWindowService { return this._windowId; } - pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise { - options.windowId = this.windowId; - - return this.windowsService.pickFileFolderAndOpen(options); - } - - pickFileAndOpen(options: INativeOpenDialogOptions): Promise { - options.windowId = this.windowId; - - return this.windowsService.pickFileAndOpen(options); - } - - pickFolderAndOpen(options: INativeOpenDialogOptions): Promise { - options.windowId = this.windowId; - - return this.windowsService.pickFolderAndOpen(options); - } - - pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise { - options.windowId = this.windowId; - - return this.windowsService.pickWorkspaceAndOpen(options); - } - - reloadWindow(args?: ParsedArgs): Promise { - return this.windowsService.reloadWindow(this.windowId, args); - } - - openDevTools(options?: IDevToolsOptions): Promise { - return this.windowsService.openDevTools(this.windowId, options); - } - - toggleDevTools(): Promise { - return this.windowsService.toggleDevTools(this.windowId); - } - - closeWorkspace(): Promise { - return this.windowsService.closeWorkspace(this.windowId); - } - - enterWorkspace(path: URI): Promise { - return this.windowsService.enterWorkspace(this.windowId, path); - } - openWindow(uris: IURIToOpen[], options: IOpenSettings = {}): Promise { if (!!this.remoteAuthority) { uris.forEach(u => u.label = u.label || this.getRecentLabel(u)); @@ -105,18 +59,6 @@ export class WindowService extends Disposable implements IWindowService { return this.windowsService.openWindow(this.windowId, uris, options); } - closeWindow(): Promise { - return this.windowsService.closeWindow(this.windowId); - } - - toggleFullScreen(target?: HTMLElement): Promise { - return this.windowsService.toggleFullScreen(this.windowId); - } - - setRepresentedFilename(fileName: string): Promise { - return this.windowsService.setRepresentedFilename(this.windowId, fileName); - } - getRecentlyOpened(): Promise { return this.windowsService.getRecentlyOpened(this.windowId); } @@ -137,50 +79,6 @@ export class WindowService extends Disposable implements IWindowService { return this.windowsService.isFocused(this.windowId); } - isMaximized(): Promise { - return this.windowsService.isMaximized(this.windowId); - } - - maximizeWindow(): Promise { - return this.windowsService.maximizeWindow(this.windowId); - } - - unmaximizeWindow(): Promise { - return this.windowsService.unmaximizeWindow(this.windowId); - } - - minimizeWindow(): Promise { - return this.windowsService.minimizeWindow(this.windowId); - } - - onWindowTitleDoubleClick(): Promise { - return this.windowsService.onWindowTitleDoubleClick(this.windowId); - } - - setDocumentEdited(flag: boolean): Promise { - return this.windowsService.setDocumentEdited(this.windowId, flag); - } - - showMessageBox(options: Electron.MessageBoxOptions): Promise { - return this.windowsService.showMessageBox(this.windowId, options); - } - - showSaveDialog(options: Electron.SaveDialogOptions): Promise { - return this.windowsService.showSaveDialog(this.windowId, options); - } - - showOpenDialog(options: Electron.OpenDialogOptions): Promise { - return this.windowsService.showOpenDialog(this.windowId, options); - } - - updateTouchBar(items: ISerializableCommandAction[][]): Promise { - return this.windowsService.updateTouchBar(this.windowId, items); - } - - resolveProxy(url: string): Promise { - return this.windowsService.resolveProxy(this.windowId, url); - } - private getRecentLabel(u: IURIToOpen): string { if (isFolderToOpen(u)) { return this.labelService.getWorkspaceLabel(u.folderUri, { verbose: true }); @@ -192,4 +90,4 @@ export class WindowService extends Disposable implements IWindowService { } } -registerSingleton(IWindowService, WindowService); \ No newline at end of file +registerSingleton(IWindowService, WindowService); diff --git a/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts index 9e8d015aa7..ec5d3ccffe 100644 --- a/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts @@ -7,7 +7,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IWindowService, MessageBoxOptions, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; @@ -50,7 +50,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { @IFileService private readonly fileService: IFileService, @ITextFileService private readonly textFileService: ITextFileService, @IWindowsService private readonly windowsService: IWindowsService, - @IWorkspacesService private readonly workspaceService: IWorkspacesService, + @IWorkspacesService private readonly workspacesService: IWorkspacesService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IDialogService private readonly dialogService: IDialogService, @@ -123,7 +123,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { // Don't Save: delete workspace case ConfirmResult.DONT_SAVE: - this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); + this.workspacesService.deleteUntitledWorkspace(workspaceIdentifier); return false; // Save: save workspace, but do not veto unload if path provided @@ -136,12 +136,12 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { try { await this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath); - const newWorkspaceIdentifier = await this.workspaceService.getWorkspaceIdentifier(newWorkspacePath); + const newWorkspaceIdentifier = await this.workspacesService.getWorkspaceIdentifier(newWorkspacePath); const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true }); this.windowService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]); - this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); + this.workspacesService.deleteUntitledWorkspace(workspaceIdentifier); } catch (error) { // ignore } @@ -293,7 +293,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } const remoteAuthority = this.environmentService.configuration.remoteAuthority; - const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders, remoteAuthority); + const untitledWorkspace = await this.workspacesService.createUntitledWorkspace(folders, remoteAuthority); if (path) { await this.saveWorkspaceAs(untitledWorkspace, path); } else { @@ -323,14 +323,14 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { // Prevent overwriting a workspace that is currently opened in another window if (windows.some(window => !!window.workspace && isEqual(window.workspace.configPath, path))) { - const options: MessageBoxOptions = { - type: 'info', - buttons: [nls.localize('ok', "OK")], - message: nls.localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)), - detail: nls.localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again."), - noLink: true - }; - await this.windowService.showMessageBox(options); + await this.dialogService.show( + Severity.Info, + nls.localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)), + [nls.localize('ok', "OK")], + { + detail: nls.localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again.") + } + ); return false; } @@ -389,7 +389,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { throw new Error('Entering a new workspace is not possible in tests.'); } - const workspace = await this.workspaceService.getWorkspaceIdentifier(path); + const workspace = await this.workspacesService.getWorkspaceIdentifier(path); // Settings migration (only if we come from a folder workspace) if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { @@ -399,7 +399,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { const workspaceImpl = this.contextService as WorkspaceService; await workspaceImpl.initialize(workspace); - const result = await this.windowService.enterWorkspace(path); + const result = await this.workspacesService.enterWorkspace(path); if (result) { // Migrate storage to new workspace @@ -415,7 +415,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { // TODO@aeschli: workaround until restarting works if (this.environmentService.configuration.remoteAuthority) { - this.windowService.reloadWindow(); + this.hostService.reload(); } // Restart the extension host: entering a workspace means a new location for diff --git a/src/vs/workbench/services/workspace/browser/workspacesService.ts b/src/vs/workbench/services/workspace/browser/workspacesService.ts index 94034b80a9..f8d6668fbd 100644 --- a/src/vs/workbench/services/workspace/browser/workspacesService.ts +++ b/src/vs/workbench/services/workspace/browser/workspacesService.ts @@ -6,20 +6,25 @@ 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'; +import { IEnterWorkspaceResult } from 'vs/platform/windows/common/windows'; export class WorkspacesService implements IWorkspacesService { _serviceBrand: undefined; - async createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { + enterWorkspace(path: URI): Promise { throw new Error('Untitled workspaces are currently unsupported in Web'); } - async deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise { + createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { throw new Error('Untitled workspaces are currently unsupported in Web'); } - async getWorkspaceIdentifier(workspacePath: URI): Promise { + deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise { + throw new Error('Untitled workspaces are currently unsupported in Web'); + } + + getWorkspaceIdentifier(workspacePath: URI): Promise { throw new Error('Untitled workspaces are currently unsupported in Web'); } } diff --git a/src/vs/workbench/services/workspace/electron-browser/workspacesService.ts b/src/vs/workbench/services/workspace/electron-browser/workspacesService.ts index 0249e060ee..f0b84fa86b 100644 --- a/src/vs/workbench/services/workspace/electron-browser/workspacesService.ts +++ b/src/vs/workbench/services/workspace/electron-browser/workspacesService.ts @@ -8,6 +8,7 @@ import { IWorkspacesService, IWorkspaceIdentifier, IWorkspaceFolderCreationData, import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { URI } from 'vs/base/common/uri'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWindowService, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows'; export class WorkspacesService implements IWorkspacesService { @@ -15,10 +16,22 @@ export class WorkspacesService implements IWorkspacesService { private channel: IChannel; - constructor(@IMainProcessService mainProcessService: IMainProcessService) { + constructor( + @IMainProcessService mainProcessService: IMainProcessService, + @IWindowService private readonly windowService: IWindowService + ) { this.channel = mainProcessService.getChannel('workspaces'); } + async enterWorkspace(path: URI): Promise { + const result: IEnterWorkspaceResult = await this.channel.call('enterWorkspace', [this.windowService.windowId, path]); + if (result) { + result.workspace = reviveWorkspaceIdentifier(result.workspace); + } + + return result; + } + createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { return this.channel.call('createUntitledWorkspace', [folders, remoteAuthority]).then(reviveWorkspaceIdentifier); } diff --git a/src/vs/workbench/test/common/editor/editorGroups.test.ts b/src/vs/workbench/test/common/editor/editorGroups.test.ts index 727b95eb42..73b806eafe 100644 --- a/src/vs/workbench/test/common/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/common/editor/editorGroups.test.ts @@ -138,8 +138,6 @@ interface ISerializedTestInput { class TestEditorInputFactory implements IEditorInputFactory { - constructor() { } - serialize(editorInput: EditorInput): string { let testEditorInput = editorInput; let testInput: ISerializedTestInput = { @@ -1303,4 +1301,4 @@ suite('Workbench editor groups', () => { assert.equal(dirty1Counter, 1); assert.equal(label1ChangeCounter, 1); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/contrib/linkProtection.test.ts b/src/vs/workbench/test/contrib/linkProtection.test.ts index 0facf78c49..1595e01ccb 100644 --- a/src/vs/workbench/test/contrib/linkProtection.test.ts +++ b/src/vs/workbench/test/contrib/linkProtection.test.ts @@ -39,4 +39,11 @@ suite('Link protection domain matching', () => { 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'])); }); + + test('no scheme', () => { + assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['a.x.org'])); + assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['*.x.org'])); + assert.ok(isURLDomainTrusted(URI.parse('https://a.b.x.org'), ['*.x.org'])); + assert.ok(isURLDomainTrusted(URI.parse('https://x.org'), ['*.x.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 0f3ff60ce4..8027d1bc60 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts @@ -28,8 +28,8 @@ const emptyDialogService = new class implements IDialogService { const emptyCommandService: ICommandService = { _serviceBrand: undefined, - onWillExecuteCommand: () => ({ dispose: () => { } }), - onDidExecuteCommand: () => ({ dispose: () => { } }), + onWillExecuteCommand: () => Disposable.None, + onDidExecuteCommand: () => Disposable.None, executeCommand: (commandId: string, ...args: any[]): Promise => { return Promise.resolve(undefined); } diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index fe8a3099f3..7740878118 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -150,7 +150,7 @@ class TestTelemetryService implements ITelemetryService { public events: any[] = []; - private emitter = new Emitter(); + private readonly emitter = new Emitter(); public get eventLogged(): Event { return this.emitter.event; diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 9f4dcb28dd..0c8961b1b8 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -35,17 +35,17 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { IWindowsService, IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, MenuBarVisibility, IURIToOpen, IOpenSettings, IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IWindowService, MenuBarVisibility, IURIToOpen, IOpenSettings, IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; -import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/position'; -import { IMenuService, MenuId, IMenu, ISerializableCommandAction } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference, IModelDecorationOptions, ITextModel, ITextSnapshot } from 'vs/editor/common/model'; @@ -71,7 +71,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ViewletDescriptor, Viewlet } from 'vs/workbench/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; -import { isLinux, isMacintosh, IProcessEnvironment } from 'vs/base/common/platform'; +import { isLinux, isMacintosh } from 'vs/base/common/platform'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; import { IDimension } from 'vs/platform/layout/browser/layoutService'; import { Part } from 'vs/workbench/browser/part'; @@ -460,8 +460,8 @@ export class TestLayoutService implements IWorkbenchLayoutService { onPanelPositionChange: Event = Event.None; onLayout = Event.None; - private _onTitleBarVisibilityChange = new Emitter(); - private _onMenubarVisibilityChange = new Emitter(); + private readonly _onTitleBarVisibilityChange = new Emitter(); + private readonly _onMenubarVisibilityChange = new Emitter(); public get onTitleBarVisibilityChange(): Event { return this._onTitleBarVisibilityChange.event; @@ -1167,14 +1167,14 @@ export class TestCodeEditorService implements ICodeEditorService { addDiffEditor(_editor: IDiffEditor): void { } removeDiffEditor(_editor: IDiffEditor): void { } listDiffEditors(): IDiffEditor[] { return []; } - getFocusedCodeEditor(): ICodeEditor | null { return null; } + getFocusedCodeEditor(): ICodeEditor | undefined { return undefined; } registerDecorationType(_key: string, _options: IDecorationRenderOptions, _parentTypeKey?: string): void { } removeDecorationType(_key: string): void { } resolveDecorationOptions(_typeKey: string, _writable: boolean): IModelDecorationOptions { return Object.create(null); } setTransientModelProperty(_model: ITextModel, _key: string, _value: any): void { } getTransientModelProperty(_model: ITextModel, _key: string) { } - getActiveCodeEditor(): ICodeEditor | null { return null; } - openCodeEditor(_input: IResourceInput, _source: ICodeEditor, _sideBySide?: boolean): Promise { return Promise.resolve(null); } + getActiveCodeEditor(): ICodeEditor | undefined { return undefined; } + openCodeEditor(_input: IResourceInput, _source: ICodeEditor, _sideBySide?: boolean): Promise { return Promise.resolve(undefined); } } export class TestWindowService implements IWindowService { @@ -1192,54 +1192,6 @@ export class TestWindowService implements IWindowService { return Promise.resolve(false); } - isMaximized(): Promise { - return Promise.resolve(false); - } - - pickFileFolderAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - pickFileAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - pickFolderAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - pickWorkspaceAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - reloadWindow(): Promise { - return Promise.resolve(); - } - - openDevTools(): Promise { - return Promise.resolve(); - } - - toggleDevTools(): Promise { - return Promise.resolve(); - } - - closeWorkspace(): Promise { - return Promise.resolve(); - } - - enterWorkspace(_path: URI): Promise { - return Promise.resolve(undefined); - } - - toggleFullScreen(): Promise { - return Promise.resolve(); - } - - setRepresentedFilename(_fileName: string): Promise { - return Promise.resolve(); - } - getRecentlyOpened(): Promise { return Promise.resolve({ workspaces: [], @@ -1259,53 +1211,9 @@ export class TestWindowService implements IWindowService { return Promise.resolve(); } - maximizeWindow(): Promise { - return Promise.resolve(); - } - - unmaximizeWindow(): Promise { - return Promise.resolve(); - } - - minimizeWindow(): Promise { - return Promise.resolve(); - } - openWindow(_uris: IURIToOpen[], _options?: IOpenSettings): Promise { return Promise.resolve(); } - - closeWindow(): Promise { - return Promise.resolve(); - } - - setDocumentEdited(_flag: boolean): Promise { - return Promise.resolve(); - } - - onWindowTitleDoubleClick(): Promise { - return Promise.resolve(); - } - - showMessageBox(_options: Electron.MessageBoxOptions): Promise { - return Promise.resolve({ button: 0 }); - } - - showSaveDialog(_options: Electron.SaveDialogOptions): Promise { - throw new Error('not implemented'); - } - - showOpenDialog(_options: Electron.OpenDialogOptions): Promise { - throw new Error('not implemented'); - } - - updateTouchBar(_items: ISerializableCommandAction[][]): Promise { - return Promise.resolve(); - } - - resolveProxy(url: string): Promise { - return Promise.resolve(undefined); - } } export class TestLifecycleService implements ILifecycleService { @@ -1315,9 +1223,9 @@ export class TestLifecycleService implements ILifecycleService { public phase: LifecyclePhase; public startupKind: StartupKind; - private _onBeforeShutdown = new Emitter(); - private _onWillShutdown = new Emitter(); - private _onShutdown = new Emitter(); + private readonly _onBeforeShutdown = new Emitter(); + private readonly _onWillShutdown = new Emitter(); + private readonly _onShutdown = new Emitter(); when(): Promise { return Promise.resolve(); @@ -1362,50 +1270,6 @@ export class TestWindowsService implements IWindowsService { return Promise.resolve(false); } - pickFileFolderAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - pickFileAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - pickFolderAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - pickWorkspaceAndOpen(_options: INativeOpenDialogOptions): Promise { - return Promise.resolve(); - } - - reloadWindow(_windowId: number): Promise { - return Promise.resolve(); - } - - openDevTools(_windowId: number): Promise { - return Promise.resolve(); - } - - toggleDevTools(_windowId: number): Promise { - return Promise.resolve(); - } - - closeWorkspace(_windowId: number): Promise { - return Promise.resolve(); - } - - enterWorkspace(_windowId: number, _path: URI): Promise { - return Promise.resolve(undefined); - } - - toggleFullScreen(_windowId: number): Promise { - return Promise.resolve(); - } - - setRepresentedFilename(_windowId: number, _fileName: string): Promise { - return Promise.resolve(); - } - addRecentlyOpened(_recents: IRecent[]): Promise { return Promise.resolve(); } @@ -1429,125 +1293,18 @@ export class TestWindowsService implements IWindowsService { return Promise.resolve(); } - closeWindow(_windowId: number): Promise { - return Promise.resolve(); - } - - isMaximized(_windowId: number): Promise { - return Promise.resolve(false); - } - - maximizeWindow(_windowId: number): Promise { - return Promise.resolve(); - } - - minimizeWindow(_windowId: number): Promise { - return Promise.resolve(); - } - - unmaximizeWindow(_windowId: number): Promise { - return Promise.resolve(); - } - - onWindowTitleDoubleClick(_windowId: number): Promise { - return Promise.resolve(); - } - - setDocumentEdited(_windowId: number, _flag: boolean): Promise { - return Promise.resolve(); - } - - quit(): Promise { - return Promise.resolve(); - } - - relaunch(_options: { addArgs?: string[], removeArgs?: string[] }): Promise { - return Promise.resolve(); - } - - whenSharedProcessReady(): Promise { - return Promise.resolve(); - } - - toggleSharedProcess(): Promise { - return Promise.resolve(); - } - // Global methods openWindow(_windowId: number, _uris: IURIToOpen[], _options: IOpenSettings): Promise { return Promise.resolve(); } - openNewWindow(): Promise { - return Promise.resolve(); - } - - openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { - return Promise.resolve(); - } - getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { throw new Error('not implemented'); } - newWindowTab(): Promise { - return Promise.resolve(); - } - - showPreviousWindowTab(): Promise { - return Promise.resolve(); - } - - showNextWindowTab(): Promise { - return Promise.resolve(); - } - - moveWindowTabToNewWindow(): Promise { - return Promise.resolve(); - } - - mergeAllWindowTabs(): Promise { - return Promise.resolve(); - } - - toggleWindowTabsBar(): Promise { - return Promise.resolve(); - } - - updateTouchBar(_windowId: number, _items: ISerializableCommandAction[][]): Promise { - return Promise.resolve(); - } - getActiveWindowId(): Promise { return Promise.resolve(undefined); } - - // This needs to be handled from browser process to prevent - // foreground ordering issues on Windows - openExternal(_url: string): Promise { - return Promise.resolve(true); - } - - // TODO: this is a bit backwards - startCrashReporter(_config: Electron.CrashReporterStartOptions): Promise { - return Promise.resolve(); - } - - showMessageBox(_windowId: number, _options: Electron.MessageBoxOptions): Promise { - throw new Error('not implemented'); - } - - showSaveDialog(_windowId: number, _options: Electron.SaveDialogOptions): Promise { - throw new Error('not implemented'); - } - - showOpenDialog(_windowId: number, _options: Electron.OpenDialogOptions): Promise { - throw new Error('not implemented'); - } - - resolveProxy(windowId: number, url: string): Promise { - return Promise.resolve(undefined); - } } export class TestTextResourceConfigurationService implements ITextResourceConfigurationService { @@ -1598,6 +1355,9 @@ export class TestSharedProcessService implements ISharedProcessService { } registerChannel(channelName: string, channel: any): void { } + + async toggleSharedProcessWindow(): Promise { } + async whenSharedProcessReady(): Promise { } } export class RemoteFileSystemProvider implements IFileSystemProvider { @@ -1636,4 +1396,12 @@ export class TestHostService implements IHostService { _serviceBrand: undefined; windowCount = Promise.resolve(1); + + async restart(): Promise { } + async reload(): Promise { } + async closeWorkspace(): Promise { } + + async openEmptyWindow(options?: { reuse?: boolean, remoteAuthority?: string }): Promise { } + + async toggleFullScreen(): Promise { } } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 5f572bdf26..30641f25b0 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -57,7 +57,7 @@ import 'vs/workbench/browser/parts/views/views'; //#region --- workbench services -import 'vs/workbench/services/extensions/common/extensionUrlHandler'; +import 'vs/workbench/services/extensions/browser/extensionUrlHandler'; import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; import 'vs/workbench/services/keybinding/common/keybindingEditing'; import 'vs/workbench/services/decorations/browser/decorationsService'; @@ -66,7 +66,6 @@ import 'vs/workbench/services/editor/browser/codeEditorService'; import 'vs/workbench/services/preferences/browser/preferencesService'; import 'vs/workbench/services/configuration/common/jsonEditingService'; import 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; -import 'vs/workbench/services/dialogs/browser/fileDialogService'; import 'vs/workbench/services/editor/browser/editorService'; import 'vs/workbench/services/history/browser/history'; import 'vs/workbench/services/activity/browser/activityService'; @@ -82,7 +81,6 @@ 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'; @@ -198,7 +196,7 @@ import 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; import 'vs/workbench/contrib/terminal/browser/terminalPanel'; // Relauncher -import 'vs/workbench/contrib/relauncher/common/relauncher.contribution'; +import 'vs/workbench/contrib/relauncher/browser/relauncher.contribution'; // Tasks // import 'vs/workbench/contrib/tasks/browser/task.contribution'; {{SQL CARBON EDIT}} diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index e1a22ac4fc..bc20b83377 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -28,6 +28,7 @@ import 'vs/workbench/electron-browser/desktop.main'; //#region --- workbench services +import 'vs/workbench/services/dialogs/electron-browser/fileDialogService'; import 'vs/workbench/services/integrity/node/integrityService'; import 'vs/workbench/services/textMate/electron-browser/textMateService'; import 'vs/workbench/services/search/node/searchService'; @@ -52,12 +53,11 @@ 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 'vs/workbench/services/request/electron-browser/requestService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService'; -import { IRequestService } from 'vs/platform/request/common/request'; -import { RequestService } from 'vs/platform/request/browser/requestService'; import { LifecycleService } from 'vs/platform/lifecycle/electron-browser/lifecycleService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; @@ -75,7 +75,6 @@ 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); registerSingleton(ILifecycleService, LifecycleService); registerSingleton(ILocalizationsService, LocalizationsService); registerSingleton(ISharedProcessService, SharedProcessService, true); @@ -212,6 +211,7 @@ import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; // Explorer +import 'vs/workbench/contrib/files/electron-browser/files.contribution'; import 'vs/workbench/contrib/files/electron-browser/fileActions.contribution'; // Debug @@ -257,6 +257,9 @@ import 'vs/workbench/contrib/tasks/electron-browser/taskService'; // User Data Sync import 'vs/workbench/contrib/userDataSync/electron-browser/userDataSync.contribution'; +// Welcome +import 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/openWebsite.contribution'; + //#endregion // {{SQL CARBON EDIT}} diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 36e57cac6c..fa7593caa3 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -42,11 +42,12 @@ 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/services/dialogs/browser/fileDialogService'; +import 'vs/workbench/services/host/browser/browserHostService'; +import 'vs/workbench/services/request/browser/requestService'; import 'vs/workbench/browser/web.simpleservices'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IRequestService } from 'vs/platform/request/common/request'; -import { RequestService } from 'vs/workbench/services/request/browser/requestService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -65,7 +66,6 @@ import { IUserDataSyncStoreService, IUserDataSyncService } from 'vs/platform/use 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(IClipboardService, BrowserClipboardService, true); @@ -81,6 +81,9 @@ registerSingleton(IUserDataSyncService, UserDataSyncService); //#region --- workbench contributions +// Explorer +import 'vs/workbench/contrib/files/browser/files.web.contribution'; + // Preferences import 'vs/workbench/contrib/preferences/browser/keyboardLayoutPicker'; diff --git a/test/automation/src/search.ts b/test/automation/src/search.ts index 80dd41822a..fc46e510ec 100644 --- a/test/automation/src/search.ts +++ b/test/automation/src/search.ts @@ -74,12 +74,12 @@ export class Search extends Viewlet { await retry( () => this.code.waitAndClick(fileMatch), - () => this.code.waitForElement(`${fileMatch} .action-label.icon.action-remove`, el => !!el && el.top > 0 && el.left > 0, 10) + () => this.code.waitForElement(`${fileMatch} .action-label.codicon.codicon-close`, el => !!el && el.top > 0 && el.left > 0, 10) ); // ¯\_(ツ)_/¯ await new Promise(c => setTimeout(c, 500)); - await this.code.waitAndClick(`${fileMatch} .action-label.icon.action-remove`); + await this.code.waitAndClick(`${fileMatch} .action-label.codicon.codicon-close`); await this.code.waitForElement(fileMatch, el => !el); } @@ -100,12 +100,12 @@ export class Search extends Viewlet { await retry( () => this.code.waitAndClick(fileMatch), - () => this.code.waitForElement(`${fileMatch} .action-label.icon.action-replace-all`, el => !!el && el.top > 0 && el.left > 0, 10) + () => this.code.waitForElement(`${fileMatch} .action-label.codicon.action-replace-all`, el => !!el && el.top > 0 && el.left > 0, 10) ); // ¯\_(ツ)_/¯ await new Promise(c => setTimeout(c, 500)); - await this.code.waitAndClick(`${fileMatch} .action-label.icon.action-replace-all`); + await this.code.waitAndClick(`${fileMatch} .action-label.codicon.action-replace-all`); } async waitForResultText(text: string): Promise { diff --git a/yarn.lock b/yarn.lock index e1f51cfd9d..71b434ab94 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9348,10 +9348,10 @@ vsce@1.48.0: yauzl "^2.3.1" yazl "^2.2.2" -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-debugprotocol@1.37.0: + version "1.37.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.37.0.tgz#e8c4694a078d18ea1a639553a7a241b35c1e6f6d" + integrity sha512-ppZLEBbFRVNsK0YpfgFi/x2CDyihx0F+UpdKmgeJcvi05UgSXYdO0n9sDVYwoGvvYQPvlpDQeWuY0nloOC4mPA== vscode-minimist@^1.2.1: version "1.2.1"