diff --git a/.github/commands.yml b/.github/commands.yml index c649e6c4d89..ea0895b3dd2 100644 --- a/.github/commands.yml +++ b/.github/commands.yml @@ -118,10 +118,10 @@ }, { type: 'label', - name: '*needs more info', + name: '~needs more info', action: 'updateLabels', addLabel: 'needs more info', - removeLabel: '*needs more info', + removeLabel: '~needs more info', comment: "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting](https://aka.ms/vscodeissuereporting) guidelines. Please take the time to review these and update the issue.\n\nHappy Coding!" }, { @@ -131,12 +131,5 @@ action: 'updateLabels', addLabel: 'a11ymas' }, - { - type: 'label', - name: '*needs more info', - action: 'updateLabels', - addLabel: 'needs more info', - removeLabel: '*needs more info' - }, ] } diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 29c39c9b5e4..7365025c343 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -1,7 +1,7 @@ [ { "name": "ms-vscode.node-debug", - "version": "1.40.1", + "version": "1.41.0", "repo": "https://github.com/Microsoft/vscode-node-debug", "metadata": { "id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6", @@ -16,7 +16,7 @@ }, { "name": "ms-vscode.node-debug2", - "version": "1.39.3", + "version": "1.41.0", "repo": "https://github.com/Microsoft/vscode-node-debug2", "metadata": { "id": "36d19e17-7569-4841-a001-947eb18602b2", @@ -31,7 +31,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.36", + "version": "0.0.37", "repo": "https://github.com/Microsoft/vscode-reference-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index 72e2fcf627f..644e9bb1192 100644 --- a/extensions/configuration-editing/src/extension.ts +++ b/extensions/configuration-editing/src/extension.ts @@ -4,23 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { getLocation, parse, visit } from 'jsonc-parser'; -import * as path from 'path'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; import { SettingsDocument } from './settingsDocumentHelper'; const localize = nls.loadMessageBundle(); -const fadedDecoration = vscode.window.createTextEditorDecorationType({ - light: { - color: '#757575' - }, - dark: { - color: '#878787' - } -}); - -let pendingLaunchJsonDecoration: NodeJS.Timer; - export function activate(context: vscode.ExtensionContext): void { //settings.json suggestions context.subscriptions.push(registerSettingsCompletions()); @@ -33,18 +21,6 @@ export function activate(context: vscode.ExtensionContext): void { // task.json variable suggestions context.subscriptions.push(registerVariableCompletions('**/tasks.json')); - - // launch.json decorations - context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(editor => updateLaunchJsonDecorations(editor), null, context.subscriptions)); - context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(event => { - if (vscode.window.activeTextEditor && event.document === vscode.window.activeTextEditor.document) { - if (pendingLaunchJsonDecoration) { - clearTimeout(pendingLaunchJsonDecoration); - } - pendingLaunchJsonDecoration = setTimeout(() => updateLaunchJsonDecorations(vscode.window.activeTextEditor), 1000); - } - }, null, context.subscriptions)); - updateLaunchJsonDecorations(vscode.window.activeTextEditor); } function registerSettingsCompletions(): vscode.Disposable { @@ -153,39 +129,6 @@ function provideInstalledExtensionProposals(extensionsContent: IExtensionsConten return undefined; } -function updateLaunchJsonDecorations(editor: vscode.TextEditor | undefined): void { - if (!editor || path.basename(editor.document.fileName) !== 'launch.json') { - return; - } - - const ranges: vscode.Range[] = []; - let addPropertyAndValue = false; - let depthInArray = 0; - visit(editor.document.getText(), { - onObjectProperty: (property, offset, length) => { - // Decorate attributes which are unlikely to be edited by the user. - // Only decorate "configurations" if it is not inside an array (compounds have a configurations property which should not be decorated). - addPropertyAndValue = property === 'version' || property === 'type' || property === 'request' || property === 'compounds' || (property === 'configurations' && depthInArray === 0); - if (addPropertyAndValue) { - ranges.push(new vscode.Range(editor.document.positionAt(offset), editor.document.positionAt(offset + length))); - } - }, - onLiteralValue: (_value, offset, length) => { - if (addPropertyAndValue) { - ranges.push(new vscode.Range(editor.document.positionAt(offset), editor.document.positionAt(offset + length))); - } - }, - onArrayBegin: (_offset: number, _length: number) => { - depthInArray++; - }, - onArrayEnd: (_offset: number, _length: number) => { - depthInArray--; - } - }); - - editor.setDecorations(fadedDecoration, ranges); -} - vscode.languages.registerDocumentSymbolProvider({ pattern: '**/launch.json', language: 'jsonc' }, { provideDocumentSymbols(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult { const result: vscode.SymbolInformation[] = []; diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 51bf7d9568d..e376922dbaf 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -82,7 +82,8 @@ namespace ServerState { export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient { private static readonly WALK_THROUGH_SNIPPET_SCHEME_COLON = `${fileSchemes.walkThroughSnippet}:`; - private pathSeparator: string; + private readonly pathSeparator: string; + private readonly inMemoryResourcePrefix = '^'; private _onReady?: { promise: Promise; resolve: () => void; reject: () => void; }; private _configuration: TypeScriptServiceConfiguration; @@ -591,23 +592,18 @@ export default class TypeScriptServiceClient extends Disposable implements IType return this.toPath(document.uri) || undefined; } - private get inMemoryResourcePrefix(): string { - return this.apiVersion.gte(API.v270) ? '^' : ''; - } - public toResource(filepath: string): vscode.Uri { if (filepath.startsWith(TypeScriptServiceClient.WALK_THROUGH_SNIPPET_SCHEME_COLON) || (filepath.startsWith(fileSchemes.untitled + ':')) ) { let resource = vscode.Uri.parse(filepath); - if (this.inMemoryResourcePrefix) { - const dirName = path.dirname(resource.path); - const fileName = path.basename(resource.path); - if (fileName.startsWith(this.inMemoryResourcePrefix)) { - resource = resource.with({ - path: path.posix.join(dirName, fileName.slice(this.inMemoryResourcePrefix.length)) - }); - } + const dirName = path.dirname(resource.path); + const fileName = path.basename(resource.path); + if (fileName.startsWith(this.inMemoryResourcePrefix)) { + resource = resource.with({ + path: path.posix.join(dirName, fileName.slice(this.inMemoryResourcePrefix.length)) + }); } + return this.bufferSyncSupport.toVsCodeResource(resource); } diff --git a/package.json b/package.json index 7ec46913bfc..ec7cd7ba956 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.41.0", - "distro": "be6ad88ea0214dfdb03b943bef28e7a5c9fc2e4b", + "distro": "403ab44be562c63a0cde1969fd8f5b45ff51709c", "author": { "name": "Microsoft Corporation" }, @@ -45,9 +45,9 @@ "onigasm-umd": "^2.2.4", "semver-umd": "^5.5.3", "spdlog": "^0.11.1", - "sudo-prompt": "9.0.0", + "sudo-prompt": "9.1.1", "v8-inspect-profiler": "^0.0.20", - "vscode-minimist": "^1.2.1", + "vscode-minimist": "^1.2.2", "vscode-nsfw": "1.2.8", "vscode-proxy-agent": "^0.5.2", "vscode-ripgrep": "^1.5.7", diff --git a/remote/package.json b/remote/package.json index 446f54ff57e..7b87df3defd 100644 --- a/remote/package.json +++ b/remote/package.json @@ -15,7 +15,7 @@ "onigasm-umd": "^2.2.4", "semver-umd": "^5.5.3", "spdlog": "^0.11.1", - "vscode-minimist": "^1.2.1", + "vscode-minimist": "^1.2.2", "vscode-nsfw": "1.2.8", "vscode-proxy-agent": "^0.5.2", "vscode-ripgrep": "^1.5.7", diff --git a/remote/yarn.lock b/remote/yarn.lock index 99fb10b59c3..8f2882c9714 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -369,10 +369,10 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -vscode-minimist@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.1.tgz#e63d3f4a9bf3680dcb8f9304eed612323fd6926a" - integrity sha512-cmB72+qDoiCFJ1UKnGUBdGYfXzdpJ3bQM/D/+XhkVk5v7uZgLbYiCz5JcwVyk7NC7hSi5VGtQ4wihzmi12NeXw== +vscode-minimist@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.2.tgz#65403f44f0c6010d259b2271d36eb5c6f4ad8aab" + integrity sha512-DXMNG2QgrXn1jOP12LzjVfvxVkzxv/0Qa27JrMBj/XP2esj+fJ/wP2T4YUH5derj73Lc96dC8F25WyfDUbTpxQ== vscode-nsfw@1.2.8: version "1.2.8" diff --git a/scripts/code.bat b/scripts/code.bat index 0eb0eb0b342..770d37b7aec 100644 --- a/scripts/code.bat +++ b/scripts/code.bat @@ -13,9 +13,8 @@ set NAMESHORT=%NAMESHORT: "=% set NAMESHORT=%NAMESHORT:"=%.exe set CODE=".build\electron\%NAMESHORT%" -:: Download Electron if needed -node build\lib\electron.js -if %errorlevel% neq 0 node .\node_modules\gulp\bin\gulp.js electron +:: Get electron +call yarn electron :: Manage built-in extensions if "%1"=="--builtin" goto builtin diff --git a/src/typings/sudo-prompt.d.ts b/src/typings/sudo-prompt.d.ts deleted file mode 100644 index 85f9783d0a6..00000000000 --- a/src/typings/sudo-prompt.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'sudo-prompt' { - - export function exec(cmd: string, options: { name?: string, icns?: string }, callback: (error: string, stdout: string, stderr: string) => void): void; -} \ No newline at end of file diff --git a/src/typings/vscode-minimist.d.ts b/src/typings/vscode-minimist.d.ts deleted file mode 100644 index 17558a1a738..00000000000 --- a/src/typings/vscode-minimist.d.ts +++ /dev/null @@ -1,92 +0,0 @@ -// Type definitions for minimist 1.2.0 -// Project: https://github.com/substack/minimist -// Definitions by: Bart van der Schoor , Necroskillz , kamranayub -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -/** - * Return an argument object populated with the array arguments from args - * - * @param args An optional argument array (typically `process.argv.slice(2)`) - * @param opts An optional options object to customize the parsing - */ -declare function minimist(args?: string[], opts?: minimist.Opts): minimist.ParsedArgs; - -/** - * Return an argument object populated with the array arguments from args. Strongly-typed - * to be the intersect of type T with minimist.ParsedArgs. - * - * @type T The type that will be intersected with minimist.ParsedArgs to represent the argument object - * @param args An optional argument array (typically `process.argv.slice(2)`) - * @param opts An optional options object to customize the parsing - */ -declare function minimist(args?: string[], opts?: minimist.Opts): T & minimist.ParsedArgs; - -/** - * Return an argument object populated with the array arguments from args. Strongly-typed - * to be the the type T which should extend minimist.ParsedArgs - * - * @type T The type that extends minimist.ParsedArgs and represents the argument object - * @param args An optional argument array (typically `process.argv.slice(2)`) - * @param opts An optional options object to customize the parsing - */ -declare function minimist(args?: string[], opts?: minimist.Opts): T; - -declare namespace minimist { - export interface Opts { - /** - * A string or array of strings argument names to always treat as strings - */ - string?: string | string[]; - - /** - * A boolean, string or array of strings to always treat as booleans. If true will treat - * all double hyphenated arguments without equals signs as boolean (e.g. affects `--foo`, not `-f` or `--foo=bar`) - */ - boolean?: boolean | string | string[]; - - /** - * An object mapping string names to strings or arrays of string argument names to use as aliases - */ - alias?: { [key: string]: string | string[] }; - - /** - * An object mapping string argument names to default values - */ - default?: { [key: string]: any }; - - /** - * When true, populate argv._ with everything after the first non-option - */ - stopEarly?: boolean; - - /** - * A function which is invoked with a command line parameter not defined in the opts - * configuration object. If the function returns false, the unknown option is not added to argv - */ - unknown?: (arg: string) => boolean; - - /** - * When true, populate argv._ with everything before the -- and argv['--'] with everything after the --. - * Note that with -- set, parsing for arguments still stops after the `--`. - */ - '--'?: boolean; - } - - export interface ParsedArgs { - [arg: string]: any; - - /** - * If opts['--'] is true, populated with everything after the -- - */ - '--'?: string[]; - - /** - * Contains all the arguments that didn't have an option associated with them - */ - _: string[]; - } -} - -declare module "vscode-minimist" { - export = minimist; -} diff --git a/src/vs/base/browser/canIUse.ts b/src/vs/base/browser/canIUse.ts index 061d1bfc74f..989b11c370f 100644 --- a/src/vs/base/browser/canIUse.ts +++ b/src/vs/base/browser/canIUse.ts @@ -55,5 +55,6 @@ export const BrowserFeatures = { return KeyboardSupport.None; })(), - touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0 + touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0, + pointerEvents: browser.isSafari && ('ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0) }; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 4470da3bcff..2757ec77f86 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -281,6 +281,21 @@ export function addDisposableNonBubblingMouseOutListener(node: Element, handler: }); } +export function addDisposableNonBubblingPointerOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable { + return addDisposableListener(node, 'pointerout', (e: MouseEvent) => { + // Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements + let toElement: Node | null = (e.relatedTarget || e.target); + while (toElement && toElement !== node) { + toElement = toElement.parentNode; + } + if (toElement === node) { + return; + } + + handler(e); + }); +} + interface IRequestAnimationFrame { (callback: (time: number) => void): number; } @@ -857,6 +872,8 @@ export const EventType = { MOUSE_OUT: 'mouseout', MOUSE_ENTER: 'mouseenter', MOUSE_LEAVE: 'mouseleave', + POINTER_UP: 'pointerup', + POINTER_DOWN: 'pointerdown', CONTEXT_MENU: 'contextmenu', WHEEL: 'wheel', // Keyboard diff --git a/src/vs/base/browser/globalMouseMoveMonitor.ts b/src/vs/base/browser/globalMouseMoveMonitor.ts index 109f6f72260..c15a4a4d60f 100644 --- a/src/vs/base/browser/globalMouseMoveMonitor.ts +++ b/src/vs/base/browser/globalMouseMoveMonitor.ts @@ -38,10 +38,10 @@ export function standardMouseMoveMerger(lastEvent: IStandardMouseMoveEventData, export class GlobalMouseMoveMonitor implements IDisposable { - private readonly hooks = new DisposableStore(); - private mouseMoveEventMerger: IEventMerger | null = null; - private mouseMoveCallback: IMouseMoveCallback | null = null; - private onStopCallback: IOnStopCallback | null = null; + protected readonly hooks = new DisposableStore(); + protected mouseMoveEventMerger: IEventMerger | null = null; + protected mouseMoveCallback: IMouseMoveCallback | null = null; + protected onStopCallback: IOnStopCallback | null = null; public dispose(): void { this.stopMonitoring(false); @@ -116,3 +116,32 @@ export class GlobalMouseMoveMonitor implements IDisposable { } } } + +export class GlobalPointerMoveMonitor extends GlobalMouseMoveMonitor { + public startMonitoring( + mouseMoveEventMerger: IEventMerger, + mouseMoveCallback: IMouseMoveCallback, + onStopCallback: IOnStopCallback + ): void { + if (this.isMonitoring()) { + // I am already hooked + return; + } + this.mouseMoveEventMerger = mouseMoveEventMerger; + this.mouseMoveCallback = mouseMoveCallback; + this.onStopCallback = onStopCallback; + + let windowChain = IframeUtils.getSameOriginWindowChain(); + for (const element of windowChain) { + this.hooks.add(dom.addDisposableThrottledListener(element.window.document, 'pointermove', + (data: R) => { + this.mouseMoveCallback!(data); + }, + (lastEvent: R, currentEvent) => this.mouseMoveEventMerger!(lastEvent, currentEvent as MouseEvent) + )); + this.hooks.add(dom.addDisposableListener(element.window.document, 'pointerup', (e: MouseEvent) => this.stopMonitoring(true))); + } + + // Currently we didn't test pointer events in iframe yet. + } +} diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index 753d7079299..beb97dd70d5 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?3443fb4013ab5a04c23a30f912dd6627") format("truetype"); + src: url("./codicon.ttf?72bd9e6bbf1e48287bcb9a9e4babeb28") format("truetype"); } .codicon[class*='codicon-'] { @@ -385,4 +385,5 @@ .codicon-list-filter:before { content: "\eb83" } .codicon-list-flat:before { content: "\eb84" } .codicon-list-selection:before { content: "\eb85" } +.codicon-selection:before { content: "\eb85" } .codicon-list-tree:before { content: "\eb86" } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf index ba3474205da..d39e8ad92fd 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/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 098e8b03182..54c9954259b 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -7,6 +7,7 @@ import 'vs/css!./contextview'; import * as DOM from 'vs/base/browser/dom'; import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Range } from 'vs/base/common/range'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; export interface IAnchor { x: number; @@ -178,7 +179,7 @@ export class ContextView extends Disposable { return; } - if (this.delegate!.canRelayout === false) { + if (this.delegate!.canRelayout === false && !BrowserFeatures.pointerEvents) { this.hide(); return; } diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 701d3a6a785..8980c3e5b0d 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -84,7 +84,11 @@ export class HighlightedLabel { } this.domNode.innerHTML = htmlContent; - this.domNode.title = this.title; + if (this.title) { + this.domNode.title = this.title; + } else { + this.domNode.removeAttribute('title'); + } this.didEverRender = true; } diff --git a/src/vs/base/common/color.ts b/src/vs/base/common/color.ts index 8068e76aadd..58397cfbcae 100644 --- a/src/vs/base/common/color.ts +++ b/src/vs/base/common/color.ts @@ -399,6 +399,23 @@ export class Color { return new Color(new RGBA(r, g, b, a)); } + makeOpaque(opaqueBackground: Color): Color { + if (this.isOpaque() || opaqueBackground.rgba.a !== 1) { + // only allow to blend onto a non-opaque color onto a opaque color + return this; + } + + const { r, g, b, a } = this.rgba; + + // https://stackoverflow.com/questions/12228548/finding-equivalent-color-with-opacity + return new Color(new RGBA( + opaqueBackground.rgba.r - a * (opaqueBackground.rgba.r - r), + opaqueBackground.rgba.g - a * (opaqueBackground.rgba.g - g), + opaqueBackground.rgba.b - a * (opaqueBackground.rgba.b - b), + 1 + )); + } + flatten(...backgrounds: Color[]): Color { const background = backgrounds.reduceRight((accumulator, color) => { return Color._flatten(color, accumulator); diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index f6a8959a04c..19f89eec626 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -267,10 +267,10 @@ export class URI implements UriComponents { } return new _URI( match[2] || _empty, - decodeURIComponent(match[4] || _empty), - decodeURIComponent(match[5] || _empty), - decodeURIComponent(match[7] || _empty), - decodeURIComponent(match[9] || _empty), + percentDecode(match[4] || _empty), + percentDecode(match[5] || _empty), + percentDecode(match[7] || _empty), + percentDecode(match[9] || _empty), _strict ); } @@ -648,3 +648,26 @@ function _asFormatted(uri: URI, skipEncoding: boolean): string { } return res; } + +// --- decode + +function decodeURIComponentGraceful(str: string): string { + try { + return decodeURIComponent(str); + } catch { + if (str.length > 3) { + return str.substr(0, 3) + decodeURIComponentGraceful(str.substr(3)); + } else { + return str; + } + } +} + +const _rEncodedAsHex = /(%[0-9A-Za-z][0-9A-Za-z])+/g; + +function percentDecode(str: string): string { + if (!str.match(_rEncodedAsHex)) { + return str; + } + return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match)); +} diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index 3255575ae57..6b35d6afc9c 100644 --- a/src/vs/base/test/common/uri.test.ts +++ b/src/vs/base/test/common/uri.test.ts @@ -439,6 +439,10 @@ suite('URI', () => { assert.equal(uri.path, uri2.path); }); + test('Unable to open \'%A0.txt\': URI malformed #76506', function () { + assert.equal(URI.parse('file://some/%.txt'), 'file://some/%25.txt'); + assert.equal(URI.parse('file://some/%A0.txt'), 'file://some/%25A0.txt'); + }); test('Links in markdown are broken if url contains encoded parameters #79474', function () { this.skip(); diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index d1a19841028..8c8bb743462 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -26,7 +26,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; /** * Merges mouse events when mouse move events are throttled */ -function createMouseMoveEventMerger(mouseTargetFactory: MouseTargetFactory | null) { +export function createMouseMoveEventMerger(mouseTargetFactory: MouseTargetFactory | null) { return function (lastEvent: EditorMouseEvent, currentEvent: EditorMouseEvent): EditorMouseEvent { let targetIsWidget = false; if (mouseTargetFactory) { @@ -71,8 +71,7 @@ export class MouseHandler extends ViewEventHandler { protected viewHelper: IPointerHandlerHelper; protected mouseTargetFactory: MouseTargetFactory; private readonly _asyncFocus: RunOnceScheduler; - - private readonly _mouseDownOperation: MouseDownOperation; + protected readonly _mouseDownOperation: MouseDownOperation; private lastMouseLeaveTime: number; constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) { @@ -179,7 +178,7 @@ export class MouseHandler extends ViewEventHandler { }); } - private _onMouseMove(e: EditorMouseEvent): void { + public _onMouseMove(e: EditorMouseEvent): void { if (this._mouseDownOperation.isActive()) { // In selection/drag operation return; @@ -196,7 +195,7 @@ export class MouseHandler extends ViewEventHandler { }); } - private _onMouseLeave(e: EditorMouseEvent): void { + public _onMouseLeave(e: EditorMouseEvent): void { this.lastMouseLeaveTime = (new Date()).getTime(); this.viewController.emitMouseLeave({ event: e, diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index 51988d09968..4c7ad07a759 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -6,11 +6,12 @@ import * as dom from 'vs/base/browser/dom'; import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { IPointerHandlerHelper, MouseHandler } from 'vs/editor/browser/controller/mouseHandler'; +import { IPointerHandlerHelper, MouseHandler, createMouseMoveEventMerger } from 'vs/editor/browser/controller/mouseHandler'; import { IMouseTarget } from 'vs/editor/browser/editorBrowser'; -import { EditorMouseEvent } from 'vs/editor/browser/editorDom'; +import { EditorMouseEvent, EditorPointerEventFactory } from 'vs/editor/browser/editorDom'; import { ViewController } from 'vs/editor/browser/view/viewController'; import { ViewContext } from 'vs/editor/common/view/viewContext'; +import { isSafari } from 'vs/base/browser/browser'; interface IThrottledGestureEvent { translationX: number; @@ -185,6 +186,67 @@ class StandardPointerHandler extends MouseHandler implements IDisposable { } } +/** + * Currently only tested on iOS 13/ iPadOS. + */ +export class PointerEventHandler extends MouseHandler { + private _lastPointerType: string; + constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) { + super(context, viewController, viewHelper); + + this._register(Gesture.addTarget(this.viewHelper.linesContentDomNode)); + this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Tap, (e) => this.onTap(e))); + this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Change, (e) => this.onChange(e))); + this._register(dom.addDisposableListener(this.viewHelper.linesContentDomNode, EventType.Contextmenu, (e: MouseEvent) => this._onContextMenu(new EditorMouseEvent(e, this.viewHelper.viewDomNode), false))); + + this._lastPointerType = 'mouse'; + + this.viewHelper.linesContentDomNode.addEventListener('pointerdown', (e: any) => { + const pointerType = e.pointerType; + if (pointerType === 'mouse') { + this._lastPointerType = 'mouse'; + return; + } else if (pointerType === 'touch') { + this._lastPointerType = 'touch'; + } else { + this._lastPointerType = 'pen'; + } + }); + + // PonterEvents + const pointerEvents = new EditorPointerEventFactory(this.viewHelper.viewDomNode); + + this._register(pointerEvents.onPointerMoveThrottled(this.viewHelper.viewDomNode, + (e) => this._onMouseMove(e), + createMouseMoveEventMerger(this.mouseTargetFactory), MouseHandler.MOUSE_MOVE_MINIMUM_TIME)); + this._register(pointerEvents.onPointerUp(this.viewHelper.viewDomNode, (e) => this._onMouseUp(e))); + this._register(pointerEvents.onPointerLeave(this.viewHelper.viewDomNode, (e) => this._onMouseLeave(e))); + this._register(pointerEvents.onPointerDown(this.viewHelper.viewDomNode, (e) => this._onMouseDown(e))); + } + + private onTap(event: GestureEvent): void { + event.preventDefault(); + this.viewHelper.focusTextArea(); + const target = this._createMouseTarget(new EditorMouseEvent(event, this.viewHelper.viewDomNode), false); + + if (target.position) { + this.viewController.moveTo(target.position); + } + } + + private onChange(e: GestureEvent): void { + if (this._lastPointerType === 'touch') { + this._context.viewLayout.deltaScrollNow(-e.translationX, -e.translationY); + } + } + + public _onMouseDown(e: EditorMouseEvent): void { + if (this._lastPointerType !== 'touch') { + super._onMouseDown(e); + } + } +} + class TouchHandler extends MouseHandler { constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) { @@ -221,6 +283,8 @@ export class PointerHandler extends Disposable { super(); if (window.navigator.msPointerEnabled) { this.handler = this._register(new MsPointerHandler(context, viewController, viewHelper)); + } else if (((window).PointerEvent && isSafari)) { + this.handler = this._register(new PointerEventHandler(context, viewController, viewHelper)); } else if ((window).TouchEvent) { this.handler = this._register(new TouchHandler(context, viewController, viewHelper)); } else if (window.navigator.pointerEnabled || (window).PointerEvent) { diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts index 92357091f08..f27c6b45e57 100644 --- a/src/vs/editor/browser/editorDom.ts +++ b/src/vs/editor/browser/editorDom.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { GlobalMouseMoveMonitor } from 'vs/base/browser/globalMouseMoveMonitor'; +import { GlobalMouseMoveMonitor, GlobalPointerMoveMonitor } from 'vs/base/browser/globalMouseMoveMonitor'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; /** * Coordinates relative to the whole document (e.g. mouse event's pageX and pageY) @@ -131,16 +132,58 @@ export class EditorMouseEventFactory { } } +export class EditorPointerEventFactory { + + private readonly _editorViewDomNode: HTMLElement; + + constructor(editorViewDomNode: HTMLElement) { + this._editorViewDomNode = editorViewDomNode; + } + + private _create(e: MouseEvent): EditorMouseEvent { + return new EditorMouseEvent(e, this._editorViewDomNode); + } + + public onPointerUp(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable { + return dom.addDisposableListener(target, 'pointerup', (e: MouseEvent) => { + callback(this._create(e)); + }); + } + + public onPointerDown(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable { + return dom.addDisposableListener(target, 'pointerdown', (e: MouseEvent) => { + callback(this._create(e)); + }); + } + + public onPointerLeave(target: HTMLElement, callback: (e: EditorMouseEvent) => void): IDisposable { + return dom.addDisposableNonBubblingPointerOutListener(target, (e: MouseEvent) => { + callback(this._create(e)); + }); + } + + public onPointerMoveThrottled(target: HTMLElement, callback: (e: EditorMouseEvent) => void, merger: EditorMouseEventMerger, minimumTimeMs: number): IDisposable { + const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent, currentEvent: MouseEvent): EditorMouseEvent => { + return merger(lastEvent, this._create(currentEvent)); + }; + return dom.addDisposableThrottledListener(target, 'pointermove', callback, myMerger, minimumTimeMs); + } +} + export class GlobalEditorMouseMoveMonitor extends Disposable { private readonly _editorViewDomNode: HTMLElement; - private readonly _globalMouseMoveMonitor: GlobalMouseMoveMonitor; + protected readonly _globalMouseMoveMonitor: GlobalMouseMoveMonitor; private _keydownListener: IDisposable | null; constructor(editorViewDomNode: HTMLElement) { super(); this._editorViewDomNode = editorViewDomNode; - this._globalMouseMoveMonitor = this._register(new GlobalMouseMoveMonitor()); + this._globalMouseMoveMonitor = this._register( + BrowserFeatures.pointerEvents + ? new GlobalPointerMoveMonitor() + : new GlobalMouseMoveMonitor() + ); this._keydownListener = null; } diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 8f2d7c90482..1b81608b013 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -16,7 +16,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -303,7 +303,7 @@ export function registerInstantiatedEditorAction(editorAction: EditorAction): vo EditorContributionRegistry.INSTANCE.registerEditorAction(editorAction); } -export function registerEditorContribution(id: string, ctor: IEditorContributionCtor): void { +export function registerEditorContribution(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }): void { EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor); } @@ -355,7 +355,7 @@ class EditorContributionRegistry { this.editorCommands = Object.create(null); } - public registerEditorContribution(id: string, ctor: IEditorContributionCtor): void { + public registerEditorContribution(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }): void { this.editorContributions.push({ id, ctor }); } diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index e61b9acafb1..b50d4508fe3 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -222,8 +222,10 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { } private _disposeAllLenses(decChangeAccessor: IModelDecorationsChangeAccessor | undefined, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor | undefined): void { - let helper = new CodeLensHelper(); - this._lenses.forEach((lens) => lens.dispose(helper, viewZoneChangeAccessor)); + const helper = new CodeLensHelper(); + for (const lens of this._lenses) { + lens.dispose(helper, viewZoneChangeAccessor); + } if (decChangeAccessor) { helper.commit(decChangeAccessor); } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index c048a6f1541..546cdfd028e 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -5,7 +5,6 @@ import 'vs/css!./codelensWidget'; import * as dom from 'vs/base/browser/dom'; -import { coalesce, isFalsyOrEmpty } from 'vs/base/common/arrays'; import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { Range } from 'vs/editor/common/core/range'; @@ -66,7 +65,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { constructor( editor: editorBrowser.ICodeEditor, symbolRange: Range, - data: CodeLensItem[] + lenses: Array ) { this._id = 'codeLensWidget' + (++CodeLensContentWidget._idPool); this._editor = editor; @@ -74,10 +73,9 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { this.setSymbolRange(symbolRange); this._domNode = document.createElement('span'); - this._domNode.innerHTML = ' '; - dom.addClass(this._domNode, 'codelens-decoration'); + this._domNode.className = 'codelens-decoration'; this.updateHeight(); - this.withCommands(data.map(data => data.symbol), false); + this.withCommands(lenses, false); } updateHeight(): void { @@ -88,39 +86,45 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { this._domNode.style.lineHeight = `${lineHeight}px`; this._domNode.style.fontSize = `${Math.round(fontInfo.fontSize * 0.9)}px`; this._domNode.style.paddingRight = `${Math.round(fontInfo.fontSize * 0.45)}px`; - this._domNode.innerHTML = ' '; } - withCommands(inSymbols: Array, animate: boolean): void { + withCommands(lenses: Array, animate: boolean): void { this._commands.clear(); - const symbols = coalesce(inSymbols); - if (isFalsyOrEmpty(symbols)) { - this._domNode.innerHTML = 'no commands'; - return; - } - - let html: string[] = []; - for (let i = 0; i < symbols.length; i++) { - const command = symbols[i].command; - if (command) { - const title = renderCodicons(command.title); - let part: string; - if (command.id) { - part = `${title}`; - this._commands.set(String(i), command); + let innerHtml = ''; + let hasSymbol = false; + for (let i = 0; i < lenses.length; i++) { + const lens = lenses[i]; + if (!lens) { + continue; + } + hasSymbol = true; + if (lens.command) { + const title = renderCodicons(lens.command.title); + if (lens.command.id) { + innerHtml += `${title}`; + this._commands.set(String(i), lens.command); } else { - part = `${title}`; + innerHtml += `${title}`; + } + if (i + 1 < lenses.length) { + innerHtml += ' | '; } - html.push(part); } } - const wasEmpty = this._domNode.innerHTML === '' || this._domNode.innerHTML === ' '; - this._domNode.innerHTML = html.join(' | '); - this._editor.layoutContentWidget(this); - if (wasEmpty && animate) { - dom.addClass(this._domNode, 'fadein'); + if (!hasSymbol) { + // symbols but no commands + this._domNode.innerHTML = 'no commands'; + + } else { + // symbols and commands + const wasEmpty = this._domNode.innerHTML === '' || this._domNode.innerHTML === ' '; + this._domNode.innerHTML = innerHtml || ' '; + this._editor.layoutContentWidget(this); + if (wasEmpty && animate) { + dom.addClass(this._domNode, 'fadein'); + } } } @@ -213,8 +217,11 @@ export class CodeLensWidget { this._decorationIds = new Array(this._data.length); let range: Range | undefined; + let lenses: CodeLens[] = []; this._data.forEach((codeLensData, i) => { + lenses.push(codeLensData.symbol); + helper.addDecoration({ range: codeLensData.symbol.range, options: ModelDecorationOptions.EMPTY @@ -229,7 +236,7 @@ export class CodeLensWidget { }); if (range) { - this._contentWidget = new CodeLensContentWidget(editor, range, this._data); + this._contentWidget = new CodeLensContentWidget(editor, range, lenses); this._viewZone = new CodeLensViewZone(range.startLineNumber - 1, updateCallback); this._viewZoneId = viewZoneChangeAccessor.addZone(this._viewZone); diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts index 4a74a4a5e0d..106c2a357e2 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts @@ -6,7 +6,7 @@ import 'vs/css!./colorPicker'; import { onDidChangeZoomLevel } from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; -import { GlobalMouseMoveMonitor, IStandardMouseMoveEventData, standardMouseMoveMerger } from 'vs/base/browser/globalMouseMoveMonitor'; +import { GlobalMouseMoveMonitor, IStandardMouseMoveEventData, standardMouseMoveMerger, GlobalPointerMoveMonitor } from 'vs/base/browser/globalMouseMoveMonitor'; import { Widget } from 'vs/base/browser/ui/widget'; import { Color, HSVA, RGBA } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; @@ -14,6 +14,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel'; import { editorHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; const $ = dom.$; @@ -150,13 +151,17 @@ class SaturationBox extends Disposable { this.layout(); - this._register(dom.addDisposableListener(this.domNode, dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e))); + this._register(dom.addDisposableListener(this.domNode, BrowserFeatures.pointerEvents ? dom.EventType.POINTER_DOWN : dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e))); this._register(this.model.onDidChangeColor(this.onDidChangeColor, this)); this.monitor = null; } private onMouseDown(e: MouseEvent): void { - this.monitor = this._register(new GlobalMouseMoveMonitor()); + this.monitor = this._register( + BrowserFeatures.pointerEvents + ? new GlobalPointerMoveMonitor() + : new GlobalMouseMoveMonitor() + ); const origin = dom.getDomNodePagePosition(this.domNode); if (e.target !== this.selection) { @@ -165,7 +170,7 @@ class SaturationBox extends Disposable { this.monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangePosition(event.posx - origin.left, event.posy - origin.top), () => null); - const mouseUpListener = dom.addDisposableListener(document, dom.EventType.MOUSE_UP, () => { + const mouseUpListener = dom.addDisposableListener(document, BrowserFeatures.pointerEvents ? dom.EventType.POINTER_UP : dom.EventType.MOUSE_UP, () => { this._onColorFlushed.fire(); mouseUpListener.dispose(); if (this.monitor) { @@ -250,7 +255,7 @@ abstract class Strip extends Disposable { this.slider = dom.append(this.domNode, $('.slider')); this.slider.style.top = `0px`; - this._register(dom.addDisposableListener(this.domNode, dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e))); + this._register(dom.addDisposableListener(this.domNode, BrowserFeatures.pointerEvents ? dom.EventType.POINTER_DOWN : dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e))); this.layout(); } @@ -262,7 +267,11 @@ abstract class Strip extends Disposable { } private onMouseDown(e: MouseEvent): void { - const monitor = this._register(new GlobalMouseMoveMonitor()); + const monitor = this._register( + BrowserFeatures.pointerEvents + ? new GlobalPointerMoveMonitor() + : new GlobalMouseMoveMonitor() + ); const origin = dom.getDomNodePagePosition(this.domNode); dom.addClass(this.domNode, 'grabbing'); @@ -272,7 +281,7 @@ abstract class Strip extends Disposable { monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangeTop(event.posy - origin.top), () => null); - const mouseUpListener = dom.addDisposableListener(document, dom.EventType.MOUSE_UP, () => { + const mouseUpListener = dom.addDisposableListener(document, BrowserFeatures.pointerEvents ? dom.EventType.POINTER_UP : dom.EventType.MOUSE_UP, () => { this._onColorFlushed.fire(); mouseUpListener.dispose(); monitor.stopMonitoring(true); diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index 82f86384de4..9d7b3fa0b19 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -227,6 +227,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget { this.setModel(undefined); this._callOnDispose.dispose(); this._disposeOnNewModel.dispose(); + this._preview.setModel(null); // drop all view-zones, workaround for https://github.com/microsoft/vscode/issues/84726 dispose(this._preview); dispose(this._previewNotAvailableMessage); dispose(this._tree); diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.css b/src/vs/editor/contrib/parameterHints/parameterHints.css index dff7f77f9e4..e77cb357003 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/parameterHints.css @@ -13,12 +13,12 @@ .monaco-editor .parameter-hints-widget > .wrapper { max-width: 440px; display: flex; - flex-direction: column; + flex-direction: row; } .monaco-editor .parameter-hints-widget.multiple { min-height: 3.3em; - padding: 0 0 0 1.9em; + padding: 0; } .monaco-editor .parameter-hints-widget.visible { @@ -62,20 +62,19 @@ padding: 0 0.4em; } -.monaco-editor .parameter-hints-widget .buttons { - position: absolute; +.monaco-editor .parameter-hints-widget .controls { display: none; - bottom: 0; - left: 0; + flex-direction: column; + align-items: center; + min-width: 22px; + justify-content: flex-end; } -.monaco-editor .parameter-hints-widget.multiple .buttons { - display: block; +.monaco-editor .parameter-hints-widget.multiple .controls { + display: flex; } .monaco-editor .parameter-hints-widget.multiple .button { - position: absolute; - left: 2px; width: 16px; height: 16px; background-repeat: no-repeat; @@ -88,26 +87,16 @@ } .monaco-editor .parameter-hints-widget .button.next { - bottom: 0; background-image: url('arrow-down.svg'); } .monaco-editor .parameter-hints-widget .overloads { - position: absolute; - display: none; text-align: center; - bottom: 14px; - left: 0; - width: 22px; height: 12px; line-height: 12px; opacity: 0.5; } -.monaco-editor .parameter-hints-widget.multiple .overloads { - display: block; -} - .monaco-editor .parameter-hints-widget .signature .parameter.active { font-weight: bold; text-decoration: underline; diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index 2ac6ede62fe..2120dc7aac8 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -22,6 +22,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorHoverBackground, editorHoverBorder, textCodeBlockBackground, textLinkForeground, editorHoverForeground } from 'vs/platform/theme/common/colorRegistry'; import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ParameterHintsModel, TriggerContext } from 'vs/editor/contrib/parameterHints/parameterHintsModel'; +import { pad } from 'vs/base/common/strings'; const $ = dom.$; @@ -76,9 +77,10 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { const wrapper = dom.append(element, $('.wrapper')); wrapper.tabIndex = -1; - const buttons = dom.append(wrapper, $('.buttons')); - const previous = dom.append(buttons, $('.button.previous')); - const next = dom.append(buttons, $('.button.next')); + const controls = dom.append(wrapper, $('.controls')); + const previous = dom.append(controls, $('.button.previous')); + const overloads = dom.append(controls, $('.overloads')); + const next = dom.append(controls, $('.button.next')); const onPreviousClick = stop(domEvent(previous, 'click')); this._register(onPreviousClick(this.previous, this)); @@ -86,8 +88,6 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { const onNextClick = stop(domEvent(next, 'click')); this._register(onNextClick(this.next, this)); - const overloads = dom.append(wrapper, $('.overloads')); - const body = $('.body'); const scrollbar = new DomScrollableElement(body, {}); this._register(scrollbar); @@ -239,12 +239,8 @@ export class ParameterHintsWidget extends Disposable implements IContentWidget { dom.toggleClass(this.domNodes.signature, 'has-docs', hasDocs); dom.toggleClass(this.domNodes.docs, 'empty', !hasDocs); - let currentOverload = String(hints.activeSignature + 1); - if (hints.signatures.length < 10) { - currentOverload += `/${hints.signatures.length}`; - } - - this.domNodes.overloads.textContent = currentOverload; + this.domNodes.overloads.textContent = + pad(hints.activeSignature + 1, hints.signatures.length.toString().length) + '/' + hints.signatures.length; if (activeParameter) { const labelToAnnounce = this.getParameterLabel(signature, hints.activeParameter); diff --git a/src/vs/editor/contrib/peekView/media/peekViewWidget.css b/src/vs/editor/contrib/peekView/media/peekViewWidget.css index 889d1148298..d0e9bc3c552 100644 --- a/src/vs/editor/contrib/peekView/media/peekViewWidget.css +++ b/src/vs/editor/contrib/peekView/media/peekViewWidget.css @@ -21,7 +21,11 @@ margin-left: 0.5em; } -.monaco-editor .peekview-widget .head .peekview-title .meta::before { +.monaco-editor .peekview-widget .head .peekview-title .meta { + white-space: nowrap; +} + +.monaco-editor .peekview-widget .head .peekview-title .meta:not(:empty)::before { content: '-'; padding: 0 0.3em; } diff --git a/src/vs/platform/auth/common/auth.ts b/src/vs/platform/auth/common/auth.ts index 1f4e643c62e..d2e026fedd3 100644 --- a/src/vs/platform/auth/common/auth.ts +++ b/src/vs/platform/auth/common/auth.ts @@ -9,7 +9,8 @@ import { URI } from 'vs/base/common/uri'; export const enum AuthTokenStatus { Inactive = 'Inactive', - Active = 'Active' + Active = 'Active', + SigningIn = 'SigningIn' } export const IAuthTokenService = createDecorator('IAuthTokenService'); diff --git a/src/vs/platform/auth/electron-browser/authTokenService.ts b/src/vs/platform/auth/electron-browser/authTokenService.ts index 00a35957be5..95e606b68de 100644 --- a/src/vs/platform/auth/electron-browser/authTokenService.ts +++ b/src/vs/platform/auth/electron-browser/authTokenService.ts @@ -71,6 +71,7 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { } public async login(callbackUri: URI): Promise { + this.setStatus(AuthTokenStatus.SigningIn); const nonce = generateUuid(); const port = (callbackUri.authority.match(/:([0-9]*)$/) || [])[1] || (callbackUri.scheme === 'https' || callbackUri.scheme === 'http' ? 443 : 80); const state = `${callbackUri.scheme},${port},${encodeURIComponent(nonce)},${encodeURIComponent(callbackUri.query)}`; @@ -88,6 +89,7 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { const timeoutPromise = new Promise((resolve: (value: IToken) => void, reject) => { const wait = setTimeout(() => { + this.setStatus(AuthTokenStatus.Inactive); clearTimeout(wait); reject('Login timed out.'); }, 1000 * 60 * 5); diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 0258fa407dd..64f783471ba 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -239,12 +239,23 @@ export interface IFileDialogService { */ showSaveDialog(options: ISaveDialogOptions): Promise; + /** + * Shows a confirm dialog for saving 1-N files. + */ + showSaveConfirm(fileNameOrResources: string | URI[]): Promise; + /** * Shows a open file dialog and returns the chosen file URI. */ showOpenDialog(options: IOpenDialogOptions): Promise; } +export const enum ConfirmResult { + SAVE, + DONT_SAVE, + CANCEL +} + const MAX_CONFIRM_FILES = 10; export function getConfirmMessage(start: string, resourcesToConfirm: readonly URI[]): string { const message = [start]; diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index c5aac80a5c5..945da642232 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -85,6 +85,7 @@ export interface ParsedArgs { 'js-flags'?: string; 'disable-gpu'?: boolean; 'nolazy'?: boolean; + 'force-device-scale-factor'?: string; } export const IEnvironmentService = createDecorator('environmentService'); diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 8176898aa76..b97cc4b20b1 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -117,6 +117,7 @@ export const OPTIONS: OptionDescriptions> = { 'inspect': { type: 'string' }, 'inspect-brk': { type: 'string' }, 'nolazy': { type: 'boolean' }, // node inspect + 'force-device-scale-factor': { type: 'string' }, '_urls': { type: 'string[]' }, _: { type: 'string[]' } // main arguments diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index d3a70db859d..d914b9c5810 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -362,7 +362,7 @@ export class FileService extends Disposable implements IFileService { // mtime and etag, we bail out to prevent dirty writing. // // First, we check for a mtime that is in the future before we do more checks. The assumption is - // that only the mtime is an indicator for a file that has changd on disk. + // that only the mtime is an indicator for a file that has changed on disk. // // Second, if the mtime has advanced, we compare the size of the file on disk with our previous // one using the etag() function. Relying only on the mtime check has prooven to produce false diff --git a/src/vs/platform/instantiation/common/extensions.ts b/src/vs/platform/instantiation/common/extensions.ts index 5957b7924f5..938b18b2938 100644 --- a/src/vs/platform/instantiation/common/extensions.ts +++ b/src/vs/platform/instantiation/common/extensions.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { SyncDescriptor } from './descriptors'; -import { ServiceIdentifier, IConstructorSignature0 } from './instantiation'; +import { ServiceIdentifier, BrandedService } from './instantiation'; const _registry: [ServiceIdentifier, SyncDescriptor][] = []; -export function registerSingleton(id: ServiceIdentifier, ctor: IConstructorSignature0, supportsDelayedInstantiation?: boolean): void { +export function registerSingleton(id: ServiceIdentifier, ctor: { new(...services: Services): T }, supportsDelayedInstantiation?: boolean): void { _registry.push([id, new SyncDescriptor(ctor, [], supportsDelayedInstantiation)]); } diff --git a/src/vs/platform/instantiation/common/instantiation.ts b/src/vs/platform/instantiation/common/instantiation.ts index 926d3c4653f..10ff5a25051 100644 --- a/src/vs/platform/instantiation/common/instantiation.ts +++ b/src/vs/platform/instantiation/common/instantiation.ts @@ -22,7 +22,7 @@ export namespace _util { // --- interfaces ------ -type BrandedService = { _serviceBrand: undefined }; +export type BrandedService = { _serviceBrand: undefined }; export interface IConstructorSignature0 { new(...services: BrandedService[]): T; @@ -101,7 +101,8 @@ export interface IInstantiationService { createInstance(descriptor: descriptors.SyncDescriptor7, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7): T; createInstance(descriptor: descriptors.SyncDescriptor8, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8): T; - createInstance any, R extends InstanceType>(t: Ctor, ...args: GetLeadingNonServiceArgs>): R; + createInstance any, R extends InstanceType>(t: Ctor, ...args: GetLeadingNonServiceArgs>): R; + createInstance any, R extends InstanceType>(t: Ctor): R; /** * diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index d0b62c048bd..fdf7f2e890b 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -496,7 +496,8 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // Remember in recent document list (unless this opens for extension development) // Also do not add paths when files are opened for diffing, only if opened individually - if (!usedWindows.some(window => window.isExtensionDevelopmentHost) && !(fileInputs?.filesToDiff) && !openConfig.noRecentEntry) { + const isDiff = fileInputs && fileInputs.filesToDiff.length > 0; + if (!usedWindows.some(window => window.isExtensionDevelopmentHost) && !isDiff && !openConfig.noRecentEntry) { const recents: IRecent[] = []; for (let pathToOpen of pathsToOpen) { if (pathToOpen.workspace) { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index e5cc9774725..9d00a3150b1 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5743,10 +5743,18 @@ declare module 'vscode' { ctime: number; /** * The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC. + * + * *Note:* If the file changed, it is important to provide an updated `mtime` that advanced + * from the previous value. Otherwise there may be optimizations in place that will not show + * the updated file contents in an editor for example. */ mtime: number; /** * The size in bytes. + * + * *Note:* If the file changed, it is important to provide an updated `size`. Otherwise there + * may be optimizations in place that will not show the updated file contents in an editor for + * example. */ size: number; } @@ -5860,6 +5868,11 @@ declare module 'vscode' { * An event to signal that a resource has been created, changed, or deleted. This * event should fire for resources that are being [watched](#FileSystemProvider.watch) * by clients of this provider. + * + * *Note:* It is important that the metadata of the file that changed provides an + * updated `mtime` that advanced from the previous value in the [stat](#FileStat) and a + * correct `size` value. Otherwise there may be optimizations in place that will not show + * the change in an editor for example. */ readonly onDidChangeFile: Event; diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index d139918f061..51728ea8fb4 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -524,7 +524,7 @@ declare module 'vscode' { //#region AndrĂ©: debug /** - * A DebugSource is an opaque stand-in type for the type [Source](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Source) defined in the Debug Adapter Protocol. + * A DebugSource is an opaque stand-in type for the [Source](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Source) type defined in the Debug Adapter Protocol. */ export interface DebugSource { // opaque contents @@ -541,7 +541,7 @@ declare module 'vscode' { * @param session An optional debug session that will be used to locate the Debug Adapter Protocol. * @return A uri that can be used to load the contents of the source. */ - function asDebugSourceUri(source: DebugSource, session?: DebugSession): Uri; + export function asDebugSourceUri(source: DebugSource, session?: DebugSession): Uri; } // deprecated diff --git a/src/vs/workbench/api/common/extHostCustomers.ts b/src/vs/workbench/api/common/extHostCustomers.ts index 1ce450c4c2a..298b2354972 100644 --- a/src/vs/workbench/api/common/extHostCustomers.ts +++ b/src/vs/workbench/api/common/extHostCustomers.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from 'vs/base/common/lifecycle'; -import { IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; +import { IConstructorSignature1, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; @@ -13,12 +13,12 @@ export type IExtHostNamedCustomer = [ProxyIdentifier, export type IExtHostCustomerCtor = IConstructorSignature1; export function extHostNamedCustomer(id: ProxyIdentifier) { - return function (ctor: IExtHostCustomerCtor): void { + return function (ctor: { new(context: IExtHostContext, ...services: Services): T }): void { ExtHostCustomersRegistryImpl.INSTANCE.registerNamedCustomer(id, ctor); }; } -export function extHostCustomer(ctor: IExtHostCustomerCtor): void { +export function extHostCustomer(ctor: { new(context: IExtHostContext, ...services: Services): T }): void { ExtHostCustomersRegistryImpl.INSTANCE.registerCustomer(ctor); } diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 82d87ead799..bffcbcddd2b 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -155,10 +155,10 @@ export class ExtHostDebugService implements IExtHostDebugService, ExtHostDebugSe debug += `${sep}ref=${source.sourceReference}`; - return vscode.Uri.parse(debug); + return URI.parse(debug); } else if (source.path) { // src is just a local file path - return vscode.Uri.file(source.path); + return URI.file(source.path); } else { throw new Error(`cannot create uri from DAP 'source' object; properties 'path' and 'sourceReference' are both missing.`); } diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts index 2cf27b60ae6..eaa70be455d 100644 --- a/src/vs/workbench/browser/actions.ts +++ b/src/vs/workbench/browser/actions.ts @@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IAction } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; -import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; /** * The action bar contributor allows to add actions to an actionbar in a given context. @@ -134,7 +134,7 @@ export interface IActionBarRegistry { * Registers an Actionbar contributor. It will be called to contribute actions to all the action bars * that are used in the Workbench in the given scope. */ - registerActionBarContributor(scope: string, ctor: IConstructorSignature0): void; + registerActionBarContributor(scope: string, ctor: { new(...services: Services): ActionBarContributor }): void; /** * Returns an array of registered action bar contributors known to the workbench for the given scope. diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index fa66a471034..d6c63962f8e 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -367,7 +367,7 @@ class ResourceLabelWidget extends IconLabel { setEditor(editor: IEditorInput, options?: IResourceLabelOptions): void { this.setResource({ resource: toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }), - name: withNullAsUndefined(editor.getName()), + name: editor.getName(), description: editor.getDescription(options ? options.descriptionVerbosity : undefined) }, options); } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 81fd79a40a4..70b956eb862 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -95,6 +95,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private readonly _onCenteredLayoutChange: Emitter = this._register(new Emitter()); readonly onCenteredLayoutChange: Event = this._onCenteredLayoutChange.event; + private readonly _onMaximizeChange: Emitter = this._register(new Emitter()); + readonly onMaximizeChange: Event = this._onMaximizeChange.event; + private readonly _onPanelPositionChange: Emitter = this._register(new Emitter()); readonly onPanelPositionChange: Event = this._onPanelPositionChange.event; @@ -142,6 +145,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi protected readonly state = { fullscreen: false, + maximized: false, hasFocus: false, windowBorder: false, @@ -304,6 +308,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Propagate to grid this.workbenchGrid.setViewVisible(this.titleBarPartView, this.isVisible(Parts.TITLEBAR_PART)); + this.updateWindowBorder(true); + this.layout(); // handle title bar when fullscreen changes } @@ -399,7 +405,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const inactiveBorder = theme.getColor(WINDOW_INACTIVE_BORDER); let windowBorder = false; - if (activeBorder || inactiveBorder) { + if (!this.state.fullscreen && !this.state.maximized && (activeBorder || inactiveBorder)) { windowBorder = true; // If one color is missing, just fallback to the other one @@ -1250,6 +1256,21 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this._onPanelPositionChange.fire(positionToString(this.state.panel.position)); } + isWindowMaximized() { + return this.state.maximized; + } + + updateWindowMaximizedState(maximized: boolean) { + if (this.state.maximized === maximized) { + return; + } + + this.state.maximized = maximized; + + this.updateWindowBorder(); + this._onMaximizeChange.fire(maximized); + } + private createGridDescriptor(): ISerializedGrid { const workbenchDimensions = this.getClientArea(); const width = this.storageService.getNumber(Storage.GRID_WIDTH, StorageScope.GLOBAL, workbenchDimensions.width); diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index c0701a781eb..618b5eca239 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -61,9 +61,8 @@ body.web { width: 100vw; } -.monaco-workbench.border { - height: calc(100vh - 2px); - width: calc(100vw - 2px); +.monaco-workbench.border:not(.fullscreen) { + box-sizing: border-box; border: 1px solid var(--window-border-color); } diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index d5ee8de2887..1e07ec23451 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -46,7 +46,7 @@ export abstract class Part extends Component implements ISerializableView { private options: IPartOptions, themeService: IThemeService, storageService: IStorageService, - layoutService: IWorkbenchLayoutService + protected readonly layoutService: IWorkbenchLayoutService ) { super(id, themeService, storageService); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 5bc06258173..b4837c8d638 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -83,7 +83,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { constructor( @IViewletService private readonly viewletService: IViewletService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index 7b900b9d42b..66231d1ab2e 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -7,6 +7,21 @@ width: 48px; } +.monaco-workbench.windows .part.activitybar, +.monaco-workbench.linux .part.activitybar, +.monaco-workbench.web .part.activitybar { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); +} + .monaco-workbench .activitybar > .content { height: 100%; display: flex; diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index d196daa8517..017b60833b6 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -56,7 +56,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { this.callbacks = callbacks; } - getTitle() { + getTitle(): string { return this.input ? this.input.getName() : nls.localize('binaryEditor', "Binary Viewer"); } diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 178c61b28b6..0c2e0156455 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { mixin } from 'vs/base/common/objects'; -import { IEditorInput, EditorInput, IEditorIdentifier, ConfirmResult, IEditorCommandsContext, CloseDirection } from 'vs/workbench/common/editor'; +import { IEditorInput, EditorInput, IEditorIdentifier, IEditorCommandsContext, CloseDirection } from 'vs/workbench/common/editor'; import { QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { EditorQuickOpenEntry, EditorQuickOpenEntryGroup, IEditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; @@ -22,6 +22,8 @@ import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/ import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; export class ExecuteCommandAction extends Action { @@ -597,6 +599,8 @@ export abstract class BaseCloseAllAction extends Action { label: string, clazz: string | undefined, private textFileService: ITextFileService, + private workingCopyService: IWorkingCopyService, + private fileDialogService: IFileDialogService, protected editorGroupService: IEditorGroupsService ) { super(id, label, clazz); @@ -619,7 +623,7 @@ export abstract class BaseCloseAllAction extends Action { async run(): Promise { // Just close all if there are no dirty editors - if (!this.textFileService.isDirty()) { + if (!this.workingCopyService.hasDirty) { return this.doCloseAll(); } @@ -636,7 +640,7 @@ export abstract class BaseCloseAllAction extends Action { return undefined; })); - const confirm = await this.textFileService.confirmSave(); + const confirm = await this.fileDialogService.showSaveConfirm(this.workingCopyService.getDirty().map(copy => copy.resource)); if (confirm === ConfirmResult.CANCEL) { return; } @@ -667,9 +671,11 @@ export class CloseAllEditorsAction extends BaseCloseAllAction { id: string, label: string, @ITextFileService textFileService: ITextFileService, + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @IFileDialogService fileDialogService: IFileDialogService, @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(id, label, 'codicon-close-all', textFileService, editorGroupService); + super(id, label, 'codicon-close-all', textFileService, workingCopyService, fileDialogService, editorGroupService); } protected doCloseAll(): Promise { @@ -686,9 +692,11 @@ export class CloseAllEditorGroupsAction extends BaseCloseAllAction { id: string, label: string, @ITextFileService textFileService: ITextFileService, + @IWorkingCopyService workingCopyService: IWorkingCopyService, + @IFileDialogService fileDialogService: IFileDialogService, @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(id, label, undefined, textFileService, editorGroupService); + super(id, label, undefined, textFileService, workingCopyService, fileDialogService, editorGroupService); } protected async doCloseAll(): Promise { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 60c90b3c842..01eab7e6856 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editorgroupview'; import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { EditorInput, EditorOptions, GroupIdentifier, ConfirmResult, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, toResource, SideBySideEditor } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { addClass, addClasses, Dimension, trackFocus, toggleClass, removeClass, addDisposableListener, EventType, EventHelper, findParentWithClass, clearNode, isAncestor } from 'vs/base/browser/dom'; @@ -50,7 +50,8 @@ import { guessMimeTypes } from 'vs/base/common/mime'; import { extname } from 'vs/base/common/resources'; import { Schemas } from 'vs/base/common/network'; import { EditorActivation, EditorOpenContext } from 'vs/platform/editor/common/editor'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; +import { ILogService } from 'vs/platform/log/common/log'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -131,7 +132,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IMenuService private readonly menuService: IMenuService, - @IContextMenuService private readonly contextMenuService: IContextMenuService + @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @ILogService private readonly logService: ILogService ) { super(themeService); @@ -920,64 +923,74 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private async doHandleOpenEditorError(error: Error, editor: EditorInput, options?: EditorOptions): Promise { - // Report error only if this was not us restoring previous error state or - // we are told to ignore errors that occur from opening an editor - if (this.isRestored && !isPromiseCanceledError(error) && (!options || !options.ignoreError)) { + // Report error only if we are not told to ignore errors that occur from opening an editor + if (!isPromiseCanceledError(error) && (!options || !options.ignoreError)) { - // Extract possible error actions from the error - let errorActions: ReadonlyArray | undefined = undefined; - if (isErrorWithActions(error)) { - errorActions = (error as IErrorWithActions).actions; - } + // Since it is more likely that errors fail to open when restoring them e.g. + // because files got deleted or moved meanwhile, we do not show any notifications + // if we are still restoring editors. + if (this.isRestored) { - // If the context is USER, we try to show a modal dialog instead of a background notification - if (options?.context === EditorOpenContext.USER) { - const buttons: string[] = []; - if (Array.isArray(errorActions) && errorActions.length > 0) { - errorActions.forEach(action => buttons.push(action.label)); - } else { - buttons.push(localize('ok', 'OK')); + // Extract possible error actions from the error + let errorActions: ReadonlyArray | undefined = undefined; + if (isErrorWithActions(error)) { + errorActions = (error as IErrorWithActions).actions; } - let cancelId: number | undefined = undefined; - if (buttons.length === 1) { - buttons.push(localize('cancel', "Cancel")); - cancelId = 1; + // If the context is USER, we try to show a modal dialog instead of a background notification + if (options?.context === EditorOpenContext.USER) { + const buttons: string[] = []; + if (Array.isArray(errorActions) && errorActions.length > 0) { + errorActions.forEach(action => buttons.push(action.label)); + } else { + buttons.push(localize('ok', 'OK')); + } + + let cancelId: number | undefined = undefined; + if (buttons.length === 1) { + buttons.push(localize('cancel', "Cancel")); + cancelId = 1; + } + + const result = await this.dialogService.show( + Severity.Error, + localize('editorOpenErrorDialog', "Unable to open '{0}'", editor.getName()), + buttons, + { + detail: toErrorMessage(error), + cancelId + } + ); + + // Make sure to run any error action if present + if (result.choice !== cancelId && Array.isArray(errorActions)) { + const errorAction = errorActions[result.choice]; + if (errorAction) { + errorAction.run(); + } + } } - const result = await this.dialogService.show( - Severity.Error, - localize('editorOpenErrorDialog', "Unable to open '{0}'", editor.getName()), - buttons, - { - detail: toErrorMessage(error), - cancelId + // Otherwise, show a background notification. + else { + const actions: INotificationActions = { primary: [] }; + if (Array.isArray(errorActions)) { + actions.primary = errorActions; } - ); - // Make sure to run any error action if present - if (result.choice !== cancelId && Array.isArray(errorActions)) { - const errorAction = errorActions[result.choice]; - if (errorAction) { - errorAction.run(); - } + const handle = this.notificationService.notify({ + severity: Severity.Error, + message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)), + actions + }); + + Event.once(handle.onDidClose)(() => actions.primary && dispose(actions.primary)); } } - // Otherwise, show a background notification. + // Restoring: just log errors to console else { - const actions: INotificationActions = { primary: [] }; - if (Array.isArray(errorActions)) { - actions.primary = errorActions; - } - - const handle = this.notificationService.notify({ - severity: Severity.Error, - message: localize('editorOpenError', "Unable to open '{0}': {1}.", editor.getName(), toErrorMessage(error)), - actions - }); - - Event.once(handle.onDidClose)(() => actions.primary && dispose(actions.primary)); + this.logService.error(error); } } @@ -1260,7 +1273,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Switch to editor that we want to handle and confirm to save/revert await this.openEditor(editor); - const res = await editor.confirmSave(); + const editorResource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); + const res = await this.fileDialogService.showSaveConfirm(editorResource ? [editorResource] : editor.getName()); // It could be that the editor saved meanwhile, so we check again // to see if anything needs to happen before closing for good. diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 83b1c0cabe3..7cb0562675b 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -139,7 +139,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro @IThemeService themeService: IThemeService, @IConfigurationService private readonly configurationService: IConfigurationService, @IStorageService storageService: IStorageService, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { super(Parts.EDITOR_PART, { hasTitle: false }, themeService, storageService, layoutService); diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts index 1c8e7996d06..bcb59d73090 100644 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ b/src/vs/workbench/browser/parts/editor/editorPicker.ts @@ -18,7 +18,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { toResource, SideBySideEditor, IEditorInput } from 'vs/workbench/common/editor'; import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { withNullAsUndefined } from 'vs/base/common/types'; export class EditorPickerEntry extends QuickOpenEntryGroup { @@ -38,8 +37,8 @@ export class EditorPickerEntry extends QuickOpenEntryGroup { }; } - getLabel() { - return withNullAsUndefined(this.editor.getName()); + getLabel(): string { + return this.editor.getName(); } getIcon(): string { diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 1f2612e42ff..645159a3399 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -248,7 +248,7 @@ export class NoTabsTitleControl extends TitleControl { // Editor Label const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - const name = editor.getName() || ''; + const name = editor.getName(); const { labelFormat } = this.accessor.partOptions; let description: string; diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 889d9c678ad..ecbc295c0da 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -65,7 +65,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { return new EditorMemento(this.getId(), key, Object.create(null), limit, editorGroupService); // do not persist in storage as diff editors are never persisted } - getTitle(): string | undefined { + getTitle(): string { if (this.input) { return this.input.getName(); } @@ -219,7 +219,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { protected getAriaLabel(): string { let ariaLabel: string; - const inputName = this.input && this.input.getName(); + const inputName = this.input?.getName(); if (this.isReadOnly()) { ariaLabel = inputName ? nls.localize('readonlyEditorWithInputAriaLabel', "{0}. Readonly text compare editor.", inputName) : nls.localize('readonlyEditorAriaLabel', "Readonly text compare editor."); } else { diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index f5bfe3e7adb..1077097edde 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -287,7 +287,7 @@ export abstract class TitleControl extends Themable { label = localize('draggedEditorGroup', "{0} (+{1})", label, this.group.count - 1); } - applyDragImage(e, withUndefinedAsNull(label), 'monaco-editor-group-drag-image'); + applyDragImage(e, label, 'monaco-editor-group-drag-image'); } })); diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 64af1d21011..82e2097540f 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -12,6 +12,21 @@ z-index: initial; } +.monaco-workbench.windows .part.panel, +.monaco-workbench.linux .part.panel, +.monaco-workbench.web .part.panel { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); +} + .monaco-workbench .part.panel .title { height: 35px; display: flex; diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 36f7c6eb674..892e5521e12 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -702,7 +702,7 @@ class QuickPick extends QuickInput implements IQuickPi if (!this.visible) { return; } - this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true, message: !!this.validationMessage } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok: this.ok }); + this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true, message: !!this.validationMessage, customButton: this.customButton } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage, customButton: this.customButton, ok: this.ok }); super.update(); if (this.ui.inputBox.value !== this.value) { this.ui.inputBox.value = this.value; @@ -895,6 +895,7 @@ export class QuickInputService extends Component implements IQuickInputService { private idPrefix = 'quickInput_'; // Constant since there is still only one. private ui: QuickInputUI | undefined; + private dimension?: dom.Dimension; private comboboxAccessibility = false; private enabled = true; private inQuickOpenWidgets: Record = {}; @@ -1498,6 +1499,7 @@ export class QuickInputService extends Component implements IQuickInputService { } layout(dimension: dom.Dimension): void { + this.dimension = dimension; this.updateLayout(); } @@ -1512,7 +1514,7 @@ export class QuickInputService extends Component implements IQuickInputService { style.marginLeft = '-' + (width / 2) + 'px'; this.ui.inputBox.layout(); - this.ui.list.layout(); + this.ui.list.layout(this.dimension && this.dimension.height * 0.6); } } diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index 1e62ba48a8a..9129e303b84 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -468,7 +468,8 @@ export class QuickInputList { this.list.domFocus(); } - layout(): void { + layout(maxHeight?: number): void { + this.list.getHTMLElement().style.maxHeight = maxHeight ? `calc(${Math.floor(maxHeight / 44) * 44}px)` : ''; this.list.layout(); } diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 6f7e2254a6a..c56d69a480c 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -724,7 +724,7 @@ export class EditorHistoryEntryGroup extends QuickOpenEntryGroup { export class EditorHistoryEntry extends EditorQuickOpenEntry { private input: IEditorInput | IResourceInput; private resource: URI | undefined; - private label: string | undefined; + private label: string; private description?: string; private dirty: boolean; @@ -745,7 +745,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { if (input instanceof EditorInput) { this.resource = resourceForEditorHistory(input, fileService); - this.label = types.withNullAsUndefined(input.getName()); + this.label = input.getName(); this.description = input.getDescription(); this.dirty = input.isDirty(); } else { @@ -765,7 +765,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { return this.dirty ? 'dirty' : ''; } - getLabel(): string | undefined { + getLabel(): string { return this.label; } diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index eb324691b34..6d699628cb6 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -3,16 +3,31 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .sidebar > .content { +.monaco-workbench.windows .part.sidebar, +.monaco-workbench.linux .part.sidebar, +.monaco-workbench.web .part.sidebar { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); +} + +.monaco-workbench .part.sidebar > .content { overflow: hidden; } -.monaco-workbench.nosidebar > .sidebar { +.monaco-workbench.nosidebar > .part.sidebar { display: none !important; visibility: hidden !important; } -.monaco-workbench .sidebar > .title > .title-label h2 { +.monaco-workbench .part.sidebar > .title > .title-label h2 { text-transform: uppercase; } @@ -54,4 +69,4 @@ background-repeat: no-repeat; width: 16px; height: 16px; -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index b859f25c71f..458d024626b 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -13,6 +13,21 @@ overflow: visible; } +.monaco-workbench.windows .part.statusbar, +.monaco-workbench.linux .part.statusbar, +.monaco-workbench.web .part.statusbar { + /* + * Explicitly put the part onto its own layer to help Chrome to + * render the content with LCD-anti-aliasing. By partioning the + * workbench into multiple layers, we can ensure that a bad + * behaving part is not making another part fallback to greyscale + * rendering. + * + * macOS: does not render LCD-anti-aliased. + */ + transform: translate3d(0px, 0px, 0px); +} + .monaco-workbench .part.statusbar.status-border-top::after { content: ''; position: absolute; diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 2f312f55819..265d6518c40 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -68,7 +68,7 @@ position: absolute; top: 0; width: 100%; - height: 20%; + height: 4px; } .monaco-workbench.windows.fullscreen .part.titlebar > .resizer, diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index dac036e01dc..0c3d435f0aa 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -42,6 +42,7 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { optional } from 'vs/platform/instantiation/common/instantiation'; // tslint:disable-next-line: import-patterns layering TODO@sbatten import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { BrowserFeatures } from 'vs/base/browser/canIUse'; export abstract class MenubarControl extends Disposable { @@ -673,7 +674,7 @@ export class CustomMenubarControl extends MenubarControl { } this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, () => { - if (this.menubar) { + if (this.menubar && !BrowserFeatures.pointerEvents) { this.menubar.blur(); } })); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 0e57e5a6908..0bbd42aae20 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -20,7 +20,7 @@ import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workben import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER } from 'vs/workbench/common/theme'; +import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { Color } from 'vs/base/common/color'; @@ -45,8 +45,6 @@ import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; // TODO@sbatten https://github.com/microsoft/vscode/issues/81360 // tslint:disable-next-line: import-patterns layering import { IElectronService } from 'vs/platform/electron/node/electron'; -// tslint:disable-next-line: import-patterns layering -import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; export class TitlebarPart extends Part implements ITitleService { @@ -107,8 +105,7 @@ export class TitlebarPart extends Part implements ITitleService { @IContextKeyService contextKeyService: IContextKeyService, @IHostService private readonly hostService: IHostService, @IProductService private readonly productService: IProductService, - @optional(IElectronService) private electronService: IElectronService, - @optional(IElectronEnvironmentService) private readonly electronEnvironmentService: IElectronEnvironmentService + @optional(IElectronService) private electronService: IElectronService ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); @@ -448,13 +445,8 @@ export class TitlebarPart extends Part implements ITitleService { // Resizer this.resizer = append(this.element, $('div.resizer')); - const isMaximized = this.environmentService.configuration.maximized ? true : false; - this.onDidChangeMaximized(isMaximized); - - this._register(Event.any( - Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), _ => true), - Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), _ => false) - )(e => this.onDidChangeMaximized(e))); + this._register(this.layoutService.onMaximizeChange(maximized => this.onDidChangeMaximized(maximized))); + this.onDidChangeMaximized(this.layoutService.isWindowMaximized()); } // Since the title area is used to drag the window, we do not want to steal focus from the @@ -510,7 +502,13 @@ export class TitlebarPart extends Part implements ITitleService { removeClass(this.element, 'inactive'); } - const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND) || ''; + const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND, (color, theme) => { + // LCD Rendering Support: the title bar part is a defining its own GPU layer. + // To benefit from LCD font rendering, we must ensure that we always set an + // opaque background color. As such, we compute an opaque color given we know + // the background color is the workbench background. + return color.isOpaque() ? color : color.makeOpaque(WORKBENCH_BACKGROUND(theme)); + }) || ''; this.element.style.backgroundColor = titleBackground; if (titleBackground && Color.fromHex(titleBackground).isLighter()) { addClass(this.element, 'light'); diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index e2a4ca4d321..e5b8cbc8679 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature0, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { runWhenIdle, IdleDeadline } from 'vs/base/common/async'; @@ -19,7 +19,7 @@ export namespace Extensions { export const Workbench = 'workbench.contributions.kind'; } -export type IWorkbenchContributionSignature = IConstructorSignature0; +type IWorkbenchContributionSignature = new (...services: Service) => IWorkbenchContribution; export interface IWorkbenchContributionsRegistry { @@ -29,7 +29,7 @@ export interface IWorkbenchContributionsRegistry { * * @param phase the lifecycle phase when to instantiate the contribution. */ - registerWorkbenchContribution(contribution: IWorkbenchContributionSignature, phase: LifecyclePhase): void; + registerWorkbenchContribution(contribution: IWorkbenchContributionSignature, phase: LifecyclePhase): void; /** * Starts the registry by providing the required services. @@ -43,8 +43,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry private readonly toBeInstantiated: Map[]> = new Map[]>(); - registerWorkbenchContribution(ctor: IWorkbenchContributionSignature, phase: LifecyclePhase = LifecyclePhase.Starting): void { - + registerWorkbenchContribution(ctor: { new(...services: Services): IWorkbenchContribution }, phase: LifecyclePhase = LifecyclePhase.Starting): void { // Instantiate directly if we are already matching the provided phase if (this.instantiationService && this.lifecycleService && this.lifecycleService.phase >= phase) { this.instantiationService.createInstance(ctor); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 4ac59c9c71c..e2dcb62669c 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -294,7 +294,7 @@ export interface IEditorInput extends IDisposable { /** * Returns the display name of this input. */ - getName(): string | undefined; + getName(): string; /** * Returns the display description of this input. @@ -360,8 +360,8 @@ export abstract class EditorInput extends Disposable implements IEditorInput { * Returns the name of this input that can be shown to the user. Examples include showing the name of the input * above the editor area when the input is shown. */ - getName(): string | undefined { - return undefined; + getName(): string { + return `Editor ${this.getTypeId()}`; } /** @@ -376,7 +376,7 @@ export abstract class EditorInput extends Disposable implements IEditorInput { * Returns the title of this input that can be shown to the user. Examples include showing the title of * the input above the editor area as hover over the input label. */ - getTitle(verbosity?: Verbosity): string | undefined { + getTitle(verbosity?: Verbosity): string { return this.getName(); } @@ -415,13 +415,6 @@ export abstract class EditorInput extends Disposable implements IEditorInput { return false; } - /** - * Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result. - */ - confirmSave(): Promise { - return Promise.resolve(ConfirmResult.DONT_SAVE); - } - /** * Saves the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation. */ @@ -476,12 +469,6 @@ export abstract class EditorInput extends Disposable implements IEditorInput { } } -export const enum ConfirmResult { - SAVE, - DONT_SAVE, - CANCEL -} - export const enum EncodingMode { /** @@ -573,10 +560,6 @@ export class SideBySideEditorInput extends EditorInput { return this.master.isDirty(); } - confirmSave(): Promise { - return this.master.confirmSave(); - } - save(): Promise { return this.master.save(); } diff --git a/src/vs/workbench/common/editor/binaryEditorModel.ts b/src/vs/workbench/common/editor/binaryEditorModel.ts index a911af30a8c..a211169a6b7 100644 --- a/src/vs/workbench/common/editor/binaryEditorModel.ts +++ b/src/vs/workbench/common/editor/binaryEditorModel.ts @@ -7,7 +7,7 @@ import { EditorModel } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { Schemas } from 'vs/base/common/network'; -import { DataUri, basename } from 'vs/base/common/resources'; +import { DataUri } from 'vs/base/common/resources'; import { MIME_BINARY } from 'vs/base/common/mime'; /** @@ -20,7 +20,7 @@ export class BinaryEditorModel extends EditorModel { constructor( public readonly resource: URI, - private readonly name: string | undefined, + private readonly name: string, @IFileService private readonly fileService: IFileService ) { super(); @@ -46,7 +46,7 @@ export class BinaryEditorModel extends EditorModel { * The name of the binary resource. */ getName(): string { - return this.name || basename(this.resource); + return this.name; } /** diff --git a/src/vs/workbench/common/editor/dataUriEditorInput.ts b/src/vs/workbench/common/editor/dataUriEditorInput.ts index d13f9e9881e..94911e9c9db 100644 --- a/src/vs/workbench/common/editor/dataUriEditorInput.ts +++ b/src/vs/workbench/common/editor/dataUriEditorInput.ts @@ -7,7 +7,7 @@ import { EditorInput } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; -import { DataUri } from 'vs/base/common/resources'; +import { DataUri, basename } from 'vs/base/common/resources'; /** * An editor input to present data URIs in a binary editor. Data URIs have the form of: @@ -17,25 +17,31 @@ export class DataUriEditorInput extends EditorInput { static readonly ID: string = 'workbench.editors.dataUriEditorInput'; + private readonly name: string; + private readonly description: string | undefined; + constructor( - private readonly name: string | undefined, - private readonly description: string | undefined, + name: string | undefined, + description: string | undefined, private readonly resource: URI, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); - if (!this.name || !this.description) { + if (!name || !description) { const metadata = DataUri.parseMetaData(this.resource); - if (!this.name) { - this.name = metadata.get(DataUri.META_DATA_LABEL); + if (!name) { + name = metadata.get(DataUri.META_DATA_LABEL) || basename(resource); } - if (!this.description) { - this.description = metadata.get(DataUri.META_DATA_DESCRIPTION); + if (!description) { + description = metadata.get(DataUri.META_DATA_DESCRIPTION); } } + + this.name = name; + this.description = description; } getResource(): URI { @@ -46,7 +52,7 @@ export class DataUriEditorInput extends EditorInput { return DataUriEditorInput.ID; } - getName(): string | undefined { + getName(): string { return this.name; } diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index bd2b9376854..3b30153f526 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -18,7 +18,13 @@ export class DiffEditorInput extends SideBySideEditorInput { private cachedModel: DiffEditorModel | null = null; - constructor(name: string, description: string | undefined, original: EditorInput, modified: EditorInput, private readonly forceOpenAsBinary?: boolean) { + constructor( + name: string, + description: string | undefined, + original: EditorInput, + modified: EditorInput, + private readonly forceOpenAsBinary?: boolean + ) { super(name, description, original, modified); } diff --git a/src/vs/workbench/common/editor/untitledTextEditorInput.ts b/src/vs/workbench/common/editor/untitledTextEditorInput.ts index 466216f9840..3e2998c3e29 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorInput.ts @@ -8,7 +8,7 @@ import { suggestFilename } from 'vs/base/common/mime'; import { createMemoizer } from 'vs/base/common/decorators'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; -import { EditorInput, IEncodingSupport, EncodingMode, ConfirmResult, Verbosity, IModeSupport } from 'vs/workbench/common/editor'; +import { EditorInput, IEncodingSupport, EncodingMode, Verbosity, IModeSupport } from 'vs/workbench/common/editor'; import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; @@ -109,7 +109,7 @@ export class UntitledTextEditorInput extends EditorInput implements IEncodingSup return this.labelService.getUriLabel(this.resource); } - getTitle(verbosity: Verbosity): string | undefined { + getTitle(verbosity: Verbosity): string { if (!this.hasAssociatedFilePath) { return this.getName(); } @@ -122,8 +122,6 @@ export class UntitledTextEditorInput extends EditorInput implements IEncodingSup case Verbosity.LONG: return this.longTitle; } - - return undefined; } isDirty(): boolean { @@ -148,10 +146,6 @@ export class UntitledTextEditorInput extends EditorInput implements IEncodingSup return false; } - confirmSave(): Promise { - return this.textFileService.confirmSave([this.resource]); - } - save(): Promise { return this.textFileService.save(this.resource); } diff --git a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index e3229c3f613..618920a6e28 100644 --- a/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -51,7 +51,7 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { } } - getTitle(): string | undefined { + getTitle(): string { return this.input ? this.input.getName() : nls.localize('binaryFileEditor', "Binary File Viewer"); } } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 1186a19c258..f88860ab91b 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -456,11 +456,13 @@ export class FileSorter implements ITreeSorter { } } -const fileOverwriteConfirm: IConfirmation = { - message: localize('confirmOverwrite', "A file or folder with the same name already exists in the destination folder. Do you want to replace it?"), - detail: localize('irreversible', "This action is irreversible!"), - primaryButton: localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), - type: 'warning' +const fileOverwriteConfirm = (name: string) => { + return { + message: localize('confirmOverwrite', "A file or folder with the name '{0}' already exists in the destination folder. Do you want to replace it?", name), + detail: localize('irreversible', "This action is irreversible!"), + primaryButton: localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), + type: 'warning' + }; }; export class FileDragAndDrop implements ITreeDragAndDrop { @@ -643,7 +645,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { const name = file.name; if (typeof name === 'string' && event.target?.result instanceof ArrayBuffer) { if (target.getChild(name)) { - const { confirmed } = await this.dialogService.confirm(fileOverwriteConfirm); + const { confirmed } = await this.dialogService.confirm(fileOverwriteConfirm(name)); if (!confirmed) { return; } @@ -717,9 +719,10 @@ export class FileDragAndDrop implements ITreeDragAndDrop { }); } - const resourceExists = resources.some(resource => targetNames.has(!hasToIgnoreCase(resource) ? basename(resource) : basename(resource).toLowerCase())); + const filtered = resources.filter(resource => targetNames.has(!hasToIgnoreCase(resource) ? basename(resource) : basename(resource).toLowerCase())); + const resourceExists = filtered.length >= 1; if (resourceExists) { - const confirmationResult = await this.dialogService.confirm(fileOverwriteConfirm); + const confirmationResult = await this.dialogService.confirm(fileOverwriteConfirm(basename(filtered[0]))); if (!confirmationResult.confirmed) { return; } diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 2304fbb4d74..8b93f82c51a 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -39,7 +39,7 @@ import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { memoize } from 'vs/base/common/decorators'; import { ElementsDragAndDropData, DesktopDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { URI } from 'vs/base/common/uri'; -import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; +import { withUndefinedAsNull } from 'vs/base/common/types'; import { isWeb } from 'vs/base/common/platform'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -619,13 +619,13 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop 1) { return String(elements.length); } const element = elements[0]; - return element instanceof OpenEditor ? withNullAsUndefined(element.editor.getName()) : element.label; + return element instanceof OpenEditor ? element.editor.getName() : element.label; } onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index 2695c40d587..104bebe8fb3 100644 --- a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; 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'; +import { EncodingMode, EditorInput, IFileEditorInput, ITextEditorModel, Verbosity, IRevertOptions } from 'vs/workbench/common/editor'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; @@ -243,10 +243,6 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return model.isDirty(); } - confirmSave(): Promise { - return this.textFileService.confirmSave([this.resource]); - } - save(): Promise { return this.textFileService.save(this.resource); } diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 2201cbe11d0..d8f74092225 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -314,6 +314,7 @@ class MarkerWidget extends Disposable { const lineMatches = filterData && filterData.lineMatches || []; let lastLineElement: HTMLElement | undefined = undefined; + this.messageAndDetailsContainer.title = element.marker.message; for (let index = 0; index < (multiline ? lines.length : 1); index++) { lastLineElement = dom.append(this.messageAndDetailsContainer, dom.$('.marker-message-line')); const messageElement = dom.append(lastLineElement, dom.$('.marker-message')); diff --git a/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts b/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts index 8362d0f7ad7..7d22b68aa69 100644 --- a/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts +++ b/src/vs/workbench/contrib/outline/browser/outlineNavigation.ts @@ -164,7 +164,7 @@ registerEditorAction(class extends EditorAction { kbOpts: { weight: KeybindingWeight.EditorContrib, kbExpr: EditorContextKeys.focus, - primary: KeyMod.Shift | KeyMod.Alt | KeyCode.DownArrow, + primary: undefined, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.DownArrow, }, @@ -188,7 +188,7 @@ registerEditorAction(class extends EditorAction { kbOpts: { weight: KeybindingWeight.EditorContrib, kbExpr: EditorContextKeys.focus, - primary: KeyMod.Shift | KeyMod.Alt | KeyCode.UpArrow, + primary: undefined, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.UpArrow, }, diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 014d39c99bd..0d41239f527 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -152,7 +152,7 @@ export class PatternInputWidget extends Widget { this._register(this.inputBox.onDidChange(() => { if (this.searchConfig.searchOnType) { this._onCancel.fire(); - this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(false), this.searchConfig.searchOnTypeDebouncePeriod); + this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(true), this.searchConfig.searchOnTypeDebouncePeriod); } })); @@ -170,7 +170,7 @@ export class PatternInputWidget extends Widget { private onInputKeyUp(keyboardEvent: IKeyboardEvent) { switch (keyboardEvent.keyCode) { case KeyCode.Enter: - this._onSubmit.fire(true); + this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(false), 0); return; case KeyCode.Escape: this._onCancel.fire(); diff --git a/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts b/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts index 62711b74fc8..1a43978f692 100644 --- a/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts +++ b/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IEditorInputFactory, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, EditorModel, ConfirmResult, IRevertOptions, EditorOptions } from 'vs/workbench/common/editor'; +import { IEditorInputFactory, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, EditorModel, IRevertOptions, EditorOptions } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { Dimension, addDisposableListener, EventType } from 'vs/base/browser/dom'; @@ -160,11 +160,6 @@ class TestCustomEditorInput extends EditorInput implements IWorkingCopy { return this.dirty; } - confirmSave(): Promise { - // TODO - return Promise.resolve(ConfirmResult.DONT_SAVE); - } - save(): Promise { this.setDirty(false); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 7d72356a6f0..5e631d16273 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -6,7 +6,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE } from 'vs/platform/userDataSync/common/userDataSync'; import { localize } from 'vs/nls'; -import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions'; @@ -26,6 +26,9 @@ import { isEqual } from 'vs/base/common/resources'; import { IEditorInput } from 'vs/workbench/common/editor'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { FalseContext } from 'vs/platform/contextkey/common/contextkeys'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthTokenStatus.Inactive); const SYNC_PUSH_LIGHT_ICON_URI = URI.parse(registerAndGetAmdImageURL(`vs/workbench/contrib/userDataSync/browser/media/check-light.svg`)); @@ -53,6 +56,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IHistoryService private readonly historyService: IHistoryService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IDialogService private readonly dialogService: IDialogService, + @IStorageService private readonly storageService: IStorageService, + @IQuickInputService private readonly quickInputService: IQuickInputService, ) { super(); this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); @@ -128,6 +133,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (this.userDataSyncService.status !== SyncStatus.Uninitialized && this.configurationService.getValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING) && this.authTokenService.status === AuthTokenStatus.Inactive) { badge = new NumberBadge(1, () => localize('sign in', "Sync: Sign in...")); + } else if (this.authTokenService.status === AuthTokenStatus.SigningIn) { + badge = new ProgressBadge(() => localize('signing in', "Signin in...")); + clazz = 'progress-badge'; } else if (this.userDataSyncService.status === SyncStatus.HasConflicts) { badge = new NumberBadge(1, () => localize('resolve conflicts', "Resolve Conflicts")); } else if (this.userDataSyncService.status === SyncStatus.Syncing) { @@ -153,7 +161,50 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } await this.signIn(); } + await this.configureSyncOptions(); await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, true); + this.notificationService.info(localize('Sync Started', "Sync Started.")); + } + + private async configureSyncOptions(): Promise { + if (this.storageService.getBoolean('userDataSync.configureOptions.donotAsk', StorageScope.GLOBAL, false)) { + return; + } + return new Promise((c, e) => { + const disposables: DisposableStore = new DisposableStore(); + const quickPick = this.quickInputService.createQuickPick(); + disposables.add(quickPick); + quickPick.placeholder = localize('select configurations to sync', "Choose what to sync"); + quickPick.canSelectMany = true; + const items = [{ + id: 'configurationSync.enableSettings', + label: localize('user settings', "User Settings") + }, { + id: 'configurationSync.enableExtensions', + label: localize('extensions', "Extensions") + }]; + quickPick.items = items; + quickPick.selectedItems = items.filter(item => this.configurationService.getValue(item.id)); + quickPick.customButton = true; + quickPick.customLabel = localize('do not ask', "Don't Ask Again"); + disposables.add(quickPick.onDidCustom(() => { + this.storageService.store('userDataSync.configureOptions.donotAsk', true, StorageScope.GLOBAL); + quickPick.hide(); + })); + disposables.add(quickPick.onDidAccept(() => quickPick.hide())); + disposables.add(quickPick.onDidHide(() => { + for (const item of items) { + const wasEnabled = this.configurationService.getValue(item.id); + const isEnabled = !!quickPick.selectedItems.filter(selected => selected.id === item.id)[0]; + if (wasEnabled !== isEnabled) { + this.configurationService.updateValue(item.id!, isEnabled); + } + } + disposables.dispose(); + c(); + })); + quickPick.show(); + }); } private async turnOff(): Promise { @@ -161,7 +212,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async signIn(): Promise { - return this.authTokenService.login(); + try { + await this.authTokenService.login(); + } catch (e) { + this.notificationService.error(e); + throw e; + } } private async signOut(): Promise { @@ -225,13 +281,13 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo id: 'workbench.userData.actions.syncStart', title: localize('start sync', "Sync: Turn On") }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`)), + when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.not(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.notEqualsTo(AuthTokenStatus.SigningIn)), }; CommandsRegistry.registerCommand(startSyncMenuItem.command.id, () => this.turnOn()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, startSyncMenuItem); MenuRegistry.appendMenuItem(MenuId.CommandPalette, startSyncMenuItem); - const signInCommandId = 'workbench.userData.actions.login'; + const signInCommandId = 'workbench.userData.actions.signin'; const signInWhenContext = ContextKeyExpr.and(CONTEXT_SYNC_STATE.notEqualsTo(SyncStatus.Uninitialized), ContextKeyExpr.has(`config.${UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING}`), CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.Inactive)); CommandsRegistry.registerCommand(signInCommandId, () => this.signIn()); MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { @@ -250,6 +306,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo when: signInWhenContext, }); + const signingInCommandId = 'workbench.userData.actions.signingin'; + CommandsRegistry.registerCommand(signInCommandId, () => null); + MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + group: '5_sync', + command: { + id: signingInCommandId, + title: localize('signinig in', "Sync: Signing in..."), + precondition: FalseContext + }, + when: CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.SigningIn) + }); + const stopSycCommand = { id: 'workbench.userData.actions.stopSync', title: localize('stop sync', "Sync: Turn Off") @@ -310,7 +378,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo const signOutMenuItem: IMenuItem = { group: '5_sync', command: { - id: 'workbench.userData.actions.logout', + id: 'workbench.userData.actions.signout', title: localize('sign out', "Sign Out") }, when: ContextKeyExpr.and(CONTEXT_AUTH_TOKEN_STATE.isEqualTo(AuthTokenStatus.Active)), diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index 1a6bee73161..4577a20a046 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -101,7 +101,7 @@ export class WebviewInput extends EditorInput { return this._name; } - public getTitle(_verbosity?: Verbosity) { + public getTitle(_verbosity?: Verbosity): string { return this.getName(); } diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index d550a53a3c4..149c916f53d 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -5,6 +5,7 @@ import 'vs/css!./walkThroughPart'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -37,6 +38,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { Dimension, size } from 'vs/base/browser/dom'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { domEvent } from 'vs/base/browser/event'; export const WALK_THROUGH_FOCUS = new RawContextKey('interactivePlaygroundFocus', false); @@ -112,6 +114,14 @@ export class WalkThroughPart extends BaseEditor { } } + private onTouchChange(event: GestureEvent) { + event.preventDefault(); + event.stopPropagation(); + + const scrollPosition = this.scrollbar.getScrollPosition(); + this.scrollbar.setScrollPosition({ scrollTop: scrollPosition.scrollTop - event.translationY }); + } + private addEventListener(element: E, type: K, listener: (this: E, ev: HTMLElementEventMap[K]) => any, useCapture?: boolean): IDisposable; private addEventListener(element: E, type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): IDisposable; private addEventListener(element: E, type: string, listener: EventListenerOrEventListenerObject, useCapture?: boolean): IDisposable { @@ -396,6 +406,8 @@ export class WalkThroughPart extends BaseEditor { this.scrollbar.scanDomNode(); this.loadTextEditorViewState(input); this.updatedScrollPosition(); + this.contentDisposables.push(Gesture.addTarget(innerContent)); + this.contentDisposables.push(domEvent(innerContent, TouchEventType.Change)(this.onTouchChange, this, this.disposables)); }); } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 1272f66f0a6..46a3496a065 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -21,7 +21,7 @@ import * as browser from 'vs/base/browser/browser'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; -import { ipcRenderer as ipc, webFrame, crashReporter, Event } from 'electron'; +import { ipcRenderer as ipc, webFrame, crashReporter, Event as IpcEvent } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -62,6 +62,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { IWorkingCopyService, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { Event } from 'vs/base/common/event'; export class ElectronWindow extends Disposable { @@ -126,7 +127,7 @@ export class ElectronWindow extends Disposable { }); // Support runAction event - ipc.on('vscode:runAction', async (event: Event, request: IRunActionInWindowRequest) => { + ipc.on('vscode:runAction', async (event: IpcEvent, request: IRunActionInWindowRequest) => { const args: unknown[] = request.args || []; // If we run an action from the touchbar, we fill in the currently active resource @@ -157,27 +158,27 @@ export class ElectronWindow extends Disposable { }); // Support runKeybinding event - ipc.on('vscode:runKeybinding', (event: Event, request: IRunKeybindingInWindowRequest) => { + ipc.on('vscode:runKeybinding', (event: IpcEvent, request: IRunKeybindingInWindowRequest) => { if (document.activeElement) { this.keybindingService.dispatchByUserSettingsLabel(request.userSettingsLabel, document.activeElement); } }); // Error reporting from main - ipc.on('vscode:reportError', (event: Event, error: string) => { + ipc.on('vscode:reportError', (event: IpcEvent, error: string) => { if (error) { errors.onUnexpectedError(JSON.parse(error)); } }); // Support openFiles event for existing and new files - ipc.on('vscode:openFiles', (event: Event, request: IOpenFileRequest) => this.onOpenFiles(request)); + ipc.on('vscode:openFiles', (event: IpcEvent, request: IOpenFileRequest) => this.onOpenFiles(request)); // Support addFolders event if we have a workspace opened - ipc.on('vscode:addFolders', (event: Event, request: IAddFoldersRequest) => this.onAddFoldersRequest(request)); + ipc.on('vscode:addFolders', (event: IpcEvent, request: IAddFoldersRequest) => this.onAddFoldersRequest(request)); // Message support - ipc.on('vscode:showInfoMessage', (event: Event, message: string) => { + ipc.on('vscode:showInfoMessage', (event: IpcEvent, message: string) => { this.notificationService.info(message); }); @@ -215,7 +216,7 @@ export class ElectronWindow extends Disposable { }); // keyboard layout changed event - ipc.on('vscode:accessibilitySupportChanged', (event: Event, accessibilitySupportEnabled: boolean) => { + ipc.on('vscode:accessibilitySupportChanged', (event: IpcEvent, accessibilitySupportEnabled: boolean) => { this.accessibilityService.setAccessibilitySupport(accessibilitySupportEnabled ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); }); @@ -281,6 +282,17 @@ export class ElectronWindow extends Disposable { } })); } + + this._register(Event.any( + Event.map(Event.filter(this.electronService.onWindowMaximize, id => id === this.electronEnvironmentService.windowId), () => true), + Event.map(Event.filter(this.electronService.onWindowUnmaximize, id => id === this.electronEnvironmentService.windowId), () => false) + )(e => this.onDidChangeMaximized(e))); + + this.onDidChangeMaximized(this.environmentService.configuration.maximized ?? false); + } + + private onDidChangeMaximized(maximized: boolean): void { + this.layoutService.updateWindowMaximizedState(maximized); } private onDidVisibleEditorsChange(): void { diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts index f8d717d76a5..cead0c626ba 100644 --- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; -import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, FileFilter } from 'vs/platform/dialogs/common/dialogs'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, FileFilter, IFileDialogService, IDialogService, ConfirmResult, getConfirmMessage } 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'; @@ -20,8 +20,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IFileService } from 'vs/platform/files/common/files'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import Severity from 'vs/base/common/severity'; -export abstract class AbstractFileDialogService { +export abstract class AbstractFileDialogService implements IFileDialogService { _serviceBrand: undefined; @@ -34,6 +35,7 @@ export abstract class AbstractFileDialogService { @IConfigurationService protected readonly configurationService: IConfigurationService, @IFileService protected readonly fileService: IFileService, @IOpenerService protected readonly openerService: IOpenerService, + @IDialogService private readonly dialogService: IDialogService ) { } defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { @@ -78,6 +80,40 @@ export abstract class AbstractFileDialogService { return this.defaultFilePath(schemeFilter); } + async showSaveConfirm(fileNameOrResources: string | URI[]): Promise { + if (this.environmentService.isExtensionDevelopment) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + } + + if (Array.isArray(fileNameOrResources) && fileNameOrResources.length === 0) { + return ConfirmResult.DONT_SAVE; + } + + let message: string; + if (typeof fileNameOrResources === 'string' || fileNameOrResources.length === 1) { + message = nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", typeof fileNameOrResources === 'string' ? fileNameOrResources : resources.basename(fileNameOrResources[0])); + } else { + message = getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", fileNameOrResources.length), fileNameOrResources); + } + + const buttons: string[] = [ + Array.isArray(fileNameOrResources) && fileNameOrResources.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), + nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), + nls.localize('cancel', "Cancel") + ]; + + const { choice } = await this.dialogService.show(Severity.Warning, message, buttons, { + cancelId: 2, + detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") + }); + + switch (choice) { + case 0: return ConfirmResult.SAVE; + case 1: return ConfirmResult.DONT_SAVE; + default: return ConfirmResult.CANCEL; + } + } + protected abstract addFileSchemaIfNeeded(schema: string): string[]; protected async pickFileFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions, preferNewWindow: boolean): Promise { @@ -179,4 +215,12 @@ export abstract class AbstractFileDialogService { protected getFileSystemSchema(options: { availableFileSystems?: readonly string[], defaultUri?: URI }): string { return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow(); } + + abstract pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise; + abstract pickFileAndOpen(options: IPickAndOpenOptions): Promise; + abstract pickFolderAndOpen(options: IPickAndOpenOptions): Promise; + abstract pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise; + abstract pickFileToSave(options: ISaveDialogOptions): Promise; + abstract showSaveDialog(options: ISaveDialogOptions): Promise; + abstract showOpenDialog(options: IOpenDialogOptions): Promise; } diff --git a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts index a4873580a8d..f4452c6dc6f 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/fileDialogService.ts @@ -6,7 +6,7 @@ import { SaveDialogOptions, OpenDialogOptions } from 'electron'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IDialogService, ConfirmResult } 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'; @@ -33,8 +33,11 @@ export class FileDialogService extends AbstractFileDialogService implements IFil @IConfigurationService configurationService: IConfigurationService, @IFileService fileService: IFileService, @IOpenerService openerService: IOpenerService, - @IElectronService private readonly electronService: IElectronService - ) { super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService); } + @IElectronService private readonly electronService: IElectronService, + @IDialogService dialogService: IDialogService + ) { + super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService, dialogService); + } private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { return { @@ -180,6 +183,16 @@ export class FileDialogService extends AbstractFileDialogService implements IFil // Don't allow untitled schema through. return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]); } + + async showSaveConfirm(fileNameOrResources: string | URI[]): Promise { + if (this.environmentService.isExtensionDevelopment) { + if (!this.environmentService.args['extension-development-confirm-save']) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + } + } + + return super.showSaveConfirm(fileNameOrResources); + } } registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index fcdb63ab519..7f48cf25e57 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -623,7 +623,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Data URI else if (resource.scheme === Schemas.data) { - input = instantiationService.createInstance(DataUriEditorInput, label, description, resource); + input = instantiationService.createInstance(DataUriEditorInput, label || basename(resource), description, resource); } // Resource diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 2851a78df2b..429b7460fa6 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -484,8 +484,8 @@ export class HistoryService extends Disposable implements IHistoryService { private handleEditorEventInHistory(editor?: IBaseEditor): void { const input = editor?.input; - // Ensure we have at least a name to show and not configured to exclude input - if (!input || !input.getName() || !this.include(input)) { + // Ensure we have not configured to exclude input + if (!input || !this.include(input)) { return; } diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 616d872bf57..94aa3ef9f55 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -41,6 +41,11 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ readonly onFullscreenChange: Event; + /** + * Emits when the window is maximized or unmaximized. + */ + readonly onMaximizeChange: Event; + /** * Emits when centered layout is enabled or disabled. */ @@ -188,4 +193,15 @@ export interface IWorkbenchLayoutService extends ILayoutService { * Register a part to participate in the layout. */ registerPart(part: Part): void; + + + /** + * Returns whether the window is maximized. + */ + isWindowMaximized(): boolean; + + /** + * Updates the maximized state of the window. + */ + updateWindowMaximizedState(maximized: boolean): void; } diff --git a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts index edcb4902f76..0b4e5a69d79 100644 --- a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts +++ b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts @@ -21,7 +21,7 @@ export class PreferencesEditorInput extends SideBySideEditorInput { return PreferencesEditorInput.ID; } - getTitle(verbosity: Verbosity): string | undefined { + getTitle(verbosity: Verbosity): string { return this.master.getTitle(verbosity); } } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index f7409aa72ca..fe4ddff014d 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -9,7 +9,7 @@ import { Emitter, AsyncEmitter } from 'vs/base/common/event'; import * as platform from 'vs/base/common/platform'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent } from 'vs/workbench/services/textfile/common/textfiles'; -import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor'; +import { IRevertOptions } from 'vs/workbench/common/editor'; import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IFileService, FileOperationError, FileOperationResult, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files'; @@ -24,9 +24,9 @@ import { Schemas } from 'vs/base/common/network'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { isEqualOrParent, isEqual, joinPath, dirname, extname, basename, toLocalResource } from 'vs/base/common/resources'; -import { getConfirmMessage, IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { coalesce } from 'vs/base/common/arrays'; @@ -232,7 +232,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } private async confirmBeforeShutdown(): Promise { - const confirm = await this.confirmSave(); + const confirm = await this.fileDialogService.showSaveConfirm(this.getDirty()); // Save if (confirm === ConfirmResult.SAVE) { @@ -496,49 +496,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return result.results.length === 1 && !!result.results[0].success; } - async confirmSave(resources?: URI[]): Promise { - if (this.environmentService.isExtensionDevelopment) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) - } - - const resourcesToConfirm = this.getDirty(resources); - if (resourcesToConfirm.length === 0) { - return ConfirmResult.DONT_SAVE; - } - - const message = resourcesToConfirm.length === 1 - ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", basename(resourcesToConfirm[0])) - : getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm); - - const buttons: string[] = [ - resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), - nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), - nls.localize('cancel', "Cancel") - ]; - - const { choice } = await this.dialogService.show(Severity.Warning, message, buttons, { - cancelId: 2, - detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") - }); - - switch (choice) { - case 0: return ConfirmResult.SAVE; - case 1: return ConfirmResult.DONT_SAVE; - default: return ConfirmResult.CANCEL; - } - } - - async confirmOverwrite(resource: URI): Promise { - const confirm: IConfirmation = { - message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), - detail: nls.localize('irreversible', "A file or folder with the same name already exists in the folder {0}. Replacing it will overwrite its current contents.", basename(dirname(resource))), - primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), - type: 'warning' - }; - - return (await this.dialogService.confirm(confirm)).confirmed; - } - saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise; saveAll(resources: URI[], options?: ISaveOptions): Promise; saveAll(arg1?: boolean | URI[], options?: ISaveOptions): Promise { @@ -849,6 +806,17 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } } + private async confirmOverwrite(resource: URI): Promise { + const confirm: IConfirmation = { + message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), + detail: nls.localize('irreversible', "A file or folder with the name '{0}' already exists in the folder '{1}'. Replacing it will overwrite its current contents.", basename(resource), basename(dirname(resource))), + primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), + type: 'warning' + }; + + return (await this.dialogService.confirm(confirm)).confirmed; + } + private suggestFileName(untitledResource: URI): URI { const untitledFileName = this.untitledTextEditorService.suggestFileName(untitledResource); const remoteAuthority = this.environmentService.configuration.remoteAuthority; diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index bfc329ca941..d07dc93f41e 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { Event, IWaitUntil } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IEncodingSupport, ConfirmResult, IRevertOptions, IModeSupport } from 'vs/workbench/common/editor'; +import { IEncodingSupport, IRevertOptions, IModeSupport } from 'vs/workbench/common/editor'; import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult, FileOperation } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; @@ -129,14 +129,6 @@ export interface ITextFileService extends IDisposable { * Move a file. If the file is dirty, its contents will be preserved and restored. */ move(source: URI, target: URI, overwrite?: boolean): Promise; - - /** - * Brings up the confirm dialog to either save, don't save or cancel. - * - * @param resources the resources of the files to ask for confirmation or null if - * confirming for all dirty resources. - */ - confirmSave(resources?: URI[]): Promise; } export class FileOperationWillRunEvent implements IWaitUntil { diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index 7a37e7ca8ab..e105588731a 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -39,7 +39,6 @@ import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ConfirmResult } from 'vs/workbench/common/editor'; import { assign } from 'vs/base/common/objects'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; @@ -315,16 +314,6 @@ export class NativeTextFileService extends AbstractTextFileService { protected getWindowCount(): Promise { return this.electronService.getWindowCount(); } - - async confirmSave(resources?: URI[]): Promise { - if (this.environmentService.isExtensionDevelopment) { - if (!this.environmentService.args['extension-development-confirm-save']) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) - } - } - - return super.confirmSave(resources); - } } export interface IEncodingOverride { diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 152212b71a2..db7e2bd8033 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -7,12 +7,11 @@ import * as sinon from 'sinon'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; -import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestContextService, TestFileService, TestElectronService, TestFilesConfigurationService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestContextService, TestFileService, TestElectronService, TestFilesConfigurationService, TestFileDialogService } from 'vs/workbench/test/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { ConfirmResult } from 'vs/workbench/common/editor'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; import { HotExitConfiguration, IFileService } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; @@ -22,6 +21,7 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { Schemas } from 'vs/base/common/network'; import { IElectronService } from 'vs/platform/electron/node/electron'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; class ServiceAccessor { constructor( @@ -32,7 +32,8 @@ class ServiceAccessor { @IWorkspaceContextService public contextService: TestContextService, @IModelService public modelService: ModelServiceImpl, @IFileService public fileService: TestFileService, - @IElectronService public electronService: TestElectronService + @IElectronService public electronService: TestElectronService, + @IFileDialogService public fileDialogService: TestFileDialogService ) { } } @@ -87,7 +88,7 @@ suite('Files - TextFileService', () => { (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; - service.setConfirmResult(ConfirmResult.CANCEL); + accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); await model.load(); model.textEditorModel!.setValue('foo'); @@ -103,7 +104,7 @@ suite('Files - TextFileService', () => { (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; - service.setConfirmResult(ConfirmResult.DONT_SAVE); + accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE); accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); await model.load(); @@ -129,7 +130,7 @@ suite('Files - TextFileService', () => { (accessor.textFileService.models).add(model.resource, model); const service = accessor.textFileService; - service.setConfirmResult(ConfirmResult.SAVE); + accessor.fileDialogService.setConfirmResult(ConfirmResult.SAVE); accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); await model.load(); @@ -429,7 +430,7 @@ suite('Files - TextFileService', () => { accessor.electronService.windowCount = Promise.resolve(2); } // Set cancel to force a veto if hot exit does not trigger - service.setConfirmResult(ConfirmResult.CANCEL); + accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); await model.load(); model.textEditorModel!.setValue('foo'); diff --git a/src/vs/workbench/test/common/editor/dataUriEditorInput.test.ts b/src/vs/workbench/test/common/editor/dataUriEditorInput.test.ts index ade72e9f942..44d111a1f81 100644 --- a/src/vs/workbench/test/common/editor/dataUriEditorInput.test.ts +++ b/src/vs/workbench/test/common/editor/dataUriEditorInput.test.ts @@ -31,4 +31,4 @@ suite('DataUriEditorInput', () => { assert.equal(model.getMime(), 'image/png'); }); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/common/editor/editorInput.test.ts b/src/vs/workbench/test/common/editor/editorInput.test.ts index 5208ba30b5a..3adc2957bf5 100644 --- a/src/vs/workbench/test/common/editor/editorInput.test.ts +++ b/src/vs/workbench/test/common/editor/editorInput.test.ts @@ -22,7 +22,7 @@ suite('Workbench editor input', () => { assert(input.matches(input)); assert(!input.matches(otherInput)); assert(!input.matches(null)); - assert(!input.getName()); + assert(input.getName()); input.onDispose(() => { assert(true); @@ -84,4 +84,4 @@ suite('Workbench editor input', () => { otherInput.dispose(); assert.equal(counter, 2); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index c150c035c6f..1e87e03eeaa 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -11,7 +11,7 @@ import * as resources from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { ConfirmResult, IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions } from 'vs/workbench/common/editor'; import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; @@ -49,7 +49,7 @@ 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'; import { Range } from 'vs/editor/common/core/range'; -import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IShowResult } from 'vs/platform/dialogs/common/dialogs'; +import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IShowResult, ConfirmResult } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -192,7 +192,6 @@ export class TestTextFileService extends NativeTextFileService { public cleanupBackupsBeforeShutdownCalled!: boolean; private promptPath!: URI; - private confirmResult!: ConfirmResult; private resolveTextContentError!: FileOperationError | null; constructor( @@ -241,10 +240,6 @@ export class TestTextFileService extends NativeTextFileService { this.promptPath = path; } - public setConfirmResult(result: ConfirmResult): void { - this.confirmResult = result; - } - public setResolveTextContentErrorOnce(error: FileOperationError): void { this.resolveTextContentError = error; } @@ -275,14 +270,6 @@ export class TestTextFileService extends NativeTextFileService { return Promise.resolve(this.promptPath); } - public confirmSave(_resources?: URI[]): Promise { - return Promise.resolve(this.confirmResult); - } - - public confirmOverwrite(_resource: URI): Promise { - return Promise.resolve(true); - } - protected cleanupBackupsBeforeShutdown(): Promise { this.cleanupBackupsBeforeShutdownCalled = true; return Promise.resolve(); @@ -303,6 +290,8 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(IUntitledTextEditorService, instantiationService.createInstance(UntitledTextEditorService)); instantiationService.stub(IStorageService, new TestStorageService()); instantiationService.stub(IWorkbenchLayoutService, new TestLayoutService()); + instantiationService.stub(IDialogService, new TestDialogService()); + instantiationService.stub(IFileDialogService, new TestFileDialogService()); instantiationService.stub(IElectronService, new TestElectronService()); instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl)); instantiationService.stub(IHistoryService, new TestHistoryService()); @@ -420,6 +409,8 @@ export class TestFileDialogService implements IFileDialogService { public _serviceBrand: undefined; + private confirmResult!: ConfirmResult; + public defaultFilePath(_schemeFilter?: string): URI | undefined { return undefined; } @@ -450,6 +441,12 @@ export class TestFileDialogService implements IFileDialogService { public showOpenDialog(_options: IOpenDialogOptions): Promise { return Promise.resolve(undefined); } + public setConfirmResult(result: ConfirmResult): void { + this.confirmResult = result; + } + public showSaveConfirm(resources: string | URI[]): Promise { + return Promise.resolve(this.confirmResult); + } } export class TestLayoutService implements IWorkbenchLayoutService { @@ -463,6 +460,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { onZenModeChange: Event = Event.None; onCenteredLayoutChange: Event = Event.None; onFullscreenChange: Event = Event.None; + onMaximizeChange: Event = Event.None; onPanelPositionChange: Event = Event.None; onPartVisibilityChange: Event = Event.None; onLayout = Event.None; @@ -572,6 +570,12 @@ export class TestLayoutService implements IWorkbenchLayoutService { public resizePart(_part: Parts, _sizeChange: number): void { } public registerPart(part: Part): void { } + + isWindowMaximized() { + return false; + } + + public updateWindowMaximizedState(maximized: boolean): void { } } let activeViewlet: Viewlet = {} as any; diff --git a/yarn.lock b/yarn.lock index 8bd5a9e0fb3..e9078a4f13f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8150,10 +8150,10 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= -sudo-prompt@9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.0.0.tgz#eebedeee9fcd6f661324e6bb46335e3288e8dc8a" - integrity sha512-kUn5fiOk0nhY2oKD9onIkcNCE4Zt85WTsvOfSmqCplmlEvXCcPOmp1npH5YWuf8Bmyy9wLWkIxx+D+8cThBORQ== +sudo-prompt@9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0" + integrity sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA== supports-color@1.2.0: version "1.2.0" @@ -9017,10 +9017,10 @@ vscode-debugprotocol@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" - resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.1.tgz#e63d3f4a9bf3680dcb8f9304eed612323fd6926a" - integrity sha512-cmB72+qDoiCFJ1UKnGUBdGYfXzdpJ3bQM/D/+XhkVk5v7uZgLbYiCz5JcwVyk7NC7hSi5VGtQ4wihzmi12NeXw== +vscode-minimist@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/vscode-minimist/-/vscode-minimist-1.2.2.tgz#65403f44f0c6010d259b2271d36eb5c6f4ad8aab" + integrity sha512-DXMNG2QgrXn1jOP12LzjVfvxVkzxv/0Qa27JrMBj/XP2esj+fJ/wP2T4YUH5derj73Lc96dC8F25WyfDUbTpxQ== vscode-nls-dev@^3.3.1: version "3.3.1"